#! /usr/bin/env python3
# -*- coding: utf-8 -*-

# Tauon Music Box

# Copyright © 2015-2022, Taiko2k captain(dot)gxj(at)gmail.com

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# --------------------------------------------------------------------
# Preamble

# Welcome to the Tauon Music Box source code. I started this project when I was first
# learning python, as a result this code can be quite messy. No doubt I have
# written some things terribly wrong or inefficiently in places.
# I would highly recommend not using this project as an example on how to code cleanly or correctly.

# --------------------------------------------------------------------

import sys
import socket

n_version = "7.1.3"
t_version = "v" + n_version
t_title = 'Tauon Music Box'
t_id = 'tauonmb'
t_agent = "TauonMusicBox/" + n_version

print(f"{t_title} {t_version}")
print('Copyright 2015-2022 Taiko2k captain.gxj@gmail.com\n')

from t_modules import t_bootstrap
h = t_bootstrap.holder
t_window = h.w
renderer = h.r
logical_size = h.wl
window_size = h.wr
maximized = h.m
scale = h.s
window_opacity = h.o
draw_border = h.d
transfer_args_and_exit = h.e
old_window_position = h.ow
install_directory = h.id
pyinstaller_mode = h.py
phone = h.p
window_default_size = h.wdf

print(f"Window size: {window_size}")

import os
import pickle
import shutil

# Detect platform
windows_native = False
macos = False
msys = False
if sys.platform == 'win32':
    #system = 'windows'
    #windows_native = False
    system = 'linux'
    msys = True
else:
    system = 'linux'
    import fcntl

if sys.platform == "darwin":
    macos = True

if not windows_native:
    import gi
    from gi.repository import GLib


# Detect what desktop environment we are in to enable specific features
desktop = os.environ.get('XDG_CURRENT_DESKTOP')
#de_notify_support = desktop == 'GNOME' or desktop == 'KDE'
de_notify_support = False
draw_min_button = True
draw_max_button = True
left_window_control = False
xdpi = 0

from t_modules.t_extra import *

detect_macstyle = False
gtk_settings = None
mac_close = (253, 70, 70, 255)
mac_maximize = (254, 176, 36, 255)
mac_minimize = (42, 189, 49, 255)
try:
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk
    gtk_settings = Gtk.Settings().get_default()
    xdpi = gtk_settings.get_property("gtk-xft-dpi") / 1024
    if "minimize" not in str(gtk_settings.get_property("gtk-decoration-layout")):
        draw_min_button = False
    if "maximize" not in str(gtk_settings.get_property("gtk-decoration-layout")):
        draw_max_button = False
    if "close" in str(gtk_settings.get_property("gtk-decoration-layout")).split(":")[0]:
        left_window_control = True
    gtk_theme = str(gtk_settings.get_property("gtk-theme-name")).lower()
    print(f"GTK theme is: {gtk_theme}")
    for k, v in mac_styles.items():
        if k in gtk_theme:
            detect_macstyle = True
            if v is not None:
                mac_close = v[0]
                mac_maximize = v[1]
                mac_minimize = v[2]

except:
    print("Error accessing GTK settings")


# if system == "windows" or msys:
#     os.environ["PYSDL2_DLL_PATH"] = install_directory + "\\lib"

# Set data folders (portable mode)
user_directory = install_directory
config_directory = user_directory
cache_directory = os.path.join(user_directory, "cache")
home_directory = os.path.join(os.path.expanduser('~'))

asset_directory = os.path.join(install_directory, "assets")
svg_directory = os.path.join(install_directory, "assets/svg")
scaled_asset_directory = asset_directory

music_directory = os.path.join(os.path.expanduser('~'), "Music")
if not os.path.isdir(music_directory):
    music_directory = os.path.join(os.path.expanduser('~'), "music")

download_directory = os.path.join(os.path.expanduser('~'), "Downloads")

# Detect if we are installed or running portable
install_mode = False
flatpak_mode = False
snap_mode = False
if install_directory.startswith("/opt/")\
        or install_directory.startswith("/usr/")\
        or install_directory.startswith("/app/")\
        or install_directory.startswith("/snap/"):

    install_mode = True
    if install_directory[:6] == "/snap/":
        snap_mode = True
    if install_directory[:5] == "/app/":
        # Flatpak mode

        t_id = "com.github.taiko2k.tauonmb"
        print("Detected running as Flatpak")

        # [old / no longer used] Symlink fontconfig from host system as workaround for poor font rendering
        if os.path.exists(os.path.join(home_directory, ".var/app/com.github.taiko2k.tauonmb/config")):

            host_fcfg = os.path.join(home_directory, ".config/fontconfig/")
            flatpak_fcfg = os.path.join(home_directory, ".var/app/com.github.taiko2k.tauonmb/config/fontconfig")

            if os.path.exists(host_fcfg):

                # if os.path.isdir(flatpak_fcfg) and not os.path.islink(flatpak_fcfg):
                #     shutil.rmtree(flatpak_fcfg)
                if os.path.islink(flatpak_fcfg):
                    print("-- Symlink to fonconfig exists, removing")
                    os.unlink(flatpak_fcfg)
                # else:
                #     print("-- Symlinking user fonconfig")
                #     #os.symlink(host_fcfg, flatpak_fcfg)

        flatpak_mode = True

# If we're installed, use home data locations
if (install_mode and system == 'linux') or macos:

    cache_directory = os.path.join(GLib.get_user_cache_dir(), "TauonMusicBox")
    user_directory = os.path.join(GLib.get_user_data_dir(), "TauonMusicBox")
    config_directory = os.path.join(GLib.get_user_data_dir(), "TauonMusicBox")

    if not os.path.isdir(user_directory):
        os.makedirs(user_directory)

    if not os.path.isdir(config_directory):
        os.makedirs(config_directory)

    if snap_mode:
        print("Installed as Snap")
    elif flatpak_mode:
        print("Installed as Flatpak")
    else:
        print("Running from installed location")

    print("User files location: " + user_directory)

    if not os.path.isdir(os.path.join(user_directory, "encoder")):
        os.makedirs(os.path.join(user_directory, "encoder"))


elif (system == 'windows' or msys) and ('Program Files' in install_directory or
                                  os.path.isfile(install_directory + '\\unins000.exe')):

    user_directory = os.path.expanduser('~').replace("\\", '/') + "/Music/TauonMusicBox"
    config_directory = user_directory
    cache_directory = user_directory + "\\cache"
    print("User Directroy: ", end="")
    print(user_directory)
    install_mode = True
    if not os.path.isdir(user_directory):
        os.makedirs(user_directory)


else:
    print("Running in portable mode")

    user_directory = os.path.join(install_directory, "user-data")
    config_directory = user_directory

    if not os.path.isdir(user_directory):
        os.makedirs(user_directory)

if not os.path.isfile(os.path.join(user_directory, "state.p")):
    if os.path.isdir(cache_directory):
        print("Clearing old cache directory")
        print(cache_directory)
        shutil.rmtree(cache_directory)

n_cache_dir = os.path.join(cache_directory, "network")
e_cache_dir = os.path.join(cache_directory, "export")
g_cache_dir = os.path.join(cache_directory, "gallery")
a_cache_dir = os.path.join(cache_directory, "artist")
r_cache_dir = os.path.join(cache_directory, "radio-thumbs")
b_cache_dir = os.path.join(user_directory, "artist-backgrounds")

if not os.path.isdir(n_cache_dir):
    os.makedirs(n_cache_dir)
if not os.path.isdir(e_cache_dir):
    os.makedirs(e_cache_dir)
if not os.path.isdir(g_cache_dir):
    os.makedirs(g_cache_dir)
if not os.path.isdir(a_cache_dir):
    os.makedirs(a_cache_dir)
if not os.path.isdir(b_cache_dir):
    os.makedirs(b_cache_dir)
if not os.path.isdir(r_cache_dir):
    os.makedirs(r_cache_dir)

if not os.path.isdir(os.path.join(user_directory, "artist-pictures")):
    os.makedirs(os.path.join(user_directory, "artist-pictures"))

if not os.path.isdir(os.path.join(user_directory, "theme")):
    os.makedirs(os.path.join(user_directory, "theme"))

if system == 'linux':
    system_config_directory = GLib.get_user_config_dir()
    xdg_dir_file = os.path.join(system_config_directory, 'user-dirs.dirs')

    if os.path.isfile(xdg_dir_file):
        with open(xdg_dir_file) as f:
            for line in f.readlines():
                if line.startswith("XDG_MUSIC_DIR="):
                    music_directory = os.path.expanduser(os.path.expandvars(line.split("=")[1].strip().replace('"', "")))
                    print(f"Found XDG-Music: {music_directory}")
                if line.startswith("XDG_DOWNLOAD_DIR="):
                    target = os.path.expanduser(os.path.expandvars(line.split("=")[1].strip().replace('"', "")))
                    if os.path.isdir(target):
                        download_directory = target
                    print(f"Found XDG-Downloads: {download_directory}")

if not os.path.isdir(music_directory):
    music_directory = None

#transfer_target = user_directory + "/transfer.p"
# print('Argument List: ' + str(sys.argv))
print('Install directory: ' + install_directory)

old_backend = 2
# # Find home music folder
# music_folder = os.path.join(os.path.expanduser('~'), "Music")
# if not os.path.isdir(music_folder):
#     music_folder = None

# Things for detecting and launching programs outside of flatpak sandbox
def whicher(target):
    try:
        if flatpak_mode:
            complete = subprocess.run(shlex.split("flatpak-spawn --host which " + target), stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)
            r = complete.stdout.decode()
            return "bin/" + target in r
        else:
            return shutil.which(target)
    except:
        return False

launch_prefix = ""
if flatpak_mode:
    launch_prefix = "flatpak-spawn --host "

ffmpeg = "ffmpeg"
if pyinstaller_mode:
     os.environ["PATH"] = os.environ["PATH"] + ":" + sys._MEIPASS

# ffmpeg = sys._MEIPASS + "/ffmpeg"
# import subprocess
# subprocess.call("ffmpeg", stdout=subprocess.PIPE, shell=False)

# -------------------------------
# Single Instancing

pid = os.getpid()

if msys:
    if os.path.isfile('.gitignore') and False:
        print("Dev mode, ignoring single instancing")
    else:
        pid_file = os.path.join(user_directory, 'program.pid')
        try:
            if os.path.isfile(pid_file):
                os.remove(pid_file)
            fp = open(pid_file, 'w')
        except IOError:
            # another instance is running
            print("Program is already running")
            pickle.dump(sys.argv, open(user_directory + "/transfer.p", "wb"))
            sys.exit()


if system == 'windows':
    from win32event import CreateMutex
    from win32api import CloseHandle, GetLastError
    from winerror import ERROR_ALREADY_EXISTS


    class singleinstance:
        """ Limits application to single instance """

        def __init__(self):
            self.mutexname = "tauonmusicbox_{A0E858DF-985E-4907-B7FB-7D732C3FC3B9}"
            self.mutex = CreateMutex(None, False, self.mutexname)
            self.lasterror = GetLastError()

        def aleradyrunning(self):
            return (self.lasterror == ERROR_ALREADY_EXISTS)

        def __del__(self):
            if self.mutex:
                CloseHandle(self.mutex)

    lock = singleinstance()

    if lock.aleradyrunning():
        print("Program is already running")
        pickle.dump(sys.argv, open(user_directory + "/transfer.p", "wb"))
        sys.exit()

# ------------------------------------
# Continue startup

from sdl2 import *
from sdl2.sdlimage import *
from ctypes import pointer

SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, b'1')
if desktop == "KDE":
    try:
        SDL_SetHint(b"SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR", b"0")
    except:
        print("Old version of SDL2 detected")
try:
    SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, b"1")
except:
    print("old version of SDL detected")

# draw_border = True
# window_default_size = [1120, 600]
# window_size = window_default_size
# logical_size = [0, 0]
# window_opacity = 1
# scale = 1
# maximized = False
# old_window_position = None
#
# try:
#
#     state_file = open(user_directory + "/window.p", "rb")
#     save = pickle.load(state_file)
#
#     draw_border = save[0]
#     window_size = save[1]
#     logical_size = save[1]
#     window_opacity = save[2]
#     scale = save[3]
#     maximized = save[4]
#     old_window_position = save[5]
#
#     del save
#
# except:
#     print('No previous window state')



## Init SDL2

# SDL_Init(SDL_INIT_VIDEO)
#
# err = SDL_GetError()
# if err:
#     print(f"SDL init error: {err.decode()}")
#     SDL_ClearError()
# #
# window_title = t_title
# window_title = window_title.encode('utf-8')

# flags = SDL_WINDOW_RESIZABLE # SDL_WINDOW_HIDDEN |
#
# if draw_border:
#     flags |= SDL_WINDOW_BORDERLESS
#
# if old_window_position is None:
#     o_x = SDL_WINDOWPOS_UNDEFINED
#     o_y = SDL_WINDOWPOS_UNDEFINED
# else:
#     o_x = old_window_position[0]
#     o_y = old_window_position[1]
#
# t_window = SDL_CreateWindow(window_title,
#                             o_x, o_y,
#                             window_size[0], window_size[1],
#                             flags)

#
# err = SDL_GetError()
# if err:
#     print(f"SDL window error: {err.decode()}")
#     SDL_ClearError()
#
# if maximized:
#     SDL_MaximizeWindow(t_window)
#
#
# renderer = SDL_CreateRenderer(t_window, 0, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)

# window_surface = SDL_GetWindowSurface(t_window)

# SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND)
#
# err = SDL_GetError()
# if err:
#     print(f"SDL render error: {err.decode()}")
#     SDL_ClearError()

# display_index = SDL_GetWindowDisplayIndex(t_window)
# display_bounds = SDL_Rect(0, 0)
# SDL_GetDisplayBounds(display_index, display_bounds)

if not macos:
    icon = IMG_Load(os.path.join(asset_directory, "icon-64.png").encode())
else:
    icon = IMG_Load(os.path.join(asset_directory, "tau-mac.png").encode())

SDL_SetWindowIcon(t_window, icon)
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best".encode())

if not phone:
    if macos:
        SDL_SetWindowMinimumSize(t_window, 560, 330)
    else:
        SDL_SetWindowMinimumSize(t_window, round(560 * scale), round(330 * scale))


max_window_tex = 1000
if window_size[0] > max_window_tex or window_size[1] > max_window_tex:

    while window_size[0] > max_window_tex:
        max_window_tex += 1000
    while window_size[1] > max_window_tex:
        max_window_tex += 1000


main_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, max_window_tex, max_window_tex)
main_texture_overlay_temp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, max_window_tex, max_window_tex)

overlay_texture_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, 300, 300)
SDL_SetTextureBlendMode(overlay_texture_texture, SDL_BLENDMODE_BLEND)
SDL_SetRenderTarget(renderer, overlay_texture_texture)
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0)
SDL_RenderClear(renderer)
SDL_SetRenderTarget(renderer, None)


tracklist_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, max_window_tex, max_window_tex)
tracklist_texture_rect = SDL_Rect(0, 0, max_window_tex, max_window_tex)
SDL_SetTextureBlendMode(tracklist_texture, SDL_BLENDMODE_BLEND)


SDL_SetRenderTarget(renderer, None)

# Paint main texture
SDL_SetRenderTarget(renderer, main_texture)
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255)

SDL_SetRenderTarget(renderer, main_texture_overlay_temp)
SDL_SetTextureBlendMode(main_texture_overlay_temp, SDL_BLENDMODE_BLEND)
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255)
SDL_RenderClear(renderer)


#
# SDL_SetRenderTarget(renderer, None)
# SDL_SetRenderDrawColor(renderer, 7, 7, 7, 255)
# SDL_RenderClear(renderer)
# #SDL_RenderPresent(renderer)
#
# SDL_SetWindowOpacity(t_window, window_opacity)


class LoadImageAsset:
    assets = []
    def __init__(self, path, is_full_path=False, reload=False, scale_name=""):
        if not reload:
            self.assets.append(self)

        self.path = path
        self.scale_name = scale_name

        raw_image = IMG_Load(self.path.encode())
        self.sdl_texture = SDL_CreateTextureFromSurface(renderer, raw_image)

        p_w = pointer(c_int(0))
        p_h = pointer(c_int(0))
        SDL_QueryTexture(self.sdl_texture, None, None, p_w, p_h)

        if is_full_path:
            SDL_SetTextureAlphaMod(self.sdl_texture, prefs.custom_bg_opacity)

        self.rect = SDL_Rect(0, 0, p_w.contents.value, p_h.contents.value)
        SDL_FreeSurface(raw_image)
        self.w = p_w.contents.value
        self.h = p_h.contents.value

    def reload(self):
        SDL_DestroyTexture(self.sdl_texture)
        if self.scale_name:
            self.path = os.path.join(scaled_asset_directory, self.scale_name)
        self.__init__(self.path, reload=True, scale_name=self.scale_name)

    def render(self, x, y, colour=None):
        self.rect.x = round(x)
        self.rect.y = round(y)
        SDL_RenderCopy(renderer, self.sdl_texture, None, self.rect)


class WhiteModImageAsset:
    assets = []
    def __init__(self, path, reload=False, scale_name=""):
        if not reload:
            self.assets.append(self)
        self.path = path
        self.scale_name = scale_name
        raw_image = IMG_Load(path.encode())
        self.sdl_texture = SDL_CreateTextureFromSurface(renderer, raw_image)
        self.colour = [255, 255, 255, 255]
        p_w = pointer(c_int(0))
        p_h = pointer(c_int(0))
        SDL_QueryTexture(self.sdl_texture, None, None, p_w, p_h)
        self.rect = SDL_Rect(0, 0, p_w.contents.value, p_h.contents.value)
        SDL_FreeSurface(raw_image)
        self.w = p_w.contents.value
        self.h = p_h.contents.value

    def reload(self):
        SDL_DestroyTexture(self.sdl_texture)
        if self.scale_name:
            self.path = os.path.join(scaled_asset_directory, self.scale_name)
        self.__init__(self.path, reload=True, scale_name=self.scale_name)

    def render(self, x, y, colour):
        if colour != self.colour:
            SDL_SetTextureColorMod(self.sdl_texture, colour[0], colour[1], colour[2])
            SDL_SetTextureAlphaMod(self.sdl_texture, colour[3])
            self.colour = colour
        self.rect.x = round(x)
        self.rect.y = round(y)
        SDL_RenderCopy(renderer, self.sdl_texture, None, self.rect)


loaded_asset_dc = {}
def asset_loader(name, mod=False):
    if name in loaded_asset_dc:
        return loaded_asset_dc[name]

    target = os.path.join(scaled_asset_directory, name)
    if mod:
        item = WhiteModImageAsset(target, scale_name=name)
    else:
        item = LoadImageAsset(target, scale_name=name)
    loaded_asset_dc[name] = item
    return item

# loading_image = asset_loader('loading.png')

if maximized:
    i_x = pointer(c_int(0))
    i_y = pointer(c_int(0))
    import time
    time.sleep(0.02)
    SDL_PumpEvents()
    SDL_GetWindowSize(t_window, i_x, i_y)
    logical_size[0] = i_x.contents.value
    logical_size[1] = i_y.contents.value
    SDL_GL_GetDrawableSize(t_window, i_x, i_y)
    window_size[0] = i_x.contents.value
    window_size[1] = i_y.contents.value

# loading_image.render(window_size[0] // 2 - loading_image.w // 2, window_size[1] // 2 - loading_image.h // 2)
# SDL_RenderPresent(renderer)

# if install_directory != config_directory and not os.path.isfile(os.path.join(config_directory, "config.txt")):
#     print("Config file is missing... copying template from program files")
#     shutil.copy(os.path.join(install_directory, "config.txt"), config_directory)

if install_directory != config_directory and not os.path.isfile(os.path.join(config_directory, "input.txt")):
    print("Input config file is missing... copying template from program files")
    shutil.copy(os.path.join(install_directory, "input.txt"), config_directory)

last_fm_enable = False

try:
    import setproctitle
    setproctitle.setproctitle("tauonmb")
except:
    print("Could not set process title.")


# try:
#     import rpc
#     discord_allow = True
# except:
#     pass
try:
    from pypresence import Presence
    import asyncio
    discord_allow = True
except:
    discord_allow = False

if snap_mode:
    discord_allow = False

if system == "windows":
    import win32con, win32api, win32gui, win32ui, comtypes
    import atexit
try:
    import pylast
    last_fm_enable = True
except:
    raise
    print("PyLast moduel not found, last fm will be disabled.")

use_natsort = False
try:
    import natsort
    use_natsort = True
except:
    print("Warning: Python module natsort not found")

import time
import ctypes
import ctypes.util
import random
import threading
import logging
import io
import copy
import subprocess
import urllib.parse
import urllib.request
import datetime
import shlex
import math
import locale as py_locale
import webbrowser
import base64
import re
import zipfile
import warnings
import colorsys
import requests
import stat
import hashlib
import platform
import gettext
import secrets
import json
import glob
import xml.etree.ElementTree as ET
import musicbrainzngs
from pathlib import Path
from xml.sax.saxutils import escape, unescape
from ctypes import *
from send2trash import send2trash
from isounidecode import unidecode
from collections import OrderedDict

musicbrainzngs.set_useragent("TauonMusicBox", n_version, "https://github.com/Taiko2k/TauonMusicBox")

arch = platform.machine()


#print(arch)
# -----------------------------------------------------------
# Detect locale for translations (currently none available)

def _(message):
    return message

try:
    py_locale.setlocale(py_locale.LC_ALL, '')
except:
    print("SET LOCALE ERROR")

# ------------------------------------------------

if system == 'windows':
    os.environ["PYSDL2_DLL_PATH"] = install_directory + "\\lib"
elif not msys and not macos:
    gi.require_version('Notify', '0.7')  # Doesn't really matter, just stops it from complaining
    from gi.repository import Notify


# Other imports

from PIL import Image, ImageDraw, ImageFilter
import mutagen
import mutagen.id3
import mutagen.flac
import mutagen.mp4
def no_padding(info):
    # this will remove all padding
    return 0

if not os.environ.get('SDL_VIDEODRIVER') == "wayland":
    os.environ['GDK_BACKEND'] = "x11"

from t_modules.t_tagscan import Flac
from t_modules.t_tagscan import Opus
from t_modules.t_tagscan import Ape
from t_modules.t_tagscan import Wav
from t_modules.t_tagscan import M4a
from t_modules.t_tagscan import parse_picture_block
from t_modules.t_cast import *
from t_modules.t_stream import *
from t_modules.t_lyrics import *
from t_modules.t_themeload import load_theme
from t_modules.t_spot import SpotCtl
from t_modules.t_search import *


if system == 'linux':
    from t_modules import t_topchart

if system == "linux" and not macos and not msys:
    from t_modules.t_dbus import Gnome
    from t_modules.t_gdk_extra import *

import_cursors = False

if desktop == "GNOME":
    print("Using crash workaround for gdk crash")
    import_cursors = True

if import_cursors and system == "linux" and not macos and not msys:
    c_br = cursor_get_gdk(4)
    c_rs = cursor_get_gdk(8)
    c_ts = cursor_get_gdk(9)
    c_ls = cursor_get_gdk(10)
    c_bs = cursor_get_gdk(11)

# Setting various timers

message_box_min_timer = Timer()
cursor_blink_timer = Timer()
animate_monitor_timer = Timer()
min_render_timer = Timer()
check_file_timer = Timer()
vis_rate_timer = Timer()
vis_decay_timer = Timer()
scroll_timer = Timer()
perf_timer = Timer()
quick_d_timer = Timer()
core_timer = Timer()
sleep_timer = Timer()
gallery_select_animate_timer = Timer()
gallery_select_animate_timer.force_set(10)
search_clear_timer = Timer()
gall_pl_switch_timer = Timer()
gall_pl_switch_timer.force_set(999)
d_click_timer = Timer()
d_click_timer.force_set(10)
lyrics_check_timer = Timer()
scroll_hide_timer = Timer(100)
scroll_gallery_hide_timer = Timer(100)
get_lfm_wait_timer = Timer(10)
lyrics_fetch_timer = Timer(10)
gallery_load_delay = Timer(10)
queue_add_timer = Timer(100)
toast_love_timer = Timer(100)
toast_mode_timer = Timer(100)
scrobble_warning_timer = Timer(1000)
sync_file_timer = Timer(1000)
sync_file_update_timer = Timer(1000)
sync_get_device_click_timer = Timer(100)

f_store = FunctionStore()

after_scan = []

search_string_cache = {}
search_dia_string_cache = {}

vis_update = False


# GUI Variables -------------------------------------------------------------------------------------------

# Variables now go in the gui, pctl, input and prefs class instances. The following just haven't been moved yet.

class DConsole:
    def __init__(self):
        self.messages = []
        self.show = False
    def print(self, message, level=0):
        
        if len(self.messages) > 50:
            del self.messages[0]
        
        dtime = datetime.datetime.now()
        self.messages.append((message, level, dtime, Timer()))
        if level > 0:
            print(message)

console = DConsole()

spot_cache_saved_albums = []

resize_mode = False

side_panel_text_align = 0

album_mode = False
spec_smoothing = True

# gui.offset_extra = 0

old_album_pos = -55

album_dex = []
album_artist_dict = {}
row_len = 5
last_row = 0
album_v_gap = 66
album_h_gap = 30
album_v_slide_value = 50

album_mode_art_size = int(200 * scale)

time_last_save = 0

b_info_y = int(window_size[1] * 0.7)  # For future possible panel below playlist
fullscreen = False

volume_store = 50  # Used to save the previous volume when muted

# row_alt = False

to_get = 0  # Used to store temporary import count display
to_got = 0

editline = ""
#gui.rsp = True
quick_drag = False


# Playlist Panel
pl_view_offset = 0
pl_rect = (2, 12, 10, 10)

theme = 7
scroll_enable = True
scroll_timer = Timer()
scroll_timer.set()
scroll_opacity = 0
break_enable = True

source = None

album_playlist_width = 430

update_title = False

playlist_hold_position = 0
playlist_hold = False
selection_stage = 0

shift_selection = []

gen_codes = {}
# Control Variables--------------------------------------------------------------------------

mouse_down = False
right_down = False
click_location = [200, 200]
last_click_location = [0, 0]
mouse_position = [0, 0]
mouse_up_position = [0, 0]

k_input = True
key_shift_down = False
drag_mode = False
side_drag = False
clicked = False

# Player Variables----------------------------------------------------------------------------

format_colours = {  # These are the colours used for the label icon in UI 'track info box'
    "MP3": [255, 130, 80, 255],
    "FLAC": [156, 249, 79, 255],
    "M4A": [81, 220, 225, 255],
    "OGG": [244, 244, 78, 255],
    "OGA": [244, 244, 78, 255],
    "WMA": [213, 79, 247, 255],
    "APE": [247, 79, 79, 255],
    "TTA": [94, 78, 244, 255],
    "OPUS": [247, 79, 146, 255],
    "AAC": [79, 247, 168, 255],
    "WV": [229, 23, 18, 255],
    "PLEX": [229, 160, 13, 255],
    "KOEL": [111, 98, 190, 255],
    "SUB": [235, 140, 20, 255],
    "SPTY": [30, 215, 96, 255],
    "JELY": [190, 100, 210, 255],
    "XM": [50, 50, 50, 255],
    "MOD": [50, 50, 50, 255],
    "S3M": [50, 50, 50, 255],
    "IT": [50, 50, 50, 255],
    "MPTM": [50, 50, 50, 255],
}

# These will be the extensions of files to be added when importing
DA_Formats = {'mp3', 'wav', 'opus', 'flac', 'ape',
              'm4a', 'ogg', 'oga', 'aac', 'tta', 'wv', 'wma'}


MOD_Formats = {'xm', 'mod', 's3m', 'it', 'mptm'}
DA_Formats |= MOD_Formats

Archive_Formats = {'zip'}

if whicher('unrar'):
    Archive_Formats.add("rar")

if whicher('7z'):
    Archive_Formats.add("7z")



cargo = []

# ---------------------------------------------------------------------
# Player variables

# pl_follow = False

# List of encodings to check for with the fix mojibake function
encodings = ['cp932', 'utf-8', 'big5hkscs', 'gbk']  # These seem to be the most common for Japanese

track_box = False

transcode_list = []
transcode_state = ""

taskbar_progress = True
QUE = []

playing_in_queue = 0
draw_sep_hl = False

# -------------------------------------------------------------------------------
# Playlist Variables
playlist_view_position = 0
playlist_playing = -1
playlist_selected = -1

loading_in_progress = False

core_use = 0

random_mode = False
repeat_mode = False

# Functions to generate empty playlist
# Playlist is [Name, playing, playlist, position, hide folder title, selected, uid, last_folder, hidden(bool)]

# 0 Name (string)
# 1 Playing (int)
# 2 list  (list of int)
# 3 View Position (int)
# 4 hide playlist folder titles (bool)
# 5 selected (int)
# 6 Unique id (int)
# 7 last folder import path (string)
# 8 hidden (bool)
# 9 Locked (bool)
# 10 Filter parent playlist id (string)


def uid_gen():
    return random.randrange(1, 100000000)

notify_change = lambda : None

def pl_gen(title='Default',
           playing=0,
           playlist=None,
           position=0,
           hide_title=0,
           selected=0,
           parent="",
           hidden=False):

    if playlist == None:
        playlist = []

    notify_change()

    return copy.deepcopy([title, playing, playlist, position, hide_title, selected, uid_gen(), [], hidden, False, parent])

multi_playlist = [pl_gen()] # Create default playlist

def queue_item_gen(trackid, position, pl_id, type=0, album_stage=0):
    # type; 0 is track, 1 is album
    auto_stop = False

    return [trackid, position, pl_id, type, album_stage, uid_gen(), auto_stop]


default_playlist = multi_playlist[0][2]
playlist_active = 0


quick_search_mode = False
search_index = 0


# ----------------------------------------
# Playlist right click menu

r_menu_index = 0
r_menu_position = 0

# Library and loader Variables--------------------------------------------------------
master_library = {}

cue_list = []

LC_None = 0
LC_Done = 1
LC_Folder = 2
LC_File = 3

loaderCommand = LC_None
loaderCommandReady = False

master_count = 0

load_orders = []

volume = 75

folder_image_offsets = {}
db_version = 0.0

albums = []
album_position = 0


class Prefs:    # Used to hold any kind of settings

    def gen_gst_out(self):
        # "rgvolume pre-amp=-2 fallback-gain=-6 ! autoaudiosink"
        line = ""
        if prefs.replay_gain:
            line += f"rgvolume pre-amp={prefs.replay_preamp} fallback-gain=0 album-mode="
            if prefs.replay_gain == 1:
                line += "false"
            else:
                line += "true"

        if line:
            line += " ! "

        #print(prefs.gst_device)
        #print(pctl.gst_outputs)

        if prefs.gst_device == "Auto":
            line += "autoaudiosink"
        elif prefs.gst_device == "PipeWire":
            line += "pipewiresink"
        elif prefs.gst_device == "PulseAudio":
            line += f"pulsesink client-name=\"{t_title}\""
        elif prefs.gst_device == "JACK":
            line += f"jackaudiosink client-name=\"{t_title}\""
        elif prefs.gst_device == "ALSA":
            line += "alsasink"
        elif prefs.gst_device in pctl.gst_outputs:
            line += f"{pctl.gst_outputs[prefs.gst_device][0]} device={pctl.gst_outputs[prefs.gst_device][1]} client-name=\"{t_title}\""
        else:
            print("Device not found, fallback to PulseAudio")

        return line

    def __init__(self):
        self.colour_from_image = False
        self.dim_art = False
        self.prefer_side = True  # Saves whether side panel is shown or not
        self.pause_fade_time = 400
        self.change_volume_fade_time = 400
        self.cross_fade_time = 700 #700
        self.volume_wheel_increment = 2
        self.encoder_output = user_directory + '/encoder/'
        if music_directory is not None:
            self.encoder_output = music_directory + '/encode-output/'
        self.rename_folder_template = "<albumartist> - <album>"
        self.rename_tracks_template = "<tn>. <artist> - <title>.<ext>"

        self.enable_web = False
        self.allow_remote = False
        self.expose_web = True

        self.enable_transcode = True
        self.show_rym = False
        self.show_band = False
        self.show_wiki = False
        self.show_transfer = True
        self.show_queue = True
        self.prefer_bottom_title = True
        self.append_date = True

        self.transcode_codec = 'opus'
        self.transcode_mode = 'single'
        self.transcode_bitrate = 64

        # self.line_style = 1
        self.device = 1
        self.device_name = ""

        self.cache_gallery = True
        self.gallery_row_scroll = True
        self.gallery_scroll_wheel_px = 90

        self.playlist_font_size = 15
        self.playlist_row_height = 27

        self.tag_editor_name = ""
        self.tag_editor_target = ""
        self.tag_editor_path = ""

        self.use_title = False
        self.auto_extract = False
        self.auto_del_zip = False
        self.pl_thumb = False

        self.use_custom_fonts = False
        self.linux_font = "Noto Sans, Noto Sans CJK JP"
        self.linux_font_semibold = "Noto Sans, Noto Sans CJK JP Medium"
        self.linux_font_bold = "Noto Sans, Noto Sans CJK JP Bold"
        self.linux_font_condensed = "Noto Sans ExtraCondensed"
        self.linux_font_condensed_bold = "Noto Sans ExtraCondensed Bold"

        self.spec2_scroll = True

        self.spec2_p_base = [10, 10, 100]
        self.spec2_p_multiply = [0.5, 1, 1]

        self.spec2_base = [10, 10, 100]
        self.spec2_multiply = [0.5, 1, 1]
        self.spec2_colour_setting = 'custom'

        self.auto_lfm = False
        self.scrobble_mark = False
        self.enable_mpris = True

        self.mkey = True
        self.replay_gain = 0  # 0=off 1=track 2=album
        self.replay_preamp = 0  # db
        self.radio_page_lyrics = True

        self.show_gimage = False
        self.end_setting = "stop"
        self.show_gen = False
        self.show_lyrics_side = True

        self.log_vol = False

        self.ui_scale = scale
        self.last_device = "PulseAudio Sound Server"

        # if flatpak_mode:

        self.transcode_opus_as = False

        self.discord_active = False
        self.discord_ready = False
        self.disconnect_discord = False

        self.monitor_downloads = True
        self.extract_to_music = False

        self.enable_lb = False
        self.lb_token = ""

        self.use_jump_crossfade = True
        self.use_transition_crossfade = False
        self.use_pause_fade = True

        self.show_notifications = True

        self.true_shuffle = True
        self.append_total_time = False
        self.backend = 4  # 2 gstreamer, 4 phazor

        self.album_repeat_mode = False # passed to pctl
        self.album_shuffle_mode = False # passed to pctl

        self.finish_current = False  # Finish current album when adding to queue

        self.reload_play_state = False # Resume playback on app restart
        self.reload_state = None

        self.mono = False

        self.last_fm_token = None
        self.last_fm_username = ""

        self.use_card_style = True

        self.plex_username = ""
        self.plex_password = ""
        self.plex_servername = ""

        self.koel_username = "admin@example.com"
        self.koel_password = "admin"
        self.koel_server_url = "http://localhost:8050"

        self.auto_lyrics = False  # Function has been disabled
        self.jelly_username = ""
        self.jelly_password = ""
        self.jelly_server_url = "http://localhost:8096"

        self.auto_lyrics_checked = []

        self.show_side_art = True
        self.always_pin_playlists = True

        self.user_directory = user_directory
        self.cache_directory = cache_directory

        self.window_opacity = window_opacity
        self.gallery_single_click = True
        self.custom_bg_opacity = 40

        self.tabs_on_top = True
        self.desktop = desktop

        self.dc_device = False  # (BASS) Disconnect device on pause
        if desktop == "KDE":
            self.dc_device = True

        self.showcase_vis = True
        self.show_lyrics_showcase = True

        self.spec2_colour_mode = 0
        self.flatpak_mode = flatpak_mode

        self.device_buffer = 70

        self.eq = [0.0] * 10
        self.use_eq = False

        self.bio_large = False
        self.discord_allow = discord_allow
        self.discord_show = False

        self.min_to_tray = False

        self.guitar_chords = False
        self.prefer_synced_lyrics = True
        self.sync_lyrics_time_offset = 0

        self.playback_follow_cursor = False
        self.short_buffer = False

        self.gst_output = "rgvolume pre-amp=-2 fallback-gain=-6 ! autoaudiosink"

        self.art_bg = False
        self.art_bg_stronger = 1
        self.art_bg_opacity = 10
        self.art_bg_blur = 9
        self.art_bg_always_blur = False

        self.random_mode = False
        self.repeat_mode = False

        self.failed_artists = []
        self.failed_background_artists = []

        self.artist_list = False
        self.auto_sort = False

        self.transcode_inplace = False

        self.bg_showcase_only = False

        self.lyrics_enables = []

        self.fatvap = "6b2a9499238ce6416783fc8129b8ac67"

        self.fanart_notify = True
        self.discogs_pat = ""

        self.artist_list_prefer_album_artist = True

        self.mini_mode_mode = 0
        self.dc_device_setting = "on"

        self.download_dir1 = ""
        self.dd_index = False

        self.broadcast_port = 8000
        self.metadata_page_port = 7590
        self.broadcast_bitrate = 128

        self.custom_encoder_output = ""
        self.column_aa_fallback_artist = False

        self.meta_persists_stop = False
        self.meta_shows_selected = False
        self.meta_shows_selected_always = False

        self.left_align_album_artist_title = False
        self.stop_notifications_mini_mode = False
        self.scale_want = 1
        self.x_scale = True
        self.hide_queue = True
        self.show_playlist_list = True
        self.thin_gallery_borders = False
        self.show_current_on_transition = False

        self.force_subpixel_text = False
        if gtk_settings:
            if gtk_settings.get_property("gtk-xft-rgba") == "rgb":
                self.force_subpixel_text = True

        self.chart_rows = 3
        self.chart_columns = 3
        self.chart_bg = [7, 7, 7]
        self.chart_text = True
        self.chart_font = "Monospace 10"
        self.chart_tile = False

        self.chart_cascade = False
        self.chart_c1 = 5
        self.chart_c2 = 6
        self.chart_c3 = 10
        self.chart_d1 = 2
        self.chart_d2 = 2
        self.chart_d3 = 2

        self.art_in_top_panel = True
        self.always_art_header = False

        #self.center_bg = True
        self.ui_lang = 'auto'
        self.side_panel_layout = 0
        self.use_absolute_track_index = False

        self.hide_bottom_title = True
        self.auto_goto_playing = False

        self.diacritic_search = True
        self.increase_gallery_row_spacing = False
        self.center_gallery_text = False

        self.tracklist_y_text_offset = 0
        self.theme_name = "Turbo"
        self.left_panel_mode = "playlist"

        self.folder_tree_codec_colours = False

        self.network_stream_bitrate = 0  # 0 is off

        self.show_side_lyrics_art_panel = True

        self.gst_use_custom_output = False
        self.gst_device = "PulseAudio"
        if macos:
            self.gst_device = "Auto"

        self.notify_include_album = True

        self.auto_dl_artist_data = False

        self.enable_fanart_artist = False
        self.enable_fanart_bg = False
        self.enable_fanart_cover = False

        self.always_auto_update_playlists = False

        self.subsonic_server = "http://localhost:4040"
        self.subsonic_user = ""
        self.subsonic_password = ""
        self.subsonic_password_plain = False

        self.subsonic_playlists = {}

        self.write_ratings = False
        self.rating_playtime_stars = False

        self.lyrics_subs = {}

        self.radio_urls = []

        self.lyric_metadata_panel_top = False
        self.showcase_overlay_texture = False

        self.sync_target = ""
        self.sync_deletes = False
        self.sync_playlist = None
        self.download_playlist = None

        self.sep_genre_multi = False
        self.topchart_sorts_played = True

        self.spot_client = ""
        self.spot_secret = ""
        self.spot_mode = False
        self.launch_spotify_web = False
        self.remove_network_tracks = False
        self.bypass_transcode = False
        self.force_hide_max_button = False
        self.zoom_art = False
        self.auto_rec = False
        self.radio_record_codec = "OPUS"
        self.pa_fast_seek = False
        self.save_window_position = False
        self.spotify_token = ""

        self.use_libre_fm = False
        self.back_restarts = False

        self.old_playlist_box_position = 0
        self.listenbrainz_url = ""
        self.maloja_enable = False
        self.maloja_url = ""
        self.maloja_key = ""

        self.scrobble_hold = False

        self.artist_list_sort_mode = "alpha"

        self.phazor_device_selected = "Default"
        self.phazor_devices = {"Default": "Default"}
        self.bg_flips = set()
        self.use_tray = False
        self.tray_show_title = False
        self.drag_to_unpin = True
        self.enable_remote = False

        self.artist_list_style = 1
        self.discord_enable = False
        self.stop_end_queue = False

        self.block_suspend = False
        self.smart_bypass = True
        self.seek_interval = 15
        self.shuffle_lock = False
        self.premium = False
        self.power_save = False
        if macos or phone:
            self.power_save = True
        self.left_window_control = macos or left_window_control
        self.macstyle = macos or detect_macstyle
        self.radio_thumb_bans = []
        self.show_nag = False

        self.playlist_exports = {}
        self.show_chromecast = False

prefs = Prefs()


def open_uri(uri):
    print("OPEN URI")
    load_order = LoadClass()

    for w in range(len(pctl.multi_playlist)):
        if pctl.multi_playlist[w][0] == "Default":
            load_order.playlist = pctl.multi_playlist[w][6]
            break
    else:
        pctl.multi_playlist.append(pl_gen())
        load_order.playlist = pctl.multi_playlist[len(pctl.multi_playlist) - 1][6]
        switch_playlist(len(pctl.multi_playlist) - 1)

    load_order.target = str(urllib.parse.unquote(uri)).replace("file:///", "/").replace("\r", "")

    if gui.auto_play_import is False:
        load_order.play = True
        gui.auto_play_import = True

    load_orders.append(copy.deepcopy(load_order))
    gui.update += 1


class GuiVar:   # Use to hold any variables for use in relation to UI
    def update_layout(self):
        global update_layout
        update_layout = True

    def show_message(self, line1, line2="", line3="", mode="info"):
        show_message(line1, line2, line3, mode=mode)

    def delay_frame(self, t):
        gui.frame_callback_list.append(TestTimer(t))

    def destroy_textures(self):
        SDL_DestroyTexture(self.spec4_tex)
        SDL_DestroyTexture(self.spec1_tex)
        SDL_DestroyTexture(self.spec2_tex)
        SDL_DestroyTexture(self.spec_level_tex)

    def rescale(self):
        self.spec_y = int(round(5 * self.scale))
        self.spec_w = int(round(80 * self.scale))
        self.spec_h = int(round(20 * self.scale))
        self.spec1_rec = SDL_Rect(0, self.spec_y, self.spec_w, self.spec_h)

        self.spec4_y = int(round(200 * self.scale))
        self.spec4_w = int(round(322 * self.scale))
        self.spec4_h = int(round(100 * self.scale))
        self.spec4_rec = SDL_Rect(0, self.spec4_y, self.spec4_w, self.spec4_h)

        self.bar = SDL_Rect(10, 10, round(3 * self.scale), 10) # spec bar bin
        self.bar4 = SDL_Rect(10, 10, round(3 * self.scale), 10) # spec bar bin
        self.set_height = round(25 * self.scale)
        self.panelBY = round(51 * self.scale)
        self.panelY = round(30 * self.scale)
        self.panelY2 = round(30 * self.scale)
        self.playlist_top = self.panelY + (8 * self.scale)
        self.playlist_top_bk = self.playlist_top
        self.scroll_hide_box = (0, self.panelY, 28, window_size[1] - self.panelBY - self.panelY)

        self.spec2_y = int(round(22 * self.scale))
        self.spec2_w = int(round(140 * self.scale))
        self.spec2 = [0] * self.spec2_y
        self.spec2_phase = 0
        self.spec2_buffers = []
        self.spec2_rec = SDL_Rect(1230, round(4 * self.scale), self.spec2_w, self.spec2_y)
        self.spec2_source = SDL_Rect(900, round(4 * self.scale), self.spec2_w, self.spec2_y)
        self.spec2_dest = SDL_Rect(900, round(4 * self.scale), self.spec2_w, self.spec2_y)
        self.spec2_position = 0
        self.spec2_timer = Timer()
        self.spec2_timer.set()

        self.level_w = 5 * self.scale
        self.level_y = 16 * self.scale
        self.level_s = 1 * self.scale
        self.level_ww = round(79 * self.scale)
        self.level_hh = round(18 * self.scale)
        self.spec_level_rec = SDL_Rect(0, round(self.level_y - 10 * self.scale), round(self.level_ww), round(self.level_hh))

        self.spec2_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, self.spec2_w,
                                          self.spec2_y)
        self.spec4_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, self.spec4_w,
                                          self.spec4_y)
        self.spec1_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, self.spec_w,
                                          self.spec_h)
        self.spec_level_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET,
                                               self.level_ww, self.level_hh)
        SDL_SetTextureBlendMode(self.spec4_tex, SDL_BLENDMODE_BLEND)
        self.artist_panel_height = 320 * self.scale
        self.last_artist_panel_height = self.artist_panel_height

        self.window_control_hit_area_w = 100 * self.scale
        self.window_control_hit_area_h = 30 * self.scale


    def __init__(self):

        self.scale = prefs.ui_scale

        self.window_id = 0
        self.update = 2  # UPDATE
        self.turbo = True
        self.turbo_next = 0
        self.pl_update = 1
        self.lowered = False
        self.request_raise = False
        self.maximized = False

        self.message_box = False
        self.message_text = ""
        self.message_mode = 'info'
        self.message_subtext = ""
        self.message_subtext2 = ""
        self.message_box_confirm_reference = None
        self.message_box_use_reference = True
        self.message_box_confirm_callback = None


        self.save_size = [450, 310]
        self.show_playlist = True
        self.show_bottom_title = False
        # self.show_top_title = True
        self.search_error = False

        self.level_update = False
        self.level_time = Timer()
        self.level_peak = [0, 0]
        self.level = 0
        self.time_passed = 0
        self.level_meter_colour_mode = 3

        self.vis = 0  # visualiser mode actual
        self.vis_want = 0  # visualiser mode setting
        self.spec = None
        self.s_spec = [0] * 24
        self.s4_spec = [0] * 45
        self.update_spec = 0

        #self.spec_rect = [0, 5, 80, 20]  # x = 72 + 24 - 6 - 10


        self.spec4_array = []

        self.draw_spec4 = False


        self.combo_mode = False
        self.showcase_mode = False
        self.display_time_mode = 0

        self.pl_text_real_height = 12
        self.pl_title_real_height = 11

        self.row_extra = 0
        self.test = False
        self.light_mode = False

        self.level_2_click = False
        self.universal_y_text_offset = 0

        self.star_text_y_offset = 0
        if system == "windows":
            self.star_text_y_offset = -2

        self.set_bar = True
        self.set_mode = False
        self.set_hold = -1
        self.set_label_hold = -1
        self.set_label_point = (0, 0)
        self.set_point = 0
        self.set_old = 0
        self.pl_st = [['Artist', 156, False], ['Title', 188, False], ['T', 40, True], ['Album', 153, False], ['P', 28, True], ['Starline', 86, True], ['Date', 48, True], ['Codec', 55, True], ['Time', 53, True]]

        for item in self.pl_st:
            item[1] = item[1] * self.scale



        self.offset_extra = 0

        self.playlist_row_height = 16
        self.playlist_text_offset = 0
        self.row_font_size = 13
        self.compact_bar = False
        self.tracklist_texture_rect = tracklist_texture_rect
        self.tracklist_texture = tracklist_texture

        self.trunk_end = "..." # "…"
        self.temp_themes = {}
        self.theme_temp_current = -1

        self.pl_title_y_offset = 0
        self.pl_title_font_offset = -1

        self.playlist_box_d_click = -1

        self.gallery_show_text = True
        self.bb_show_art = False


        self.rename_folder_box = False

        self.present = False
        self.drag_source_position = (0, 0)
        self.drag_source_position_persist = (0, 0)
        self.album_tab_mode = False
        self.main_art_box = (0, 0, 10, 10)
        self.gall_tab_enter = False

        self.lightning_copy = False

        self.gallery_animate_highlight_on = 0

        self.seek_cur_show = False
        self.cur_time = "0"
        self.force_showcase_index = -1

        self.frame_callback_list = []

        self.playlist_left = None
        self.image_downloading = False
        self.tc_cancel = False
        self.im_cancel = False
        self.force_search = False

        self.pl_pulse = False

        self.view_name = "S"
        self.restart_album_mode = False

        self.dtm3_index = -1
        self.dtm3_cum = 0
        self.dtm3_total = 0
        self.previous_playlist_id = ""

        self.star_mode = "line"
        self.heart_fields = []
        self.show_ratings = False

        self.web_running = False

        self.rsp = True
        if phone:
            self.rsp = False
        self.rspw = round(300 * self.scale)
        self.lsp = False
        self.lspw = round(220 * self.scale)
        self.plw = None

        self.pref_rspw = 300

        self.pref_gallery_w = 600

        self.artist_info_panel = False

        self.show_hearts = True

        self.cursor_is = 0
        self.cursor_want = 0
        # 0 standard
        # 1 drag horizontal
        # 2 text
        # 3 hand

        self.power_bar = None
        self.gallery_scroll_field_left = 1
        self.combo_was_album = False

        self.gallery_positions = {}

        self.remember_library_mode = False

        self.first_in_grid = None

        self.art_aspect_ratio = 1
        self.art_drawn_rect = None
        self.art_unlock_ratio = False
        self.art_max_ratio_lock = 1
        self.side_bar_drag_source = 0
        self.side_bar_drag_original = 0

        self.scroll_direction = 0
        self.add_music_folder_ready = False

        self.playlist_current_visible_tracks = 0
        self.playlist_current_visible_tracks_id = 0

        self.theme_name = ""
        self.rename_playlist_box = False
        self.queue_frame_draw = None  # Set when need draw frame later

        self.mode = 1

        self.save_position = [0, 0]



        self.draw_vis4_top = False
        #self.vis_4_colour = [0,0,0,255]
        self.vis_4_colour = None

        self.layer_focus = 0
        self.tab_menu_pl = 0

        self.tool_tip_lock_off_f = False
        self.tool_tip_lock_off_b = False

        self.auto_play_import = False

        self.transcoding_batch_total = 0
        self.transcoding_bach_done = 0

        self.seek_bar_rect = (0,0,0,0)
        self.volume_bar_rect = (0,0,0,0)

        self.mini_mode_return_maximized = False

        self.opened_config_file = False

        self.notify_main_id = None

        self.halt_image_rendering = False
        self.generating_chart = False

        self.top_bar_mode2 = False
        self.mode_toast_text = ""

        self.rescale()
        #self.smooth_scrolling = False

        self.compact_artist_list = False

        self.rsp_full_lock = False

        self.album_scroll_px = album_v_slide_value
        self.queue_toast_plural = False
        self.reload_theme = False
        self.theme_number = 0
        self.toast_queue_object = None
        self.toast_love_object = None
        self.toast_love_added = True

        self.force_side_on_drag = False
        self.last_left_panel_mode = "playlist"
        self.showing_l_panel = False

        self.downloading_bass = False
        self.d_click_ref = -1

        self.max_window_tex = max_window_tex
        self.main_texture = main_texture
        self.main_texture_overlay_temp = main_texture_overlay_temp

        self.preview_artist = ""
        self.preview_artist_location = (0, 0)
        self.preview_artist_loading = ""
        self.mouse_left_window = False

        self.rendered_playlist_position = 0
        
        self.console = console
        self.show_album_ratings = False
        self.gen_code_errors = False

        self.regen_single = -1
        self.regen_single_id = None

        self.tracklist_bg_is_light = False
        self.clear_image_cache_next = 0

        self.column_d_click_timer = Timer(10)
        self.column_d_click_on = -1
        self.column_sort_ani_timer = Timer(10)
        self.column_sort_down_icon = asset_loader("sort-down.png", True)
        self.column_sort_up_icon = asset_loader("sort-up.png", True)
        self.column_sort_ani_direction = 1
        self.column_sort_ani_x = 0

        self.restore_showcase_view = False
        self.restore_radio_view = False

        self.tracklist_center_mode = False
        self.tracklist_inset_left = 0
        self.tracklist_inset_width = 0
        self.tracklist_highlight_width = 0
        self.tracklist_highlight_left = 0

        self.hide_tracklist_in_gallery = False

        self.saved_prime_tab = 0
        self.saved_prime_direction = 0

        self.stop_sync = False
        self.sync_progress = ""
        self.sync_speed = ""

        self.bar_hover_timer = Timer()

        self.level_decay_timer = Timer()

        self.showed_title = False

        self.to_get = 0
        self.to_got = 0
        self.switch_showcase_off = False

        self.backend_reloading = False

        self.spot_info_icon = asset_loader("spot-info.png", True)
        self.tray_active = False
        self.buffering = 0  # 0:false 1:true

        self.update_on_drag = False
        self.pl_update_on_drag = False
        self.drop_playlist_target = 0
        self.discord_status = "Standby"
        self.mouse_unknown = False
        self.macstyle = prefs.macstyle
        if macos or detect_macstyle:
            self.macstyle = True
        self.radio_view = False
        self.window_size = window_size
        self.box_over = False
        self.suggest_clean_db = False
        self.style_worker_timer = Timer()


gui = GuiVar()

def toast(text):
    gui.mode_toast_text = text
    toast_mode_timer.set()
    gui.frame_callback_list.append(TestTimer(1.5))

def set_artist_preview(path, artist, x, y):
    m = min(round(500 * gui.scale), window_size[1] - (gui.panelY + gui.panelBY + 50 * gui.scale))
    artist_preview_render.load(path, box_size=m)
    artist_preview_render.show = True
    ah = artist_preview_render.size[1]
    ay = round(y) - (ah // 2)
    if ay < gui.panelY + 20 * gui.scale:
        ay = gui.panelY + round(20 * gui.scale)
    if ay + ah > window_size[1] - (gui.panelBY + 5 * gui.scale):
        ay = window_size[1] - (gui.panelBY + ah + round(5 * gui.scale))
    gui.preview_artist = artist
    gui.preview_artist_location = (x + 15 * gui.scale,
                                   ay)


def get_artist_preview(artist, x, y):

    #show_message(_("Loading artist image..."))


    gui.preview_artist_loading = artist
    artist_info_box.get_data(artist, force_dl=True)
    path = artist_info_box.get_data(artist, get_img_path=True)
    if not path:
        show_message(_("No artist image found."))
        if not prefs.enable_fanart_artist and not verify_discogs():
            show_message(_("No artist image found."), _("No providers are enabled in settings!"), mode='warning')
        gui.preview_artist_loading = ""
        return
    set_artist_preview(path, artist, x, y)
    gui.message_box = False
    gui.preview_artist_loading = ""


def set_drag_source():
    gui.drag_source_position = tuple(click_location)
    gui.drag_source_position_persist = tuple(click_location)



# Functions for reading and setting play counts
class StarStore:

    def __init__(self):

        self.db = {}

    def key(self, track_id):

        track_object = pctl.master_library[track_id]
        return track_object.artist, track_object.title, track_object.filename

    def object_key(self, track):

        return track.artist, track.title, track.filename

    # Increments the play time
    def add(self, index, value):

        track_object = pctl.master_library[index]

        if after_scan:
            if track_object in after_scan:
                return

        key = track_object.artist, track_object.title, track_object.filename

        if key in self.db:
            self.db[key][0] += value
            if value < 0 and self.db[key][0] < 0:
                self.db[key][0] = 0
        else:
            self.db[key] = [value, "", 0]

    # Returns the track play time
    def get(self, index):
        if index < 0:
            return 0
        return self.db.get(self.key(index), (0,))[0]

    # Returns the track user rating
    def get_rating(self, index):
        key = self.key(index)
        if key in self.db:
            # self.db[key]
            return self.db[key][2]
        return 0

    # Sets the track user rating
    def set_rating(self, index, value, write=False):
        key = self.key(index)
        if key not in self.db:
            self.db[key] = self.new_object()
        self.db[key][2] = value

        tr = pctl.g(index)
        if tr.file_ext == "SUB":
            self.db[key][2] = math.ceil(value / 2) * 2
            shooter(subsonic.set_rating, (tr, value))

        if prefs.write_ratings and write:
            print("Writing rating..")
            assert value <= 10
            assert value >= 0
            if tr.file_ext == "MP3":
                tag = mutagen.id3.ID3(tr.fullpath)
                if value == 0:
                    changed = False
                    frames = tag.getall("TXXX")
                    for i in reversed(range(len(frames))):
                        if frames[i].desc.lower() == "fmps_rating":
                            changed = True
                    if changed:
                        tag.delall("TXXX:FMPS_RATING")
                        tag.save()
                else:
                    changed = False
                    frames = tag.getall("TXXX")
                    for i in reversed(range(len(frames))):
                        if frames[i].desc.lower() == "fmps_rating":
                            frames[i].text = '{:.2f}'.format(value / 10)
                            changed = True
                    if not changed:
                        tag.add(mutagen.id3.TXXX(encoding=mutagen.id3.Encoding.UTF8, text='{:.2f}'.format(value / 10), desc="FMPS_RATING"))
                    tag.save()

            elif tr.file_ext == "FLAC":
                audio = mutagen.flac.FLAC(tr.fullpath)
                tags = audio.tags
                if value == 0:
                    if "FMPS_Rating" in tags:
                        del tags["FMPS_Rating"]
                        audio.save()
                else:
                    tags["FMPS_Rating"] = '{:.2f}'.format(value / 10)
                    audio.save()

            tr.misc["FMPS_Rating"] = float(value / 10)
            if value == 0:
                del tr.misc["FMPS_Rating"]


    def new_object(self):
        return [0, "", 0]

    def get_by_object(self, track):

        return self.db.get(self.object_key(track), (0,))[0]

    def get_total(self):

        return sum(item[0] for item in self.db.values())

    def full_get(self, index):
        return self.db.get(self.key(index))

    def remove(self, index):
        key = self.key(index)
        if key in self.db:
            del self.db[key]

    def insert(self, index, object):
        key = self.key(index)
        self.db[key] = object

    def merge(self, index, object):
        if object is None or object == [0, "", 0]:
            return
        key = self.key(index)
        if key not in self.db:
            self.db[key] = object
        else:
            self.db[key][0] += object[0]
            self.db[key][2] = object[2]
            for cha in object[1]:
                if cha not in self.db[key][1]:
                    self.db[key][1] += cha


star_store = StarStore()

class AlbumStarStore:

    def __init__(self):
        self.db = {}

    def get_key(self, track_object):
        artist = track_object.album_artist
        if not artist:
            artist = track_object.artist
        return artist + ":" + track_object.album

    def get_rating(self, track_object):
        return self.db.get(self.get_key(track_object), 0)

    def set_rating(self, track_object, rating):
        self.db[self.get_key(track_object)] = rating
        if track_object.file_ext == "SUB":
            self.db[self.get_key(track_object)] = math.ceil(rating / 2) * 2
            subsonic.set_album_rating(track_object, rating)

    def set_rating_artist_title(self, artist, album, rating):
        self.db[artist + ":" + album] = rating

    def get_rating_artist_title(self, artist, album):
        return self.db.get(artist + ":" + album, 0)

album_star_store = AlbumStarStore()


class Fonts:    # Used to hold font sizes (I forget to use this)

    def __init__(self):

        self.tabs = 211
        self.panel_title = 213

        self.side_panel_line1 = 214
        self.side_panel_line2 = 13

        self.bottom_panel_time = 212

        # if system == 'windows':
        #     self.bottom_panel_time = 12  # The Arial bold font is too big so just leaving this as normal. (lazy)

fonts = Fonts()


class Input:    # Used to keep track of button states (or should be)

    def __init__(self):

        self.mouse_click = False
        # self.right_click = False
        self.level_2_enter = False
        self.key_return_press = False
        self.key_tab_press = False
        self.backspace_press = 0
        

        self.media_key = ""

    def m_key_play(self):
        self.media_key = "Play"
        gui.update += 1
    def m_key_pause(self):
        self.media_key = "Pause"
        gui.update += 1
    def m_key_stop(self):
        self.media_key = "Stop"
        gui.update += 1
    def m_key_next(self):
        self.media_key = "Next"
        gui.update += 1
    def m_key_previous(self):
        self.media_key = "Previous"
        gui.update += 1

inp = Input()


class KeyMap:

    def __init__(self):

        self.hits = [] # The keys hit this frame
        self.maps = {} # Loaded from input.txt

    def load(self):

        path = os.path.join(config_directory, "input.txt")
        with open(path, encoding="utf_8") as f:
            content = f.read().splitlines()
            for p in content:
                if len(p) == 0 or len(p) > 100:
                    continue
                if p[0] == " " or p[0] == "#":
                    continue

                items = p.split()
                if 1 < len(items) < 5:
                    function = items[0]

                    if items[1] in ("MB4", "MB5"):
                        key = items[1]
                    else:
                        key = SDL_GetKeyFromName(items[1].encode())
                        if key == 0:
                            continue

                    mod = []

                    if len(items) > 2:
                        mod.append(items[2].lower())
                    if len(items) > 3:
                        mod.append(items[3].lower())

                    if function in self.maps:
                        self.maps[function].append((key, mod))
                    else:
                        self.maps[function] = [(key, mod)]

    def test(self, function):

        if not self.hits:
            return False
        if function not in self.maps:
            return False

        for code, mod in self.maps[function]:

            if code in self.hits:

                ctrl = (key_ctrl_down or key_rctrl_down) * 1
                shift = (key_shift_down or key_shiftr_down) * 10
                alt = (key_lalt or key_ralt) * 100

                if ctrl + shift + alt == ("ctrl" in mod) * 1 + ("shift" in mod) * 10 + ("alt" in mod) * 100:
                    return True

        return False


keymaps = KeyMap()


def update_set():   # This is used to scale columns when windows is resized or items added/removed

    wid = gui.plw - round(16 * gui.scale)
    if gui.tracklist_center_mode:
        wid = gui.tracklist_highlight_width - round(16 * gui.scale)

    total = 0
    for item in gui.pl_st:
        if item[2] is False:
            total += item[1]
        else:
            wid -= item[1]

    if wid <= 75:
        wid = 75

    for i in range(len(gui.pl_st)):
        if gui.pl_st[i][2] is False and total:
            gui.pl_st[i][1] = int(round((gui.pl_st[i][1] / total) * wid)) #+ 1

def auto_size_columns():

    fixed_n = 0

    wid = gui.plw - round(16 * gui.scale)
    if gui.tracklist_center_mode:
        wid = gui.tracklist_highlight_width - round(16 * gui.scale)

    total = wid
    for item in gui.pl_st:

        if item[2]:
            fixed_n += 1

        if item[0] == "Lyrics":
            item[1] = round(50 * gui.scale)
            total -= round(50 * gui.scale)

        if item[0] == "Rating":
            item[1] = round(80 * gui.scale)
            total -= round(80 * gui.scale)

        if item[0] == "Starline":
            item[1] = round(78 * gui.scale)
            total -= round(78 * gui.scale)

        if item[0] == "Time":
            item[1] = round(58 * gui.scale)
            total -= round(58 * gui.scale)

        if item[0] == "Codec":
            item[1] = round(58 * gui.scale)
            total -= round(58 * gui.scale)

        if item[0] == "P" or item[0] == "S" or item[0] == "#":
            item[1] = round(32 * gui.scale)
            total -= round(32 * gui.scale)

        if item[0] == "Date":
            item[1] = round(55 * gui.scale)
            total -= round(55 * gui.scale)

        if item[0] == "Bitrate":
            item[1] = round(67 * gui.scale)
            total -= round(67 * gui.scale)

        if item[0] == "❤":
            item[1] = round(27 * gui.scale)
            total -= round(27 * gui.scale)

    vr = len(gui.pl_st) - fixed_n

    if vr > 0 and total > 50:

        space = round(total / vr)

        for item in gui.pl_st:
            if not item[2]:
                item[1] = space

    gui.pl_update += 1
    update_set()


class ColoursClass:     # Used to store colour values for UI elements. These are changed for themes.
    def grey(self, value):
        return [value, value, value, 255]

    def alpha_grey(self, value):
        return [255, 255, 255, value]

    def grey_blend_bg(self, value):
        return alpha_blend((255, 255, 255, value), self.box_background)

    def __init__(self):

        self.column_colours = {}
        self.column_colours_playing = {}

        self.last_album = ""
        self.link_text = [100, 200, 252, 255]

        self.tb_line = self.grey(21)  # not currently used
        self.art_box = self.grey(24)

        self.volume_bar_background = self.grey(30)
        self.volume_bar_fill = self.grey(125)
        self.seek_bar_background = self.grey(30)
        self.seek_bar_fill = self.grey(80)

        self.tab_text_active = self.grey(230)
        self.tab_text = self.grey(215)
        self.tab_background = self.grey(25)
        self.tab_highlight = self.grey(40)
        self.tab_background_active = self.grey(45)

        self.title_text = [190, 190, 190, 255]
        self.index_text = self.grey(70)
        self.time_text = self.grey(180)
        self.artist_text = [195, 255, 104, 255]
        self.album_text = [245, 240, 90, 255]

        self.index_playing = self.grey(190)
        self.artist_playing = [195, 255, 104, 255]
        self.album_playing = [245, 240, 90, 255]
        self.title_playing = self.grey(230)

        self.time_playing = [180, 194, 107, 255]

        self.playlist_text_missing = self.grey(85)
        self.bar_time = self.grey(70)

        self.top_panel_background = self.grey(15)
        self.side_panel_background = self.grey(18)
        self.gallery_background = self.side_panel_background
        self.playlist_panel_background = self.grey(21)
        self.bottom_panel_colour = self.grey(15)

        self.row_playing_highlight = [255,255,255,4]
        self.row_select_highlight = [255,255,255,5]

        self.side_bar_line1 = self.grey(230)
        self.side_bar_line2 = self.grey(210)

        self.mode_button_off = self.grey(50)
        self.mode_button_over = self.grey(200)
        self.mode_button_active = self.grey(190)

        self.media_buttons_over = self.grey(220)
        self.media_buttons_active = self.grey(220)
        self.media_buttons_off = self.grey(55)

        self.star_line = [100, 100, 100, 255]
        self.star_line_playing = None
        self.folder_title = [130, 130, 130, 255]
        self.folder_line = [40, 40, 40, 255]

        self.scroll_colour = [45, 45, 45, 255]

        self.level_1_bg = [0, 30, 0, 255]
        self.level_2_bg = [30, 30, 0, 255]
        self.level_3_bg = [30, 0, 0, 255]
        self.level_green = [20, 120, 20, 255]
        self.level_red = [190, 30, 30, 255]
        self.level_yellow = [135, 135, 30, 255]

        self.vis_colour = self.grey(200)
        self.vis_bg = [0, 0, 0, 255]

        self.menu_background = None #self.grey(12)
        self.menu_highlight_background = None
        self.menu_text = [230, 230, 230, 255]
        self.menu_text_disabled = self.grey(50)
        self.menu_icons = [255, 255, 255, 25]
        self.menu_tab = self.grey(30)

        self.gallery_highlight = self.artist_playing

        self.status_info_text = [245, 205, 0, 255]
        self.streaming_text = [220, 75, 60, 255]
        self.lyrics = self.grey(245)

        self.corner_button = [255, 255, 255, 50] #[60, 60, 60, 255]
        self.corner_button_active = [255, 255, 255, 230]#[230, 230, 230, 255]

        self.window_buttons_bg = [0, 0, 0, 50]
        self.window_buttons_bg_over = [255, 255, 255, 10] #[80, 80, 80, 120]
        self.window_buttons_icon_over = (255, 255, 255, 60)
        self.window_button_icon_off = (255, 255, 255, 40)
        self.window_button_x_on = None
        self.window_button_x_off = self.window_button_icon_off

        self.message_box_bg = self.grey(0)
        self.message_box_text = self.grey(230)

        self.sys_title = self.grey(220)
        self.sys_title_strong = self.grey(230)
        self.lm = False

        self.pluse_colour = [244, 212, 66, 255]

        self.mini_mode_background = [19, 19, 19, 255]
        self.mini_mode_border = [45, 45, 45, 255]
        self.mini_mode_text_1 = [255, 255, 255, 240]
        self.mini_mode_text_2 = [255, 255, 255, 77]

        self.queue_drag_indicator_colour = [200, 50, 240, 255]

        self.playlist_box_background = self.side_panel_background

        self.bar_title_text = None

        self.corner_icon = [40, 40, 40, 255]
        self.queue_background = None #self.side_panel_background #self.grey(18) # 18
        self.queue_card_background = self.grey(23)

        self.column_bar_background = [30, 30, 30, 255]
        self.column_grip = [255, 255, 255, 14]
        self.column_bar_text = [240, 240, 240, 255]

        self.window_frame = [30, 30, 30, 255]

        self.box_background = [16, 16, 16, 255]
        self.box_border = rgb_add_hls(self.box_background, 0, 0.17, 0)
        self.box_text_border = rgb_add_hls(self.box_background, 0, 0.1, 0)
        self.box_text_label = rgb_add_hls(self.box_background, 0, 0.32, -0.1)
        self.box_sub_highlight = rgb_add_hls(self.box_background, 0, 0.07, -0.05)  # 58, 47, 85
        self.box_check_border = [255, 255, 255, 18]

        self.box_title_text = self.grey(245)
        self.box_text = self.grey(240)
        self.box_sub_text = self.grey_blend_bg(225)
        self.box_input_text = self.grey(225)
        self.box_button_text_highlight = self.grey(250)
        self.box_button_text = self.grey(225)
        self.box_button_background = alpha_blend([255, 255, 255, 11], self.box_background)
        self.box_thumb_background = None
        self.box_button_background_highlight = alpha_blend([255, 255, 255, 20], self.box_background)

        self.artist_bio_background = [27, 27, 27, 255]
        self.artist_bio_text = [230, 230, 230, 255]

    def post_config(self):

        if self.box_thumb_background is None:
            self.box_thumb_background = alpha_mod(self.box_button_background, 175)

        # Pre calculate alpha blend for spec background
        self.vis_bg[0] = int(0.05 * 255 + (1 - 0.05) * self.top_panel_background[0])
        self.vis_bg[1] = int(0.05 * 255 + (1 - 0.05) * self.top_panel_background[1])
        self.vis_bg[2] = int(0.05 * 255 + (1 - 0.05) * self.top_panel_background[2])

        self.message_box_bg = self.box_background
        self.sys_tab_bg = self.tab_background
        self.sys_tab_hl = self.tab_background_active
        self.toggle_box_on = self.folder_title
        self.toggle_box_on = [255, 150, 100, 255]
        self.toggle_box_on = self.artist_playing
        if colour_value(self.toggle_box_on) < 150:
            self.toggle_box_on = [160, 160, 160, 255]
        #self.time_sub = [255, 255, 255, 80]#alpha_blend([255, 255, 255, 80], self.bottom_panel_colour)

        self.time_sub = rgb_add_hls(self.bottom_panel_colour, 0, 0.29, 0)

        if test_lumi(colours.bottom_panel_colour) < 0.2:
            #self.time_sub = [0, 0, 0, 80]
            self.time_sub = rgb_add_hls(self.bottom_panel_colour, 0, -0.15, -0.3)
        elif test_lumi(colours.bottom_panel_colour) < 0.8:
            self.time_sub = [255, 255, 255, 135]
        #self.time_sub = self.mode_button_off

        if self.bar_title_text is None:
            self.bar_title_text = self.side_bar_line1

        self.gallery_artist_line = alpha_mod(self.side_bar_line2, 120)

        #self.status_text_normal = alpha_blend([255, 255, 255, 80], self.top_panel_background) #self.grey(100)
        self.status_text_normal = rgb_add_hls(self.top_panel_background, 0, 0.30, -0.15)


        # self.status_text_over = alpha_blend([255, 255, 255, 220], self.top_panel_background)#self.grey(220)

        self.status_text_over = rgb_add_hls(self.top_panel_background, 0, 0.83, 0)

        # self.corner_button = rgb_add_hls(self.top_panel_background, 0, 0.20, -0.15)
        # self.corner_button_active = colours.status_text_over

        if self.menu_highlight_background is None:
            self.menu_highlight_background = [40, 40, 40, 255]

        if test_lumi(colours.bottom_panel_colour) < 0.2:
            self.corner_icon = [0, 0, 0, 60]
        elif test_lumi(colours.bottom_panel_colour) < 0.8:
            self.corner_icon = [40, 40, 40, 255]
        else:
            self.corner_icon = [255, 255, 255, 30]

        if test_lumi(self.bottom_panel_colour) < 0.2:
            self.corner_icon = [0, 0, 0, 60]

        if not self.queue_background:
            self.queue_background = self.side_panel_background

        if test_lumi(self.queue_background) > 0.8:
            self.queue_card_background = alpha_blend([255, 255, 255, 10], self.queue_background)

        if self.menu_background is None and not self.lm:
            self.menu_background = self.bottom_panel_colour

        self.message_box_text = self.box_text
        self.message_box_border = self.box_border

        if self.window_button_x_on is None:
            self.window_button_x_on = self.artist_playing

        if test_lumi(self.column_bar_background) < 0.4:
            self.column_bar_text = [40, 40, 40, 200]
            self.column_grip = [255, 255, 255, 20]

        if not self.lm:
            self.corner_button = rgb_add_hls(self.top_panel_background, 0, 0.18, 0)


    def light_mode(self):

        self.lm = True
        self.star_line_playing = [255, 255, 255, 255]
        self.sys_tab_bg = self.grey(25)
        self.sys_tab_hl = self.grey(45)
        # self.box_background = self.grey(30)
        self.toggle_box_on = self.tab_background_active
        #if colour_value(self.tab_background_active) < 250:
        #    self.toggle_box_on = [255, 255, 255, 200]

        # self.time_sub = [0, 0, 0, 200]
        self.gallery_artist_line = self.grey(40)
        # self.bar_title_text = self.grey(30)
        self.status_text_normal = self.grey(70)
        self.status_text_over = self.grey(40)
        self.status_info_text = [40, 40, 40, 255]

        # self.bar_title_text = self.grey(255)
        self.vis_bg = [235, 235, 235, 255]
        #self.menu_background = [240, 240, 240, 250]
        #self.menu_text = self.grey(40)
        #self.menu_text_disabled = self.grey(180)
        #self.menu_highlight_background = [200, 200, 200, 250]
        if self.menu_background is None:
            self.menu_background = [15, 15, 15, 250]
        if not self.menu_icons:
            self.menu_icons = [0, 0, 0, 40]

        # self.menu_background = [40, 40, 40, 250]
        # self.menu_text = self.grey(220)
        # self.menu_text_disabled = self.grey(120)
        # self.menu_highlight_background = [120, 80, 220, 250]

        self.corner_button = self.grey(160)
        self.corner_button_active = self.grey(35)
        #self.window_buttons_bg = [0, 0, 0, 5]
        self.message_box_bg = [245, 245, 245, 255]
        self.message_box_text = self.grey(20)
        self.message_box_border = self.grey(40)
        self.gallery_background = self.grey(230)
        self.gallery_artist_line = self.grey(40)
        self.pluse_colour = [212, 66, 244, 255]

        #view_box.off_colour = self.grey(200)


colours = ColoursClass()
colours.post_config()


def set_colour(colour):
    SDL_SetRenderDrawColor(renderer, colour[0], colour[1], colour[2], colour[3])


def get_themes():

    themes = []  # full, name
    direcs = [install_directory + '/theme']
    if user_directory != install_directory:
        direcs.append(user_directory + '/theme')

    for direc in direcs:
        if os.path.exists(direc):
            for path in [os.path.join(direc, f) for f in os.listdir(direc)]:
                if path[-6:] == 'ttheme':
                    themes.append((path, os.path.basename(path).split(".")[0]))

    themes.sort()
    return themes


# This is legacy. New settings are added straight to the save list (need to overhaul)
view_prefs = {

    'split-line': True,
    'update-title': False,
    'star-lines': False,
    'side-panel': True,
    'dim-art': False,
    'pl-follow': False,
    'scroll-enable': True
}


class TrackClass:   # This is the fundamental object/data structure of a track
    # C-TC
    def __init__(self):
        self.index = 0
        self.fullpath = ""
        self.filename = ""
        self.parent_folder_path = ""
        self.parent_folder_name = ""
        self.file_ext = ""
        self.size = 0
        self.modified_time = 0

        self.is_network = False
        #self.url = ""
        self.url_key = ""
        self.art_url_key = ""

        self.artist = ""
        self.album_artist = ""
        self.title = ""
        self.composer = ""
        self.length = 0
        self.bitrate = 0
        self.samplerate = 0
        self.bit_depth = 0
        self.album = ""
        self.date = ""
        self.track_number = ""
        self.track_total = ""
        self.start_time = 0
        self.is_cue = False
        self.is_embed_cue = False
        self.cue_sheet = ""
        self.genre = ""
        self.found = True
        self.skips = 0
        self.comment = ""
        self.disc_number = ""
        self.disc_total = ""
        self.lyrics = ""

        self.lfm_friend_likes = set()
        self.lfm_scrobbles = 0
        self.misc = {}


class LoadClass:    # Object for import track jobs (passed to worker thread)
    def __init__(self):
        self.target = ""
        self.playlist = 0  # Playlist UID
        self.tracks = []
        self.stage = 0
        self.playlist_position = None
        self.replace_stem = False
        self.notify = False
        self.play = False
        self.force_scan = False


# url_saves = []
rename_files_previous = ""
rename_folder_previous = ""
p_force_queue = []

reload_state = None


def show_message(line1, line2="", line3="", mode='info'):
    gui.message_box = True
    gui.message_text = line1
    gui.message_mode = mode
    gui.message_subtext = line2
    gui.message_subtext2 = line3
    message_box_min_timer.set()
    gui.update = 1

# -----------------------------------------------------
# STATE LOADING
# Loading of program data from previous run
import gc as gbc
gbc.disable()
ggc = 2

try:

    sp1 = user_directory + "/star.p"
    sp2 = user_directory + "/star.p.backup"

    s1 = 0
    s2 = 0

    if os.path.isfile(sp1):
        s1 = os.path.getsize(sp1)
    if os.path.isfile(sp2):
        s2 = os.path.getsize(sp2)
    to_load = sp1
    if s2 > s1:
        print("Loading backup star.p")
        to_load = sp2

    star_store.db = pickle.load(open(to_load, "rb"))

except:
    print('No existing star.p file')

try:
    album_star_store.db = pickle.load(open(user_directory + "/album-star.p", "rb"))

except:
    print('No existing album-star.p file')


try:
    if os.path.isfile(user_directory + "/lyrics_substitutions.json"):
        with open(user_directory + "/lyrics_substitutions.json", 'r') as f:
            prefs.lyrics_subs = json.load(f)
except:
    print("Error loading lyrics_substitutions.json")

perf_timer.set()

radio_playlists = [{"uid": uid_gen(), "name": "Default", "items": []}]
radio_playlist_viewing = 0

for t in range(2):
    try:

        # if os.path.isfile(user_directory + "/state.p.backup") and (
        #
        #     not os.path.isfile(user_directory + "/state.p") or
        #     os.path.getsize(user_directory + "/state.p") < 100
        # )
        if t == 0:
            state_file = open(user_directory + "/state.p", "rb")
        if t == 1:
            state_file = open(user_directory + "/state.p.backup", "rb")

        # def tt():
        #     while True:
        #         print(state_file.tell())
        #         time.sleep(0.01)
        # shooter(tt)

        save = pickle.load(state_file)

        if t == 1:
            print("Using backup state")

        if save[63] is not None:
            prefs.ui_scale = save[63]
            #prefs.ui_scale = 1.3
            #gui.__init__()

        if save[0] is not None:
            master_library = save[0]
        master_count = save[1]
        playlist_playing = save[2]
        playlist_active = save[3]
        playlist_view_position = save[4]
        multi_playlist = save[5]
        volume = save[6]
        QUE = save[7]
        playing_in_queue = save[8]
        default_playlist = save[9]
        playlist_playing = save[10]
        # cue_list = save[11]
        # radio_field_text = save[12]
        theme = save[13]
        folder_image_offsets = save[14]
        #lfm_username = save[15]
        #lfm_hash = save[16]
        db_version = save[17]
        view_prefs = save[18]
        # window_size = save[19]
        gui.save_size = copy.copy(save[19])
        gui.rspw = save[20]
        # savetime = save[21]
        gui.vis_want = save[22]
        playlist_selected = save[23]
        if save[24] is not None:
            album_mode_art_size = save[24]
        if save[25] is not None:
            draw_border = save[25]
        if save[26] is not None:
            prefs.enable_web = save[26]
        if save[27] is not None:
            prefs.allow_remote = save[27]
        if save[28] is not None:
            prefs.expose_web = save[28]
        if save[29] is not None:
            prefs.enable_transcode = save[29]
        if save[30] is not None:
            prefs.show_rym = save[30]
        # if save[31] is not None:
        #     combo_mode_art_size = save[31]
        if save[32] is not None:
            gui.maximized = save[32]
        if save[33] is not None:
            prefs.prefer_bottom_title = save[33]
        if save[34] is not None:
            gui.display_time_mode = save[34]
        # if save[35] is not None:
        #     prefs.transcode_mode = save[35]
        if save[36] is not None:
            prefs.transcode_codec = save[36]
        if save[37] is not None:
            prefs.transcode_bitrate = save[37]
        # if save[38] is not None:
        #     prefs.line_style = save[38]
        # if save[39] is not None:
        #     prefs.cache_gallery = save[39]
        if save[40] is not None:
            prefs.playlist_font_size = save[40]
        if save[41] is not None:
            prefs.use_title = save[41]
        if save[42] is not None:
            gui.pl_st = save[42]
        # if save[43] is not None:
        #     gui.set_mode = save[43]
        #     gui.set_bar = gui.set_mode
        if save[45] is not None:
            prefs.playlist_row_height = save[45]
        if save[46] is not None:
            prefs.show_wiki = save[46]
        if save[47] is not None:
            prefs.auto_extract = save[47]
        if save[48] is not None:
            prefs.colour_from_image = save[48]
        if save[49] is not None:
            gui.set_bar = save[49]
        if save[50] is not None:
            gui.gallery_show_text = save[50]
        if save[51] is not None:
            gui.bb_show_art = save[51]
        # if save[52] is not None:
        #     gui.show_stars = save[52]
        if save[53] is not None:
            prefs.auto_lfm = save[53]
        if save[54] is not None:
            prefs.scrobble_mark = save[54]
        if save[55] is not None:
            prefs.replay_gain = save[55]
        # if save[56] is not None:
        #     prefs.radio_page_lyrics = save[56]
        if save[57] is not None:
            prefs.show_gimage = save[57]
        if save[58] is not None:
            prefs.end_setting = save[58]
        if save[59] is not None:
            prefs.show_gen = save[59]
        # if save[60] is not None:
        #     url_saves = save[60]
        if save[61] is not None:
            prefs.auto_del_zip = save[61]
        if save[62] is not None:
            gui.level_meter_colour_mode = save[62]
        if save[64] is not None:
            prefs.show_lyrics_side = save[64]
        if save[65] is not None:
            prefs.last_device = save[65]
        if save[66] is not None:
            gui.restart_album_mode = save[66]
        if save[67] is not None:
            album_playlist_width = save[67]
        if save[68] is not None:
            prefs.transcode_opus_as = save[68]
        if save[69] is not None:
            gui.star_mode = save[69]
        if save[70] is not None:
            gui.rsp = save[70]
        if save[71] is not None:
            gui.lsp = save[71]
        if save[72] is not None:
            gui.rspw = save[72]
        if save[73] is not None:
            gui.pref_gallery_w = save[73]
        if save[74] is not None:
            gui.pref_rspw = save[74]
        if save[75] is not None:
            gui.show_hearts = save[75]
        if save[76] is not None:
            prefs.monitor_downloads = save[76]
        if save[77] is not None:
            gui.artist_info_panel = save[77]
        if save[78] is not None:
            prefs.extract_to_music = save[78]
        if save[79] is not None:
            prefs.enable_lb = save[79]
        # if save[80] is not None:
        #     prefs.lb_token = save[80]
        #     if prefs.lb_token is None:
        #         prefs.lb_token = ""
        if save[81] is not None:
            rename_files_previous = save[81]
        if save[82] is not None:
            rename_folder_previous = save[82]
        if save[83] is not None:
            prefs.use_jump_crossfade = save[83]
        if save[84] is not None:
            prefs.use_transition_crossfade = save[84]
        if save[85] is not None:
            prefs.show_notifications = save[85]
        # if save[86] is not None:
        #     prefs.true_shuffle = save[86]
        if save[87] is not None:
            gui.remember_library_mode = save[87]
        # if save[88] is not None:
        #     prefs.show_queue = save[88]
        # if save[89] is not None:
        #     prefs.show_transfer = save[89]
        if save[90] is not None:
            p_force_queue = save[90]
        if save[91] is not None:
            prefs.use_pause_fade = save[91]
        if save[92] is not None:
            prefs.append_total_time = save[92]
        if save[93] is not None:
            prefs.backend = save[93]  # moved to config file
        if save[94] is not None:
            prefs.album_shuffle_mode = save[94]
        if save[95] is not None:
            prefs.album_repeat_mode = save[95]
        # if save[96] is not None:
        #     prefs.finish_current = save[96]
        if save[97] is not None:
            reload_state = save[97]
        # if save[98] is not None:
        #     prefs.reload_play_state = save[98]
        if save[99] is not None:
            prefs.last_fm_token = save[99]
        if save[100] is not None:
            prefs.last_fm_username = save[100]
        # if save[101] is not None:
        #     prefs.use_card_style = save[101]
        # if save[102] is not None:
        #     prefs.auto_lyrics = save[102]
        if save[103] is not None:
            prefs.auto_lyrics_checked = save[103]
        if save[104] is not None:
            prefs.show_side_art = save[104]
        if save[105] is not None:
            prefs.window_opacity = save[105]
        if save[106] is not None:
            prefs.gallery_single_click = save[106]
        if save[107] is not None:
            prefs.tabs_on_top = save[107]
        if save[108] is not None:
            prefs.showcase_vis = save[108]
        if save[109] is not None:
            prefs.spec2_colour_mode = save[109]
        # if save[110] is not None:
        #     prefs.device_buffer = save[110]
        if save[111] is not None:
            prefs.use_eq = save[111]
        if save[112] is not None:
            prefs.eq = save[112]
        if save[113] is not None:
            prefs.bio_large = save[113]
        if save[114] is not None:
            prefs.discord_show = save[114]
        if save[115] is not None:
            prefs.min_to_tray = save[115]
        if save[116] is not None:
            prefs.guitar_chords = save[116]
        if save[117] is not None:
            prefs.playback_follow_cursor = save[117]
        if save[118] is not None:
            prefs.art_bg = save[118]
        if save[119] is not None:
            prefs.random_mode = save[119]
        if save[120] is not None:
            prefs.repeat_mode = save[120]
        if save[121] is not None:
            prefs.art_bg_stronger = save[121]
        if save[122] is not None:
            prefs.art_bg_always_blur = save[122]
        if save[123] is not None:
            prefs.failed_artists = save[123]
        if save[124] is not None:
            prefs.artist_list = save[124]
        if save[125] is not None:
            prefs.auto_sort = save[125]
        if save[126] is not None:
            prefs.lyrics_enables = save[126]
        if save[127] is not None:
            prefs.fanart_notify = save[127]
        if save[128] is not None:
            prefs.bg_showcase_only = save[128]
        if save[129] is not None:
            prefs.discogs_pat = save[129]
        if save[130] is not None:
            prefs.mini_mode_mode = save[130]
        if save[131] is not None:
            after_scan = save[131]
        if save[132] is not None:
            gui.gallery_positions = save[132]
        if save[133] is not None:
            prefs.chart_bg = save[133]
        if save[134] is not None:
            prefs.left_panel_mode = save[134]
        if save[135] is not None:
            gui.last_left_panel_mode = save[135]
        if save[136] is not None:
            prefs.gst_device = save[136]
        if save[137] is not None:
            search_string_cache = save[137]
        if save[138] is not None:
            search_dia_string_cache = save[138]
        if save[139] is not None:
            gen_codes = save[139]
        if save[140] is not None:
            gui.show_ratings = save[140]
        if save[141] is not None:
            gui.show_album_ratings = save[141]
        if save[142] is not None:
            prefs.radio_urls = save[142]
        if save[143] is not None:
            gui.restore_showcase_view = save[143]
        if save[144] is not None:
            gui.saved_prime_tab = save[144]
        if save[145] is not None:
            gui.saved_prime_direction = save[145]
        if save[146] is not None:
            prefs.sync_playlist = save[146]
        if save[147] is not None:
            prefs.spot_client = save[147]
        if save[148] is not None:
            prefs.spot_secret = save[148]
        if save[149] is not None:
            prefs.show_band = save[149]
        if save[150] is not None:
            prefs.download_playlist = save[150]
        if save[151] is not None:
            spot_cache_saved_albums = save[151]
        if save[152] is not None:
            prefs.auto_rec = save[152]
        if save[153] is not None:
            prefs.spotify_token = save[153]
        if save[154] is not None:
            prefs.use_libre_fm = save[154]
        if save[155] is not None:
            prefs.old_playlist_box_position = save[155]
        if save[156] is not None:
            prefs.artist_list_sort_mode = save[156]
        if save[157] is not None:
            prefs.phazor_device_selected = save[157]
        if save[158] is not None:
            prefs.failed_background_artists = save[158]
        if save[159] is not None:
            prefs.bg_flips = save[159]
        if save[160] is not None:
            prefs.tray_show_title = save[160]
        if save[161] is not None:
            prefs.artist_list_style = save[161]
        if save[162] is not None:
            ds = save[162]
            for d in ds:
                nt = TrackClass()
                nt.__dict__.update(d)
                master_library[d["index"]] = nt
        if save[163] is not None:
            prefs.premium = save[163]
        if save[164] is not None:
            gui.restore_radio_view = save[164]
        if save[165] is not None:
            radio_playlists = save[165]
        if save[166] is not None:
            radio_playlist_viewing = save[166]
        if save[167] is not None:
            prefs.radio_thumb_bans = save[167]
        if save[168] is not None:
            prefs.playlist_exports = save[168]
        if save[169] is not None:
            prefs.show_chromecast = save[169]

        state_file.close()
        del save
        break

    except IndexError:
        break
    except:
        if os.path.isfile(user_directory + "/state.p"):
            print('Error loading save file')

core_timer.set()
print(f"Database loaded in {round(perf_timer.get(), 3)} seconds.")

perf_timer.set()
keys = set(master_library.keys())
for pl in multi_playlist:
    keys -= set(pl[2])
if len(keys) > 5000:
    gui.suggest_clean_db = True
#print(f"Database scanned in {round(perf_timer.get(), 3)} seconds.")


# temporary
if window_size is None:
    window_size = window_default_size
    gui.rspw = 200

def track_number_process(line):
    line = str(line).split("/", 1)[0].lstrip("0")
    if prefs.dd_index and len(line) == 1:
        return "0" + line
    return line

def advance_theme():
    global theme

    theme += 1
    gui.reload_theme = True

def get_theme_number(name):
    if name == "Mindaro":
       return 0
    themes = get_themes()
    for i, theme in enumerate(themes):
        if theme[1] == name:
            return i + 1
    return 0

def get_theme_name(number):
    if number == 0:
        return 'Mindaro'
    number -= 1
    themes = get_themes()
    print((number, themes))
    if len(themes) > number:
        return themes[number][1]
    return ""


# Upgrading from older versions
if db_version > 0:

    if db_version <= 0.8:
        print("Updating database from version 0.8 to 0.9")
        for key, value in master_library.items():
            setattr(master_library[key], 'skips', 0)

    if db_version <= 0.9:
        print("Updating database from version 0.9 to 1.1")
        for key, value in master_library.items():
            setattr(master_library[key], 'comment', "")

    if db_version <= 1.1:
        print("Updating database from version 1.1 to 1.2")
        for key, value in master_library.items():
            setattr(master_library[key], 'album_artist', "")

    if db_version <= 1.2:
        print("Updating database to version 1.3")
        for key, value in master_library.items():
            setattr(master_library[key], 'disc_number', "")
            setattr(master_library[key], 'disc_total', "")

    if db_version <= 1.3:
        print("Updating database to version 1.4")
        for key, value in master_library.items():
            setattr(master_library[key], 'lyrics', "")
            setattr(master_library[key], 'track_total', "")
        show_message(
            "Upgrade complete. Note: New attributes such as disk number won't show for existing tracks (delete state.p to reset)")

    if db_version <= 1.4:
        print("Updating database to version 1.5")
        for playlist in multi_playlist:
            playlist.append(uid_gen())

    if db_version <= 1.5:
        print("Updating database to version 1.6")
        for i in range(len(multi_playlist)):
            if len(multi_playlist[i]) == 7:
                multi_playlist[i].append("")

    if db_version <= 1.6:
        print("Updating preferences to 1.7")
        # gui.show_stars = False
        if install_mode:
                #shutil.copy(install_directory + "/config.txt", user_directory)
                print("Rewrote user config file")

    if db_version <= 1.7:
        print("Updating database to version 1.8")
        if install_mode:
                print(".... Overwriting user config file")
                #shutil.copy(install_directory + "/config.txt", user_directory)

        try:
            print(".... Updating playtime database")

            old = star_store.db
            #perf_timer.set()
            old_total = sum(old.values())
            #print(perf_timer.get())
            print("Old total: ", end='')
            print(old_total)
            star_store.db = {}

            new = {}
            for track in master_library.values():
                key = track.title + track.filename
                if key in old:
                    n_value = [old[key], ""]
                    n_key = star_store.object_key(track)
                    star_store.db[n_key] = n_value

            print("New total: ", end='')
            diff = old_total - star_store.get_total()
            print(int(diff), end='')
            print(" Secconds could not be matched to tracks. Total playtime won't be affected")
            star_store.db[("", "", "LOST")] = [diff, ""]
            print("Upgrade Complete")
        except:
            print("Error upgrading database")
            show_message("Error loading old database, did the program not exit properly after updating? Oh well.")

    if db_version <= 1.8:
        print("Updating database to 1.9")
        for key, value in master_library.items():
            setattr(master_library[key], 'track_gain', None)
            setattr(master_library[key], 'album_gain', None)
        show_message("Upgrade complete. Run a tag rescan if you want enable ReplayGain")

    if db_version <= 1.9:
        print("Updating database to version 2.0")
        for key, value in master_library.items():
            setattr(master_library[key], 'modified_time', 0)
        show_message("Upgrade complete. New sorting option may require tag rescan.")


    if db_version <= 2.0:
        print("Updating database to version 2.1")
        for key, value in master_library.items():
            setattr(master_library[key], 'is_embed_cue', False)
            setattr(master_library[key], 'cue_sheet', "")
        show_message("Updated to v2.6.3")

    if db_version <= 2.1:
        print("Updating database to version 2.1")
        for key, value in master_library.items():
            setattr(master_library[key], 'lfm_friend_likes', set())

    if db_version <= 2.2:
        for i in range(len(multi_playlist)):
            if len(multi_playlist[i]) < 9:
                multi_playlist[i].append(True)

    if db_version <= 2.3:
        print("Updating database to version 2.4")
        for key, value in master_library.items():
            setattr(master_library[key], 'bit_depth', 0)

    if db_version <= 2.4:
        if theme > 0:
           theme += 1

    if db_version <= 2.5:
        print("Updating database to version 2.6")
        for key, value in master_library.items():
            setattr(master_library[key], 'is_network', False)
        # for i in range(len(multi_playlist)):
        #     if len(multi_playlist[i]) < 10:
        #         multi_playlist[i].append(False)

    if db_version <= 26:
        print("Updating database to version 27")
        for i in range(len(multi_playlist)):
            if len(multi_playlist[i]) == 9:
                multi_playlist[i].append(False)

    if db_version <= 27:
        print("Updating database to version 28")
        for i in range(len(multi_playlist)):
            if len(multi_playlist[i]) <= 10:
                multi_playlist[i].append("")

    if db_version <= 29:
        print("Updating database to version 30")
        for key, value in master_library.items():
            setattr(master_library[key], 'composer', "")

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):

            with open(os.path.join(config_directory, "input.txt"), 'a') as f:
                f.write("global-search G Ctrl\n")
                f.write("cycle-theme-reverse\n")
                f.write("reload-theme F10\n")

        show_message("Welcome to v4.4.0. Run a tag rescan if you want enable Composer metadata.")

    if db_version <= 30:
        for i, item in enumerate(p_force_queue):
            try:
                assert item[6]
            except:
                p_force_queue[i].append(False)

    if db_version <= 31:

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):
            with open(os.path.join(config_directory, "input.txt"), 'a') as f:
                f.write("love-selected\n")
        gui.set_bar = True

    if db_version <= 32:
        if theme > 1:
           theme += 1

    if db_version <= 33:
        print("Update to db 34")
        for key, value in master_library.items():
            if not hasattr(master_library[key], 'misc'):
                setattr(master_library[key], 'misc', {})

    if db_version <= 34:
        print("Update to dv 35")
        # Moved to after config load

    if db_version <= 35:
        print("Updating database to version 36")

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):
            with open(os.path.join(config_directory, "input.txt"), 'a') as f:
                f.write("toggle-show-art H Ctrl\n")

    if db_version <= 37:
        print("Updating database to version 38")

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):
            with open(os.path.join(config_directory, "input.txt"), 'a') as f:
                f.write("toggle-console `\n")

    if db_version <= 38:
        print("Updating database to version 39")

        for key, value in star_store.db.items():
            print(value)
            if len(value) == 2:
                value.append(0)
                star_store.db[key] = value

    if db_version <= 39:
        print("Updating database to version 40")

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):
            f = open(os.path.join(config_directory, "input.txt"), 'r')
            text = f.read()
            f.close()
            lines = text.splitlines()
            if "l ctrl" not in text.lower():
                f = open(os.path.join(config_directory, "input.txt"), 'w')
                for line in lines:
                    line = line.strip()
                    if line == "love-selected":
                        line = "love-selected L Ctrl"
                    f.write(line + "\n")
                f.close()

    if db_version <= 40:
        print("Updating database to version 41")
        old = copy.deepcopy(prefs.lyrics_enables)
        prefs.lyrics_enables.clear()
        if "apiseeds" in old:
            prefs.lyrics_enables.append("Apiseeds")
        if "lyricwiki" in old:
            prefs.lyrics_enables.append("LyricWiki")
        if "genius" in old:
            prefs.lyrics_enables.append("Genius")


    if db_version <= 41:
        print("Updating database to version 42")

        for key, value in gen_codes.items():
            gen_codes[key] = value.replace("f\"", "p\"")

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):
            f = open(os.path.join(config_directory, "input.txt"), 'r')
            text = f.read()
            f.close()
            lines = text.splitlines()

            f = open(os.path.join(config_directory, "input.txt"), 'w')
            for line in lines:
                line = line.strip()
                if "rename-playlist" in line:

                    f.write(line + "\n")

                    line = "new-playlist T Ctrl\n"
                    f.write(line)

                    line = "\nnew-generator-playlist\n"
                    f.write(line)
                    if "e ctrl" in text.lower():
                        line = "edit-generator\n\n"
                    else:
                        line = "edit-generator E Ctrl\n\n"
                    f.write(line)

                    line = "search-lyrics-selected\n"
                    f.write(line)
                    line = "substitute-search-selected"

                f.write(line + "\n")

            f.close()

    if db_version <= 42:
        print("Updating database to version 43")

    if db_version <= 43:
        print("Updating database to version 44")
        # Repair db
        for key, value in star_store.db.items():
            if len(value) == 2:
                value.append(0)
                star_store.db[key] = value

    if db_version <= 44:
        print("Updating database to version 45")
        print("Cleaning cache directory")
        for item in os.listdir(cache_directory):
            path = os.path.join(cache_directory, item)
            if "-lfm." in item or "-ftv." in item or "-dcg." in item:
                os.rename(path, os.path.join(a_cache_dir, item))
        for item in os.listdir(cache_directory):
            path = os.path.join(cache_directory, item)
            if os.path.isfile(path):
                os.remove(path)

    if db_version <= 45:
        print("Updating database to version 46")
        for p in multi_playlist:
            if type(p[7]) != list:
                p[7] = [p[7]]

    if db_version <= 46:
        print("Updating database to version 47")
        for p in multi_playlist:
            if type(p[7]) != list:
                p[7] = [p[7]]

    if db_version <= 47:
        print("Updating database to version 48")
        if os.path.isfile(os.path.join(user_directory, "spot-r-token")):
            show_message("Welcome to v6.1.0. Due to changes, please re-authorise Spotify", "You can do this by clicking 'Forget Account', then 'Authroise' in Settings > Accounts > Spotify")

    if db_version <= 48:
        print("Fix bad upgrade, now 49")
        for key, value in master_library.items():
            if not hasattr(master_library[key], 'url_key'):
                setattr(master_library[key], 'url_key', "")
            if not hasattr(master_library[key], 'art_url_key'):
                setattr(master_library[key], 'art_url_key', "")

    if db_version <= 49:
        print("Updating database to version 50")
        if os.path.isfile(os.path.join(user_directory, "spot-r-token")):
            show_message("Welcome to v6.3.0. Due to an upgrade, please re-authorise Spotify",
                         "You can do this by clicking 'Authroise' in Settings > Accounts > Spotify")
            os.remove(os.path.join(user_directory, "spot-r-token"))

    if db_version <= 54:
        print("Updating database to version 55")
        for key, value in master_library.items():
            setattr(master_library[key], 'lfm_scrobbles', 0)

    if db_version <= 55:
        print("Update to db 56")
        for key, value in master_library.items():

            if hasattr(value, "track_gain"):
                if value.track_gain != 0:
                    value.misc["replaygain_track_gain"] = value.track_gain
                del value.track_gain

            if hasattr(value, "album_gain"):
                if value.album_gain != 0:
                    value.misc["replaygain_album_gain"] = value.album_gain
                del value.album_gain

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):
            with open(os.path.join(config_directory, "input.txt"), 'a') as f:
                f.write("toggle-right-panel MB5\n")
                f.write("toggle-gallery MB4\n")

    if db_version <= 56:
        print("Update to db 57")
        if "Apiseeds" in prefs.lyrics_enables:
            prefs.lyrics_enables.remove("Apiseeds")
            prefs.lyrics_enables.append("Happi")

    if db_version <= 57:
        print("Updating database to version 58")

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):
            with open(os.path.join(config_directory, "input.txt"), 'a') as f:
                f.write("\nregenerate-playlist R Alt\n")
                f.write("clear-queue Q Shift Alt\n")
                f.write("resize-window-16:9 F11 Alt\n")
                f.write("delete-playlist-force W Shift Ctrl\n")

    if db_version <= 58:
        print("Updating database to version 59")

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):
            with open(os.path.join(config_directory, "input.txt"), 'a') as f:
                f.write("\nrandom-album ; Alt\n")

    if db_version <= 59:
        print("Updating database to version 60")

        if prefs.spotify_token:
            show_message("Upgrade to v6.5.1. It looks like you are using Spotify.", "Please click \"Authorise\" again in the settings")
        prefs.spotify_token = ""

    if db_version <= 60:
        print("Updating database to version 61")

        token_path = os.path.join(user_directory, "spot-token-pkce")
        if os.path.exists(token_path):
            prefs.spotify_token = ""
            os.remove(token_path)
            show_message("Upgrade to v6.5.3 complete", "It looks like you are using Spotify. Please re-setup Spotify again in the settings")

    if db_version <= 61:
        print("Updating database to version 62")

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):
            with open(os.path.join(config_directory, "input.txt"), 'a') as f:
                f.write("\ntransfer-playtime-to P Ctrl Shift\n")

    if db_version <= 62:
        print("Updating database to version 63")
        for item in gui.pl_st:
            if item[0] == "T":
                item[0] = "#"

        if install_directory != config_directory and os.path.isfile(os.path.join(config_directory, "input.txt")):
            with open(os.path.join(config_directory, "input.txt"), 'r') as f:
                lines = f.readlines()
            with open(os.path.join(config_directory, "input.txt"), 'w') as f:
                for line in lines:
                    if line == "vol-up Up Shift\n" or line == "vol-down Down Shift\n":
                        continue
                    f.write(line)
                f.write("\n")
                f.write("shift-up Up Shift\n")
                f.write("shift-down Down Shift\n")
                f.write("vol-up Up Ctrl\n")
                f.write("vol-down Down Ctrl\n")

    if db_version <= 63:
        print("Updating database to version 64")
        if prefs.radio_urls:
            radio_playlists[0]["items"].extend(prefs.radio_urls)
            prefs.radio_urls = []
        prefs.show_nag = True
#prefs.show_nag = True

if playing_in_queue > len(QUE) - 1:
    playing_in_queue = len(QUE) - 1


shoot = threading.Thread(target=keymaps.load)
shoot.daemon = True
shoot.start()


# Loading Config -----------------

download_directories = []

if os.path.isdir(download_directory):
    download_directories.append(download_directory)

if music_directory is not None and os.path.isdir(music_directory):
    download_directories.append(music_directory)

from t_modules.t_config import Config

cf = Config()


def save_prefs():

    cf.update_value("sync-bypass-transcode", prefs.bypass_transcode)
    cf.update_value("sync-bypass-low-bitrate", prefs.smart_bypass)
    cf.update_value("radio-record-codec", prefs.radio_record_codec)

    cf.update_value("plex-username", prefs.plex_username)
    cf.update_value("plex-password", prefs.plex_password)
    cf.update_value("plex-servername", prefs.plex_servername)

    cf.update_value("subsonic-username", prefs.subsonic_user)
    cf.update_value("subsonic-password", prefs.subsonic_password)
    cf.update_value("subsonic-password-plain", prefs.subsonic_password_plain)
    cf.update_value("subsonic-server-url", prefs.subsonic_server)

    cf.update_value("jelly-username", prefs.jelly_username)
    cf.update_value("jelly-password", prefs.jelly_password)
    cf.update_value("jelly-server-url", prefs.jelly_server_url)

    cf.update_value("koel-username", prefs.koel_username)
    cf.update_value("koel-password", prefs.koel_password)
    cf.update_value("koel-server-url", prefs.koel_server_url)
    cf.update_value("stream-bitrate", prefs.network_stream_bitrate)

    cf.update_value("display-language", prefs.ui_lang)
    #cf.update_value("decode-search", prefs.diacritic_search)

    #cf.update_value("use-log-volume-scale", prefs.log_vol)
    cf.update_value("audio-backend", prefs.backend)
    cf.update_value("seek-interval", prefs.seek_interval)
    cf.update_value("pause-fade-time", prefs.pause_fade_time)
    #cf.update_value("cross-fade-time", prefs.cross_fade_time)
    cf.update_value("device-buffer-length", prefs.device_buffer)
    cf.update_value("fast-scrubbing", prefs.pa_fast_seek)
    #cf.update_value("force-mono", prefs.mono)
    #cf.update_value("disconnect-device-pause", prefs.dc_device_setting)
    #cf.update_value("use-short-buffering", prefs.short_buffer)

    cf.update_value("gst-output", prefs.gst_output)
    cf.update_value("gst-use-custom-output", prefs.gst_use_custom_output)

    cf.update_value("separate-multi-genre", prefs.sep_genre_multi)

    cf.update_value("tag-editor-name", prefs.tag_editor_name)
    cf.update_value("tag-editor-target", prefs.tag_editor_target)

    cf.update_value("playback-follow-cursor", prefs.playback_follow_cursor)
    cf.update_value("spotify-prefer-web", prefs.launch_spotify_web)
    cf.update_value("back-restarts", prefs.back_restarts)
    cf.update_value("end-queue-stop", prefs.stop_end_queue)
    cf.update_value("block-suspend", prefs.block_suspend)

    cf.update_value("ui-scale", prefs.scale_want)
    cf.update_value("auto-scale", prefs.x_scale)
    cf.update_value("tracklist-y-text-offset", prefs.tracklist_y_text_offset)
    cf.update_value("theme-name", prefs.theme_name)
    cf.update_value("mac-style", prefs.macstyle)
    cf.update_value("allow-art-zoom", prefs.zoom_art)

    cf.update_value("scroll-gallery-by-row", prefs.gallery_row_scroll)
    cf.update_value("prefs.gallery_scroll_wheel_px", prefs.gallery_row_scroll)
    cf.update_value("scroll-spectrogram", prefs.spec2_scroll)
    cf.update_value("mascot-opacity", prefs.custom_bg_opacity)
    cf.update_value("synced-lyrics-time-offset", prefs.sync_lyrics_time_offset)
    
    cf.update_value("artist-list-prefers-album-artist", prefs.artist_list_prefer_album_artist)
    cf.update_value("side-panel-info-persists", prefs.meta_persists_stop)
    cf.update_value("side-panel-info-selected", prefs.meta_shows_selected)
    cf.update_value("side-panel-info-selected-always", prefs.meta_shows_selected_always)
    cf.update_value("mini-mode-avoid-notifications", prefs.stop_notifications_mini_mode)
    cf.update_value("hide-queue-when-empty", prefs.hide_queue)
    #cf.update_value("show-playlist-list", prefs.show_playlist_list)
    cf.update_value("enable-art-header-bar", prefs.art_in_top_panel)
    cf.update_value("always-art-header-bar", prefs.always_art_header)
    #cf.update_value("prefer-center-bg", prefs.center_bg)
    cf.update_value("showcase-texture-background", prefs.showcase_overlay_texture)
    cf.update_value("side-panel-style", prefs.side_panel_layout)
    cf.update_value("side-lyrics-art", prefs.show_side_lyrics_art_panel)
    cf.update_value("side-lyrics-art-on-top", prefs.lyric_metadata_panel_top)
    cf.update_value("absolute-track-indices", prefs.use_absolute_track_index)
    cf.update_value("auto-hide-bottom-title", prefs.hide_bottom_title)
    cf.update_value("auto-show-playing", prefs.auto_goto_playing)
    cf.update_value("notify-include-album", prefs.notify_include_album)
    cf.update_value("show-rating-hint", prefs.rating_playtime_stars)
    cf.update_value("drag-tab-to-unpin", prefs.drag_to_unpin)

    cf.update_value("gallery-thin-borders", prefs.thin_gallery_borders)
    cf.update_value("increase-row-spacing", prefs.increase_gallery_row_spacing)
    cf.update_value("gallery-center-text", prefs.center_gallery_text)

    cf.update_value("use-custom-fonts", prefs.use_custom_fonts)
    cf.update_value("font-main-standard", prefs.linux_font)
    cf.update_value("font-main-medium", prefs.linux_font_semibold)
    cf.update_value("font-main-bold", prefs.linux_font_bold)

    cf.update_value("force-subpixel-text", prefs.force_subpixel_text)

    cf.update_value("double-digit-indices", prefs.dd_index)
    cf.update_value("column-album-artist-fallsback", prefs.column_aa_fallback_artist)
    cf.update_value("left-aligned-album-artist-title", prefs.left_align_album_artist_title)
    cf.update_value("import-auto-sort", prefs.auto_sort)

    cf.update_value("encode-output-dir", prefs.custom_encoder_output)
    cf.update_value("sync-device-music-dir", prefs.sync_target)
    cf.update_value("add_download_directory", prefs.download_dir1)

    cf.update_value("use-system-tray", prefs.use_tray)
    cf.update_value("enable-remote-interface", prefs.enable_remote)

    cf.update_value("enable-mpris", prefs.enable_mpris)
    cf.update_value("hide-maximize-button", prefs.force_hide_max_button)
    cf.update_value("restore-window-position", prefs.save_window_position)
    cf.update_value("enable-gnome-mediakeys", prefs.mkey)
    cf.update_value("resume-playback-on-restart", prefs.reload_play_state)
    cf.update_value("auto-dl-artist-data", prefs.auto_dl_artist_data)

    cf.update_value("fanart.tv-cover", prefs.enable_fanart_cover)
    cf.update_value("fanart.tv-artist", prefs.enable_fanart_artist)
    cf.update_value("fanart.tv-background", prefs.enable_fanart_bg)
    cf.update_value("auto-update-playlists", prefs.always_auto_update_playlists)
    cf.update_value("write-ratings-to-tag", prefs.write_ratings)
    cf.update_value("enable-spotify", prefs.spot_mode)
    cf.update_value("enable-discord-rpc", prefs.discord_enable)
    cf.update_value("auto-search-lyrics", prefs.auto_lyrics)

    cf.update_value("discogs-personal-access-token", prefs.discogs_pat)
    cf.update_value("listenbrainz-token", prefs.lb_token)
    cf.update_value("custom-listenbrainz-url", prefs.listenbrainz_url)

    cf.update_value("maloja-key", prefs.maloja_key)
    cf.update_value("maloja-url", prefs.maloja_url)
    cf.update_value("maloja-enable", prefs.maloja_enable)

    #cf.update_value("broadcast-port", prefs.broadcast_port)
    cf.update_value("broadcast-page-port", prefs.metadata_page_port)
    #cf.update_value("broadcast-bitrate", prefs.broadcast_bitrate)
    cf.update_value("show-current-on-transition", prefs.show_current_on_transition)

    cf.update_value("chart-columns", prefs.chart_columns)
    cf.update_value("chart-rows", prefs.chart_rows)
    cf.update_value("chart-uses-text", prefs.chart_text)
    cf.update_value("chart-font", prefs.chart_font)
    cf.update_value("chart-sorts-top-played", prefs.topchart_sorts_played)

    if os.path.isdir(config_directory):
        cf.dump(os.path.join(config_directory, "tauon.conf"))
    else:
        print("ERROR: Missing config directory")

def load_prefs():

    cf.reset()
    cf.load(os.path.join(config_directory, "tauon.conf"))

    cf.add_comment("Tauon Music Box configuration file")
    cf.br()
    cf.add_comment("This file will be regenerated while app is running. Formatting and additional comments will be lost.")
    cf.add_comment("Tip: Use TOML syntax highlighting")

    cf.br()
    cf.add_text("[audio]")

    prefs.backend = cf.sync_add("int", "audio-backend", prefs.backend, "4: Built in backend (Phazor), 2: GStreamer")
    prefs.seek_interval = cf.sync_add("int", "seek-interval", prefs.seek_interval, "Interval to seek when using shortcut. In seconds. Default is 15.")
    #prefs.pause_fade_time = cf.sync_add("int", "pause-fade-time", prefs.pause_fade_time, "In milliseconds. Default is 400. (GStreamer Only)")

    if prefs.pause_fade_time < 100:
        prefs.pause_fade_time = 100
    if prefs.pause_fade_time > 5000:
        prefs.pause_fade_time = 5000

    #prefs.cross_fade_time = cf.sync_add("int", "cross-fade-time", prefs.cross_fade_time, "This is a placeholder setting and currently has no effect.")
    prefs.device_buffer = cf.sync_add("int", "device-buffer-length", prefs.device_buffer, "In milliseconds. Used by Phazor backend only. Default: 40")
    prefs.pa_fast_seek = cf.sync_add("bool", "fast-scrubbing", prefs.pa_fast_seek, "Seek without a delay but may cause audible popping")
    #prefs.log_vol = cf.sync_add("bool", "use-log-volume-scale", prefs.log_vol, "This is a placeholder setting and currently has no effect.")
    #prefs.mono = cf.sync_add("bool", "force-mono", prefs.mono, "This is a placeholder setting and currently has no effect.")
    # prefs.dc_device_setting = cf.sync_add("string", "disconnect-device-pause", prefs.dc_device_setting, "Can be \"on\" or \"off\". BASS only. When off, connection to device will he held open.")
    # prefs.short_buffer = cf.sync_add("bool", "use-short-buffering", prefs.short_buffer, "BASS only.")

    prefs.gst_output = cf.sync_add("string", "gst-output", prefs.gst_output, "GStreamer output pipeline specification.")
    prefs.gst_use_custom_output = cf.sync_add("bool", "gst-use-custom-output", prefs.gst_use_custom_output, "Set this to true to apply any manual edits of the above string.")

    if prefs.dc_device_setting == 'on':
        prefs.dc_device = True
    elif prefs.dc_device_setting == 'off':
        prefs.dc_device = False

    cf.br()
    cf.add_text("[locale]")
    prefs.ui_lang = cf.sync_add("string", "display-language", prefs.ui_lang, "Override display language to use if "
                                                                             "available. E.g. \"en\", \"ja\", \"zh_CH\". "
                                                                             "Default: \"auto\"")
    # prefs.diacritic_search = cf.sync_add("bool", "decode-search", prefs.diacritic_search, "Allow searching of diacritics etc using ascii in search functions. (Disablng may speed up search)")
    cf.br()
    cf.add_text("[search]")
    prefs.sep_genre_multi = cf.sync_add("bool", "separate-multi-genre", prefs.sep_genre_multi, "If true, the standard genre result will exclude results from multi-value tags. These will be included in a separate result.")


    cf.br()
    cf.add_text("[tag-editor]")
    if system == 'windows' or msys:
        prefs.tag_editor_name = cf.sync_add("string", "tag-editor-name", "Picard", "Name to display in UI.")
        prefs.tag_editor_target = cf.sync_add("string", "tag-editor-target", "C:\Program Files (x86)\MusicBrainz Picard\picard.exe", "The path of the exe to run.")
    else:
        prefs.tag_editor_name = cf.sync_add("string", "tag-editor-name", "Picard", "Name to display in UI.")
        prefs.tag_editor_target = cf.sync_add("string", "tag-editor-target", "picard", "The name of the binary to call.")


    cf.br()
    cf.add_text("[playback]")
    prefs.playback_follow_cursor = cf.sync_add("bool", "playback-follow-cursor", prefs.playback_follow_cursor, "When advancing, always play the track that is selected.")
    prefs.launch_spotify_web = cf.sync_add("bool", "spotify-prefer-web", prefs.launch_spotify_web, "Launch the web client rather then attempting to launch the desktop client.")
    prefs.back_restarts = cf.sync_add("bool", "back-restarts", prefs.back_restarts, "Pressing the back button restarts playing track on first press.")
    prefs.stop_end_queue = cf.sync_add("bool", "end-queue-stop", prefs.stop_end_queue, "Queue will always enable auto-stop on last track")
    prefs.block_suspend = cf.sync_add("bool", "block-suspend", prefs.block_suspend, "Prevent system suspend during playback")

    cf.br()
    cf.add_text("[HiDPI]")
    prefs.scale_want = cf.sync_add("float", "ui-scale", prefs.scale_want, "UI scale factor. Default is 1.0, try increase if using a HiDPI display." )
    prefs.x_scale = cf.sync_add("bool", "auto-scale", prefs.x_scale, "Automatically choose above setting" )
    prefs.tracklist_y_text_offset = cf.sync_add("int", "tracklist-y-text-offset", prefs.tracklist_y_text_offset, "If you're using a UI scale, you may need to tweak this.")

    cf.br()
    cf.add_text("[ui]")

    prefs.theme_name = cf.sync_add("string", "theme-name", prefs.theme_name)
    macstyle = cf.sync_add("bool", "mac-style", prefs.macstyle, "Use macOS style window buttons")
    prefs.zoom_art = cf.sync_add("bool", "allow-art-zoom", prefs.zoom_art)
    prefs.gallery_row_scroll = cf.sync_add("bool", "scroll-gallery-by-row", True)
    prefs.gallery_scroll_wheel_px = cf.sync_add("int", "scroll-gallery-distance", 90, "Only has effect if scroll-gallery-by-row is false.")
    prefs.spec2_scroll = cf.sync_add("bool", "scroll-spectrogram", prefs.spec2_scroll)
    prefs.custom_bg_opacity = cf.sync_add("int", "mascot-opacity", prefs.custom_bg_opacity)
    if prefs.custom_bg_opacity < 0 or prefs.custom_bg_opacity > 100:
        prefs.custom_bg_opacity = 40
        print("Warning: Invalid value for mascot-opacity")

    prefs.sync_lyrics_time_offset = cf.sync_add("int", "synced-lyrics-time-offset", prefs.sync_lyrics_time_offset, "In milliseconds. May be negative.")
    prefs.artist_list_prefer_album_artist = cf.sync_add("bool", "artist-list-prefers-album-artist", prefs.artist_list_prefer_album_artist, "May require restart for change to take effect.")
    prefs.meta_persists_stop = cf.sync_add("bool", "side-panel-info-persists", prefs.meta_persists_stop, "Show album art and metadata of last played track when stopped.")
    prefs.meta_shows_selected = cf.sync_add("bool", "side-panel-info-selected", prefs.meta_shows_selected, "Show album art and metadata of selected track when stopped. (overides above setting)")
    prefs.meta_shows_selected_always = cf.sync_add("bool", "side-panel-info-selected-always", prefs.meta_shows_selected_always, "Show album art and metadata of selected track at all times. (overides the above 2 settings)")
    prefs.stop_notifications_mini_mode = cf.sync_add("bool", "mini-mode-avoid-notifications", prefs.stop_notifications_mini_mode, "Avoid sending track change notifications when in Mini Mode")
    prefs.hide_queue = cf.sync_add("bool", "hide-queue-when-empty", prefs.hide_queue)
    #prefs.show_playlist_list = cf.sync_add("bool", "show-playlist-list", prefs.show_playlist_list)

    prefs.show_current_on_transition = cf.sync_add("bool", "show-current-on-transition", prefs.show_current_on_transition, "Always jump to new playing track even with natural transition (broken setting, is always enabled")
    prefs.art_in_top_panel = cf.sync_add("bool", "enable-art-header-bar", prefs.art_in_top_panel, "Show art in top panel when window is narrow")
    prefs.always_art_header = cf.sync_add("bool", "always-art-header-bar", prefs.always_art_header, "Show art in top panel at any size. (Requires enable-art-header-bar)")

    #prefs.center_bg = cf.sync_add("bool", "prefer-center-bg", prefs.center_bg, "Always center art for the background art function")
    prefs.showcase_overlay_texture = cf.sync_add("bool", "showcase-texture-background", prefs.showcase_overlay_texture, "Draw pattern over background art")
    prefs.side_panel_layout = cf.sync_add("int", "side-panel-style", prefs.side_panel_layout, "0:default, 1:centered")
    prefs.show_side_lyrics_art_panel = cf.sync_add("bool", "side-lyrics-art", prefs.show_side_lyrics_art_panel)
    prefs.lyric_metadata_panel_top = cf.sync_add("bool", "side-lyrics-art-on-top", prefs.lyric_metadata_panel_top)
    prefs.use_absolute_track_index = cf.sync_add("bool", "absolute-track-indices", prefs.use_absolute_track_index, "For playlists with titles disabled only")
    prefs.hide_bottom_title = cf.sync_add("bool", "auto-hide-bottom-title", prefs.hide_bottom_title, "Hide title in bottom panel when already shown in side panel")
    prefs.auto_goto_playing = cf.sync_add("bool", "auto-show-playing", prefs.auto_goto_playing, "Show playing track in current playlist on track and playlist change even if not the playing playlist")

    prefs.notify_include_album = cf.sync_add("bool", "notify-include-album", prefs.notify_include_album, "Include album name in track change notifications")
    prefs.rating_playtime_stars = cf.sync_add("bool", "show-rating-hint", prefs.rating_playtime_stars, "Indicate playtime in rating stars")

    prefs.drag_to_unpin = cf.sync_add("bool", "drag-tab-to-unpin", prefs.drag_to_unpin, "Dragging a tab off the top-panel un-pins it")

    cf.br()
    cf.add_text("[gallery]")
    prefs.thin_gallery_borders = cf.sync_add("bool", "gallery-thin-borders", prefs.thin_gallery_borders)
    prefs.increase_gallery_row_spacing = cf.sync_add("bool", "increase-row-spacing", prefs.increase_gallery_row_spacing)
    prefs.center_gallery_text = cf.sync_add("bool", "gallery-center-text", prefs.center_gallery_text)

#show-current-on-transition", prefs.show_current_on_transition)
    if system != 'windows':
        cf.br()
        cf.add_text("[fonts]")
        cf.add_comment("Changes will require app restart.")
        prefs.use_custom_fonts = cf.sync_add("bool", "use-custom-fonts", prefs.use_custom_fonts, "Setting to false will reset below settings to default on restart")
        if prefs.use_custom_fonts:
            prefs.linux_font = cf.sync_add("string", "font-main-standard", prefs.linux_font, "Sugested alternate: Liberation Sans")
            prefs.linux_font_semibold = cf.sync_add("string", "font-main-medium", prefs.linux_font_semibold)
            prefs.linux_font_bold = cf.sync_add("string", "font-main-bold", prefs.linux_font_bold)
        else:
            cf.sync_add("string", "font-main-standard", prefs.linux_font, "Sugested alternate: Liberation Sans")
            cf.sync_add("string", "font-main-medium", prefs.linux_font_semibold)
            cf.sync_add("string", "font-main-bold", prefs.linux_font_bold)

        #prefs.force_subpixel_text = cf.sync_add("bool", "force-subpixel-text", prefs.force_subpixel_text, "(Subpixel rendering defaults to off with Flatpak)")

    cf.br()
    cf.add_text("[tracklist]")
    prefs.dd_index = cf.sync_add("bool", "double-digit-indices", prefs.dd_index)
    prefs.column_aa_fallback_artist = cf.sync_add("bool", "column-album-artist-fallsback", prefs.column_aa_fallback_artist, "'Album artist' column shows 'artist' if otherwise blank.")
    prefs.left_align_album_artist_title = cf.sync_add("bool", "left-aligned-album-artist-title", prefs.left_align_album_artist_title, "Show 'Album artist' in the folder/album title. Uses colour 'column-album-artist' from theme file")
    prefs.auto_sort = cf.sync_add("bool", "import-auto-sort", prefs.auto_sort, "This setting is deprecated and will be removed in a future version")

    cf.br()
    cf.add_text("[transcode]")
    prefs.bypass_transcode = cf.sync_add("bool", "sync-bypass-transcode", prefs.bypass_transcode, "Don't transcode files with sync function")
    prefs.smart_bypass = cf.sync_add("bool", "sync-bypass-low-bitrate", prefs.smart_bypass, "Skip transcode of <=128kbs folders")
    prefs.radio_record_codec = cf.sync_add("string", "radio-record-codec", prefs.radio_record_codec, "Can be OPUS, OGG, FLAC, or MP3. Default: OPUS")


    cf.br()
    cf.add_text("[directories]")
    cf.add_comment("Use full paths")
    prefs.sync_target = cf.sync_add("string", "sync-device-music-dir", prefs.sync_target)
    prefs.custom_encoder_output = cf.sync_add("string", "encode-output-dir", prefs.custom_encoder_output, "E.g. \"/home/example/music/output\". If left blank, encode-output in home music dir will be used.")
    if prefs.custom_encoder_output:
        prefs.encoder_output = prefs.custom_encoder_output
    prefs.download_dir1 = cf.sync_add("string", "add_download_directory", prefs.download_dir1, "Add another folder to monitor in addition to home downloads and music.")
    if prefs.download_dir1 and prefs.download_dir1 not in download_directories:
        if os.path.isdir(prefs.download_dir1):
            download_directories.append(prefs.download_dir1)
        else:
            print("Warning: Invalid download directory in config")

    cf.br()
    cf.add_text("[app]")
    prefs.enable_remote = cf.sync_add("bool", "enable-remote-interface", prefs.enable_remote, "For use with Tauon Music Remote for Android")
    prefs.use_tray = cf.sync_add("bool", "use-system-tray", prefs.use_tray)
    prefs.force_hide_max_button = cf.sync_add("bool", "hide-maximize-button", prefs.force_hide_max_button)
    prefs.save_window_position = cf.sync_add("bool", "restore-window-position", prefs.save_window_position, "Save and restore the last window position on desktop on open")
    prefs.enable_mpris = cf.sync_add("bool", "enable-mpris", prefs.enable_mpris)
    prefs.mkey = cf.sync_add("bool", "enable-gnome-mediakeys", prefs.mkey)
    prefs.reload_play_state = cf.sync_add("bool", "resume-playback-on-restart", prefs.reload_play_state)
    prefs.auto_dl_artist_data = cf.sync_add("bool", "auto-dl-artist-data", prefs.auto_dl_artist_data, "Enable automatic downloading of thumbnails in artist list")
    prefs.enable_fanart_cover = cf.sync_add("bool", "fanart.tv-cover", prefs.enable_fanart_cover)
    prefs.enable_fanart_artist = cf.sync_add("bool", "fanart.tv-artist", prefs.enable_fanart_artist)
    prefs.enable_fanart_bg = cf.sync_add("bool", "fanart.tv-background", prefs.enable_fanart_bg)
    prefs.always_auto_update_playlists = cf.sync_add("bool", "auto-update-playlists", prefs.always_auto_update_playlists, "Automatically update generator playlists")
    prefs.write_ratings = cf.sync_add("bool", "write-ratings-to-tag", prefs.write_ratings, "This writes FMPS_Rating tag to files. Only MP3 and FLAC supported. FLAC requires flac package installed on host system. ")
    prefs.spot_mode = cf.sync_add("bool", "enable-spotify", prefs.spot_mode, "Enable Spotify specific features")
    prefs.discord_enable = cf.sync_add("bool", "enable-discord-rpc", prefs.discord_enable, "Show track info in running Discord application")
    prefs.auto_lyrics = cf.sync_add("bool", "auto-search-lyrics", prefs.auto_lyrics, "Automatically search internet for lyrics when display is wanted")


    cf.br()
    cf.add_text("[tokens]")
    temp = cf.sync_add("string", "discogs-personal-access-token", prefs.discogs_pat, "Used for sourcing of artist thumbnails.")
    if not temp:
        prefs.discogs_pat = ""
    elif len(temp) != 40:
        print("Warning: Invalid discogs token in config")
    else:
        prefs.discogs_pat = temp

    prefs.listenbrainz_url = cf.sync_add("string", "custom-listenbrainz-url", prefs.listenbrainz_url, "Specify a custom Listenbrainz compatible api url. E.g. \"https://example.tld/apis/listenbrainz/\" Default: Blank")
    prefs.lb_token = cf.sync_add("string", "listenbrainz-token", prefs.lb_token)

    cf.br()
    cf.add_text("[maloja_account]")
    prefs.maloja_url = cf.sync_add("string", "maloja-url", prefs.maloja_url, "A Maloja server URL, e.g. http://localhost:32400")
    prefs.maloja_key = cf.sync_add("string", "maloja-key", prefs.maloja_key, "One of your Maloja API keys")
    prefs.maloja_enable = cf.sync_add("bool", "maloja-enable", prefs.maloja_enable)

    cf.br()
    cf.add_text("[plex_account]")
    prefs.plex_username = cf.sync_add("string", "plex-username", prefs.plex_username, "Probably the email address you used to make your PLEX account.")
    prefs.plex_password = cf.sync_add("string", "plex-password", prefs.plex_password, "The password associated with your PLEX account." )
    prefs.plex_servername = cf.sync_add("string", "plex-servername", prefs.plex_servername, "Probably your servers hostname.")

    cf.br()
    cf.add_text("[subsonic_account]")
    prefs.subsonic_user = cf.sync_add("string", "subsonic-username", prefs.subsonic_user)
    prefs.subsonic_password = cf.sync_add("string", "subsonic-password", prefs.subsonic_password)
    prefs.subsonic_password_plain = cf.sync_add("bool", "subsonic-password-plain", prefs.subsonic_password_plain)
    prefs.subsonic_server = cf.sync_add("string", "subsonic-server-url", prefs.subsonic_server)

    cf.br()
    cf.add_text("[koel_account]")
    prefs.koel_username = cf.sync_add("string", "koel-username", prefs.koel_username, "E.g. admin@example.com")
    prefs.koel_password = cf.sync_add("string", "koel-password", prefs.koel_password, "The default is admin")
    prefs.koel_server_url = cf.sync_add("string", "koel-server-url", prefs.koel_server_url, "The URL or IP:Port where the Koel server is hosted. E.g. http://localhost:8050 or https://localhost:8060")
    prefs.koel_server_url = prefs.koel_server_url.rstrip("/")
    
    cf.br()
    cf.add_text("[jellyfin_account]")
    prefs.jelly_username = cf.sync_add("string", "jelly-username", prefs.jelly_username, "")
    prefs.jelly_password = cf.sync_add("string", "jelly-password", prefs.jelly_password, "")
    prefs.jelly_server_url = cf.sync_add("string", "jelly-server-url", prefs.jelly_server_url, "The IP:Port where the jellyfin server is hosted.")
    prefs.jelly_server_url = prefs.jelly_server_url.rstrip("/")

    cf.br()
    cf.add_text("[network]")
    prefs.network_stream_bitrate = cf.sync_add("int", "stream-bitrate", prefs.network_stream_bitrate, "Optional bitrate koel/subsonic should transcode to (Server may need to be configured for this). Set to 0 to disable transcoding.")

    cf.br()
    cf.add_text("[broadcasting]")
    #prefs.broadcast_port = cf.sync_add("int", "broadcast-port", prefs.broadcast_port)
    prefs.metadata_page_port = cf.sync_add("int", "broadcast-page-port", prefs.metadata_page_port, "Make sure to stop server first or restart app after changing this.")
    # prefs.broadcast_bitrate = cf.sync_add("int", "broadcast-bitrate", prefs.broadcast_bitrate, "Codec is OGG. Higher values may reduce latency.")

    cf.br()
    cf.add_text("[chart]")
    prefs.chart_columns = cf.sync_add("int", "chart-columns", prefs.chart_columns)
    prefs.chart_rows = cf.sync_add("int", "chart-rows", prefs.chart_rows)
    prefs.chart_text = cf.sync_add("bool", "chart-uses-text", prefs.chart_text)
    prefs.topchart_sorts_played = cf.sync_add("bool", "chart-sorts-top-played", prefs.topchart_sorts_played)
    prefs.chart_font = cf.sync_add("string", "chart-font", prefs.chart_font, "Format is fontname + size. Default is Monospace 10")

load_prefs()
save_prefs()

# Temporary
if 0 < db_version <= 34:
    prefs.theme_name = get_theme_name(theme)
if 0 < db_version <= 51:
    prefs.device_buffer = 70
if 0 < db_version <= 53:
    print("Resetting fonts to defaults")
    prefs.linux_font = "Noto Sans"
    prefs.linux_font_semibold = "Noto Sans Medium"
    prefs.linux_font_bold = "Noto Sans Bold"
    save_prefs()


lang = ""

locale_dir = os.path.join(install_directory, "locale")

if flatpak_mode:
    locale_dir = "/app/share/locale"
elif install_directory.startswith("/opt/") or install_directory.startswith("/usr/"):
    locale_dir = "/usr/share/locale"

lang = []
if prefs.ui_lang != "auto" or prefs.ui_lang == "":
    lang = [prefs.ui_lang]

if lang:
    # Force set lang
    f = gettext.find('tauon', localedir=locale_dir, languages=lang)

    if f:
        translation = gettext.translation('tauon', localedir=locale_dir, languages=lang)
        translation.install()
        _ = translation.gettext

        print("Translation file loaded")
    else:
        print("No translation file available")

else:
    # Auto detect lang
    f = gettext.find('tauon', localedir=locale_dir)

    if f:
        translation = gettext.translation('tauon', localedir=locale_dir)
        translation.install()
        _ = translation.gettext

        print("Translation file loaded")
    # else:
    #     print("No translation file available")

# ----

sss = SDL_SysWMinfo()
SDL_GetWindowWMInfo(t_window, sss)

force_render = False

def auto_scale():
    old = prefs.x_scale
    if prefs.x_scale:
        if sss.subsystem in (SDL_SYSWM_WAYLAND, SDL_SYSWM_COCOA, SDL_SYSWM_UNKNOWN):
            prefs.scale_want = window_size[0] / logical_size[0]
            if old != prefs.x_scale:
                print("Applying scale based on buffer size")
        elif sss.subsystem == SDL_SYSWM_X11:
            if xdpi > 40:
                prefs.scale_want = xdpi / 96
                if old != prefs.x_scale:
                    print("Applying scale based on xft setting")

    prefs.scale_want = round(round(prefs.scale_want / 0.05) * 0.05, 2)

    if prefs.scale_want == 0.95:
        prefs.scale_want = 1.0
    if prefs.scale_want == 1.05:
        prefs.scale_want = 1.0
    if prefs.scale_want == 1.95:
        prefs.scale_want = 2.0
    if prefs.scale_want == 2.05:
        prefs.scale_want = 2.0

    if old != prefs.x_scale:
        print(f"Using UI scale: {prefs.scale_want}")

auto_scale()

def scale_assets(scale_want, force=force_render):
    global scaled_asset_directory
    if scale_want != 1:
        scaled_asset_directory = os.path.join(user_directory, "scaled-icons")
        if not os.path.exists(scaled_asset_directory) or len(os.listdir(svg_directory)) != len(
                os.listdir(scaled_asset_directory)):
            print("Force rerender icons")
            force_render = True
    else:
        scaled_asset_directory = asset_directory

    if scale_want != prefs.ui_scale or force:

        if scale_want != 1:
            if os.path.isdir(scaled_asset_directory) and scaled_asset_directory != asset_directory:
                shutil.rmtree(scaled_asset_directory)
            from t_modules.t_svgout import render_icons

            if scaled_asset_directory != asset_directory:
                print("Rendering icons...")
                render_icons(svg_directory, scaled_asset_directory, scale_want)

        print("Done rendering icons")

        diff_ratio = scale_want / prefs.ui_scale
        prefs.ui_scale = scale_want
        prefs.playlist_row_height = round(22 * prefs.ui_scale)

        # Save user values
        column_backup = gui.pl_st
        rspw = gui.pref_rspw
        grspw = gui.pref_gallery_w

        gui.destroy_textures()
        gui.rescale()

        # Scale saved values
        gui.pl_st = column_backup
        for item in gui.pl_st:
            item[1] = item[1] * diff_ratio
        gui.pref_rspw = rspw * diff_ratio
        gui.pref_gallery_w = grspw * diff_ratio
        global album_mode_art_size
        album_mode_art_size = int(album_mode_art_size * diff_ratio)

scale_assets(scale_want=prefs.scale_want)

try:
    # star_lines = view_prefs['star-lines']
    update_title = view_prefs['update-title']
    prefs.prefer_side = view_prefs['side-panel']
    prefs.dim_art = False #view_prefs['dim-art']
    gui.turbo = view_prefs['level-meter']
    # pl_follow = view_prefs['pl-follow']
    scroll_enable = view_prefs['scroll-enable']
    break_enable = view_prefs['break-enable']
    #dd_index = view_prefs['dd-index']
    # custom_line_mode = view_prefs['custom-line']
    #thick_lines = view_prefs['thick-lines']
    prefs.append_date = view_prefs['append-date']
except:
    print("warning: error loading settings")

if prefs.prefer_side is False:
    gui.rsp = False


def get_global_mouse():
    i_y = pointer(c_int(0))
    i_x = pointer(c_int(0))
    SDL_GetGlobalMouseState(i_x, i_y)
    return i_x.contents.value, i_y.contents.value


def get_window_position():
    i_y = pointer(c_int(0))
    i_x = pointer(c_int(0))
    SDL_GetWindowPosition(t_window, i_x, i_y)
    return i_x.contents.value, i_y.contents.value

# Access functions from libopenmpt for scanning tracker files
class MOD(Structure):
    _fields_ = [('ctl', c_char_p),
                ('value', c_char_p)]

mpt = None
try:
    p = ctypes.util.find_library("libopenmpt")
    if p:
        mpt = ctypes.cdll.LoadLibrary(p)
    else:
        mpt = ctypes.cdll.LoadLibrary("libopenmpt.so")

    mpt.openmpt_module_create_from_memory.restype = c_void_p
    mpt.openmpt_module_get_metadata.restype = c_char_p
    mpt.openmpt_module_get_duration_seconds.restype = c_double
except:
    print("Failed to load libopenmpt!")

# This function takes a track object and scans metadata for it. (Filepath needs to be set)
def tag_scan(nt):

    if nt.is_embed_cue:
        return nt

    try:
        try:
            nt.modified_time = os.path.getmtime(nt.fullpath)
            nt.found = True
        except FileNotFoundError:
            nt.found = False
            return nt

        nt.misc.clear()

        nt.file_ext = os.path.splitext(os.path.basename(nt.fullpath))[1][1:].upper()

        if nt.file_ext in ("MOD", "IT", "XM", "S3M", "MPTM") and mpt:
            f = open(nt.fullpath, "rb")
            data = f.read()
            f.close()
            MOD1 = MOD.from_address(
                mpt.openmpt_module_create_from_memory(ctypes.c_char_p(data), ctypes.c_size_t(len(data)), None, None,
                                                      None))
            nt.length = mpt.openmpt_module_get_duration_seconds(byref(MOD1))
            nt.title = mpt.openmpt_module_get_metadata(byref(MOD1), ctypes.c_char_p(b"title")).decode()
            nt.artist = mpt.openmpt_module_get_metadata(byref(MOD1), ctypes.c_char_p(b"artist")).decode()
            nt.comment = mpt.openmpt_module_get_metadata(byref(MOD1), ctypes.c_char_p(b"message_raw")).decode()

            mpt.openmpt_module_destroy(byref(MOD1))
            del MOD1

        elif nt.file_ext == "FLAC":

            audio = Flac(nt.fullpath)
            audio.read()

            nt.length = audio.length
            nt.title = audio.title
            nt.artist = audio.artist
            nt.album = audio.album
            nt.composer = audio.composer
            nt.date = audio.date
            nt.samplerate = audio.sample_rate
            nt.bit_depth = audio.bit_depth
            nt.size = os.path.getsize(nt.fullpath)
            nt.track_number = audio.track_number
            nt.genre = audio.genre
            nt.album_artist = audio.album_artist
            nt.disc_number = audio.disc_number
            nt.lyrics = audio.lyrics
            if nt.length:
                nt.bitrate = int(nt.size / nt.length * 8 / 1024)
            nt.track_total = audio.track_total
            nt.disc_total = audio.disc_total
            nt.comment = audio.comment
            nt.cue_sheet = audio.cue_sheet
            nt.misc = audio.misc

        elif nt.file_ext == "WAV":

            audio = Wav(nt.fullpath)
            audio.read()

            nt.samplerate = audio.sample_rate
            nt.length = audio.length
            nt.title = audio.title
            nt.artist = audio.artist
            nt.album = audio.album
            nt.track_number = audio.track_number

        elif nt.file_ext == "OPUS" or nt.file_ext == "OGG" or nt.file_ext == "OGA":

            # print("get opus")
            audio = Opus(nt.fullpath)
            audio.read()

            # print(audio.title)

            nt.length = audio.length
            nt.title = audio.title
            nt.artist = audio.artist
            nt.album = audio.album
            nt.composer = audio.composer
            nt.date = audio.date
            nt.samplerate = audio.sample_rate
            nt.size = os.path.getsize(nt.fullpath)
            nt.track_number = audio.track_number
            nt.genre = audio.genre
            nt.album_artist = audio.album_artist
            nt.bitrate = audio.bit_rate
            nt.lyrics = audio.lyrics
            nt.disc_number = audio.disc_number
            nt.track_total = audio.track_total
            nt.disc_total = audio.disc_total
            nt.comment = audio.comment
            nt.misc = audio.misc
            if nt.bitrate == 0 and nt.length > 0:
                nt.bitrate = int(nt.size / nt.length * 8 / 1024)

        elif nt.file_ext == "APE" or nt.file_ext == "WV" or nt.file_ext == "TTA":

            audio = Ape(nt.fullpath)
            audio.read()

            # print(audio.title)

            nt.length = audio.length
            nt.title = audio.title
            nt.artist = audio.artist
            nt.album = audio.album
            nt.date = audio.date
            nt.composer = audio.composer
            nt.samplerate = audio.sample_rate
            nt.bit_depth = audio.bit_depth
            nt.size = os.path.getsize(nt.fullpath)
            nt.track_number = audio.track_number
            nt.genre = audio.genre
            nt.album_artist = audio.album_artist
            nt.disc_number = audio.disc_number
            nt.lyrics = audio.lyrics
            if nt.length > 0:
                nt.bitrate = int(nt.size / nt.length * 8 / 1024)
            nt.track_total = audio.track_total
            nt.disc_total = audio.disc_total
            nt.comment = audio.comment
            nt.misc = audio.misc

        else:

            # Use MUTAGEN
            try:

                audio = mutagen.File(nt.fullpath)

                nt.samplerate = audio.info.sample_rate
                nt.bitrate = audio.info.bitrate // 1000
                nt.length = audio.info.length
                nt.size = os.path.getsize(nt.fullpath)

                if type(audio.tags) == mutagen.mp4.MP4Tags:
                    tags = audio.tags
                    def in_get(key, tags):
                        if key in tags:
                            return tags[key][0]
                        return ""

                    nt.title = in_get("\xa9nam", tags)
                    nt.album = in_get("\xa9alb", tags)
                    nt.artist = in_get("\xa9ART", tags)
                    nt.album_artist = in_get("aART", tags)
                    nt.composer = in_get("\xa9wrt", tags)
                    nt.date = in_get("\xa9day", tags)
                    nt.comment = in_get("\xa9cmt", tags)
                    nt.genre = in_get("\xa9gen", tags)
                    if "\xa9lyr" in tags:
                        nt.lyrics = in_get("\xa9lyr", tags)
                    nt.track_total = ""
                    nt.track_number = ""
                    t = in_get("trkn", tags)
                    if t:
                        nt.track_number = str(t[0])
                        if t[1]:
                            nt.track_total = str(t[1])

                    nt.disc_total = ""
                    nt.disc_number = ""
                    t = in_get("disk", tags)
                    if t:
                        nt.disc_number = str(t[0])
                        if t[1]:
                            nt.disc_total = str(t[1])

                    if '----:com.apple.iTunes:MusicBrainz Track Id' in tags:
                        nt.misc['musicbrainz_recordingid'] = in_get("----:com.apple.iTunes:MusicBrainz Track Id", tags).decode()
                    if '----:com.apple.iTunes:MusicBrainz Release Track Id' in tags:
                        nt.misc['musicbrainz_trackid'] = in_get("----:com.apple.iTunes:MusicBrainz Release Track Id", tags).decode()
                    if '----:com.apple.iTunes:MusicBrainz Album Id' in tags:
                        nt.misc['musicbrainz_albumid'] = in_get("----:com.apple.iTunes:MusicBrainz Album Id", tags).decode()
                    if '----:com.apple.iTunes:MusicBrainz Release Group Id' in tags:
                        nt.misc['musicbrainz_releasegroupid'] = in_get("----:com.apple.iTunes:MusicBrainz Release Group Id", tags).decode()
                    if '----:com.apple.iTunes:MusicBrainz Artist Id' in tags:
                        nt.misc['musicbrainz_artistids'] = [x.decode() for x in tags.get("----:com.apple.iTunes:MusicBrainz Artist Id")]


                elif type(audio.tags) == mutagen.id3.ID3:
                    def natural_get(tag, track, frame, attr):
                        frames = tag.getall(frame)
                        if frames and frames[0].text:
                            if track is None:
                                return str(frames[0].text[0])
                            setattr(track, attr, str(frames[0].text[0]))
                        elif track is None:
                            return ""
                        else:
                            setattr(track, attr, "")

                    tag = audio.tags

                    natural_get(audio.tags, nt, "TIT2", "title")
                    natural_get(audio.tags, nt, "TPE1", "artist")
                    natural_get(audio.tags, nt, "TPE2", "album_artist")
                    natural_get(audio.tags, nt, "TCON", "genre")  # content type
                    natural_get(audio.tags, nt, "TALB", "album")
                    natural_get(audio.tags, nt, "TDRC", "date")
                    natural_get(audio.tags, nt, "TCOM", "composer")
                    natural_get(audio.tags, nt, "COMM", "comment")

                    process_odat(nt, natural_get(audio.tags, None, "TDOR", None))

                    if len(nt.comment) > 4 and nt.comment[2] == "+":
                        nt.comment = ""
                    if nt.comment[0:3] == "000":
                        nt.comment = ""

                    frames = tag.getall("USLT")
                    if frames:
                        nt.lyrics = frames[0].text
                        if 0 < len(nt.lyrics) < 150:
                            if "unavailable" in nt.lyrics or ".com" in nt.lyrics or "www." in nt.lyrics:
                                nt.lyrics = ""

                    frames = tag.getall("TPE1")
                    if frames:
                        d = []
                        for frame in frames:
                            for t in frame.text:
                                d.append(t)
                        if len(d) > 1:
                            nt.misc['artists'] = d
                            nt.artist = "; ".join(d)

                    frames = tag.getall("TCON")
                    if frames:
                        d = []
                        for frame in frames:
                            for t in frame.text:
                                d.append(t)
                        if len(d) > 1:
                            nt.misc['genres'] = d
                        nt.genre = " / ".join(d)

                    track_no = natural_get(audio.tags, None, "TRCK", None)
                    nt.track_total = ""
                    nt.track_number = ""
                    if track_no:
                        if "/" in track_no:
                            a, b = track_no.split("/")
                            nt.track_number = a
                            nt.track_total = b
                        else:
                            nt.track_number = track_no

                    disc = natural_get(audio.tags, None, "TPOS", None)  # set ? or ?/?
                    nt.disc_total = ""
                    nt.disc_number = ""
                    if disc:
                        if "/" in disc:
                            a, b = disc.split("/")
                            nt.disc_number = a
                            nt.disc_total = b
                        else:
                            nt.disc_number = disc

                    tx = audio.tags.getall("UFID")
                    if tx:
                        for item in tx:
                            if item.owner == "http://musicbrainz.org":
                                nt.misc['musicbrainz_recordingid'] = item.data.decode()

                    tx = audio.tags.getall("TXXX")
                    if tx:
                        for item in tx:
                            if item.desc == "MusicBrainz Release Track Id":
                                nt.misc['musicbrainz_trackid'] = item.text[0]
                            if item.desc == "MusicBrainz Album Id":
                                nt.misc['musicbrainz_albumid'] = item.text[0]
                            if item.desc == "MusicBrainz Release Group Id":
                                nt.misc['musicbrainz_releasegroupid'] = item.text[0]
                            if item.desc == "MusicBrainz Artist Id":
                                nt.misc['musicbrainz_artistids'] = list(item.text)

                            try:
                                desc = item.desc.lower()
                                if desc == "replaygain_track_gain":
                                    nt.misc['replaygain_track_gain'] = float(item.text[0].strip(" dB"))
                                if desc == "replaygain_track_peak":
                                    nt.misc['replaygain_track_peak'] = float(item.text[0])
                                if desc == "replaygain_album_gain":
                                    nt.misc['replaygain_album_gain'] = float(item.text[0].strip(" dB"))
                                if desc == "replaygain_album_peak":
                                    nt.misc['replaygain_album_peak'] = float(item.text[0])
                            except:
                                print("Tag Scan: Read Replay Gain MP3 error")
                                print(nt.fullpath)

                            if item.desc == "FMPS_RATING":
                                nt.misc['FMPS_Rating'] = float(item.text[0])

            except Exception as e:
                raise
                print(e)

    except:
        print("Warning: Tag read error")
        print("     On file: " + nt.fullpath)
        return nt

    # Parse any multiple artists into list
    artists = nt.artist.split(";")
    if len(artists) > 1:
        for a in artists:
            a = a.strip()
            if a:
                if "artists" not in nt.misc:
                    nt.misc["artists"] = []
                if a not in nt.misc["artists"]:
                    nt.misc["artists"].append(a)

    return nt


def get_radio_art():

    if radiobox.loaded_url in radiobox.websocket_source_urls:
        return
    elif "ggdrasil" in radiobox.playing_title:
        time.sleep(3)
        url = "https://yggdrasilradio.net/data.php?"
        response = requests.get(url)
        if response.status_code == 200:
            lines = response.content.decode().split("|")
            if len(lines) > 11 and lines[11]:
                art_id = lines[11].strip().strip("*")
                art_url = "https://yggdrasilradio.net/images/albumart/" + art_id
                art_response = requests.get(art_url)
                if art_response.status_code == 200:
                    if pctl.radio_image_bin:
                        pctl.radio_image_bin.close()
                        pctl.radio_image_bin = None
                    pctl.radio_image_bin = io.BytesIO(art_response.content)
                    pctl.radio_image_bin.seek(0)
                    radiobox.dummy_track.art_url_key = "ok"
            pctl.update_tag_history()

    elif "gensokyoradio.net" in radiobox.loaded_url:

        response = requests.get("https://gensokyoradio.net/api/station/playing/")

        if response.status_code == 200:
            d = json.loads(response.text)
            song_info = d.get("SONGINFO")
            if song_info:
                radiobox.dummy_track.artist = song_info.get("ARTIST", "")
                radiobox.dummy_track.title = song_info.get("TITLE", "")
                radiobox.dummy_track.album = song_info.get("ALBUM", "")

            misc = d.get("MISC")
            if misc:
                art = misc.get("ALBUMART")
                if art:
                    art_url = "https://gensokyoradio.net/images/albums/500/" + art
                    art_response = requests.get(art_url)
                    if art_response.status_code == 200:
                        if pctl.radio_image_bin:
                            pctl.radio_image_bin.close()
                            pctl.radio_image_bin = None
                        pctl.radio_image_bin = io.BytesIO(art_response.content)
                        pctl.radio_image_bin.seek(0)
                        radiobox.dummy_track.art_url_key = "ok"
            pctl.update_tag_history()

    elif "radio.plaza.one" in radiobox.loaded_url:
        time.sleep(3)
        console.print("Fetching plaza art")
        response = requests.get("https://api.plaza.one/status")
        if response.status_code == 200:
            d = json.loads(response.text)
            if "song" in d:
                tr = d["song"]["length"] - d["song"]["position"]
                tr += 1
                if tr < 10:
                    tr = 10
                pctl.radio_poll_timer.force_set(tr * -1)

                if "artist" in d["song"]:
                    radiobox.dummy_track.artist = d["song"]["artist"]
                if "title" in d["song"]:
                    radiobox.dummy_track.title = d["song"]["title"]
                if "album" in d["song"]:
                    radiobox.dummy_track.album = d["song"]["album"]
                if "artwork_src" in d["song"]:
                    art_url = d["song"]["artwork_src"]
                    art_response = requests.get(art_url)
                    if art_response.status_code == 200:
                        if pctl.radio_image_bin:
                            pctl.radio_image_bin.close()
                            pctl.radio_image_bin = None
                        pctl.radio_image_bin = io.BytesIO(art_response.content)
                        pctl.radio_image_bin.seek(0)
                        radiobox.dummy_track.art_url_key = "ok"
                pctl.update_tag_history()

    # Failure
    elif pctl.radio_image_bin:
        pctl.radio_image_bin.close()
        pctl.radio_image_bin = None

    gui.clear_image_cache_next += 1

# Main class that controls playback (play, pause, stepping, playlists, queue etc). Sends commands to backend.
class PlayerCtl:
    # C-PC
    def __init__(self):

        self.running = True
        self.system = system
        self.macos = macos
        self.windows_native = windows_native
        self.install_directory = install_directory
        self.user_directory = user_directory
        self.config_directory = config_directory

        # Database

        self.master_count = master_count
        self.total_playtime = 0
        self.master_library = master_library
        self.db_inc = random.randint(0, 10000)
        #self.star_library = star_library
        self.LoadClass = LoadClass

        self.gen_codes = gen_codes

        self.shuffle_pools = {}
        self.after_import_flag = False
        self.quick_add_target = None

        # Misc player control

        self.url = ""
        # self.save_urls = url_saves
        self.tag_meta = ""
        self.found_tags = {}
        self.encoder_pause = 0

        # Playback

        self.track_queue = QUE
        self.queue_step = playing_in_queue
        self.playing_time = 0
        self.playlist_playing_position = playlist_playing  # track in playlist that is playing
        self.playlist_view_position = playlist_view_position
        self.target_open = ""
        self.target_object = None
        self.start_time = 0
        self.b_start_time = 0
        self.playerCommand = ""
        self.playerSubCommand = ""
        self.broadcastCommand = ""
        self.broadcastCommandReady = False
        self.playerCommandReady = False
        self.playing_state = 0
        self.playing_length = 0
        self.jump_time = 0
        self.random_mode = prefs.random_mode
        self.repeat_mode = prefs.repeat_mode
        self.album_repeat_mode = prefs.album_repeat_mode
        self.album_shuffle_mode = prefs.album_shuffle_mode
        self.album_shuffle_lock_mode = False
        # self.album_shuffle_pool = []
        # self.album_shuffle_id = ""
        self.last_playing_time = 0
        self.multi_playlist = multi_playlist
        self.active_playlist_viewing = playlist_active  # the playlist index that is being viewed
        self.active_playlist_playing = playlist_active  # the playlist index that is playing from
        self.force_queue = p_force_queue  # []
        self.pause_queue = False
        self.left_time = 0
        self.left_index = 0
        self.player_volume = volume
        self.new_time = 0
        self.time_to_get = []
        self.a_time = 0
        self.b_time = 0
        #self.playlist_backup = []
        self.active_replaygain = 0
        self.auto_stop = False

        # Broadcasting

        self.broadcast_active = False
        #self.join_broadcast = False
        self.broadcast_playlist = ""
        self.broadcast_position = 0
        self.broadcast_index = 0
        self.broadcast_time = 0
        self.broadcast_seek_position = 0
        self.broadcast_last_time = 0
        self.broadcast_line = ""
        self.broadcast_clients = []
        self.broadcast_update_train = []

        self.record_stream = False
        self.record_title = ""

        # Bass

        self.bass_devices = []
        self.set_device = 0

        self.gst_devices = []  # Display names
        self.gst_outputs = {}  # Display name : (sink, device)


        self.mpris = None
        self.tray_update = None
        self.eq = [0] * 2  # not used
        self.enable_eq = True  # not used

        self.playing_time_int = 0  # playing time but with no decimel

        self.windows_progress = None

        self.finish_transition = False
        # self.queue_target = 0
        self.start_time_target = 0

        self.decode_time = 0
        self.download_time = 0

        self.radio_meta_on = ""
        self.radio_meta_timer = Timer()

        self.radio_scrobble_trip = True
        self.radio_scrobble_timer = Timer()

        self.radio_image_bin = None
        self.radio_rate_timer = Timer(2)
        self.radio_poll_timer = Timer(2)

        self.volume_update_timer = Timer()
        self.wake_past_time = 0

        self.regen_in_progress = False
        self.notify_in_progress = False

        self.radio_playlists = radio_playlists
        self.radio_playlist_viewing = radio_playlist_viewing
        self.tag_history = {}

    def notify_change(self):
        self.db_inc += 1
        tauon.bg_save()

    def update_tag_history(self):
        if prefs.auto_rec:
            self.tag_history[radiobox.song_key] = {
                "title": radiobox.dummy_track.title,
                "artist": radiobox.dummy_track.artist,
                "album": radiobox.dummy_track.album,
                #"image": pctl.radio_image_bin
            }

    def radio_progress(self):
        if radiobox.loaded_url and "radio.plaza.one" in radiobox.loaded_url and self.radio_poll_timer.get() > 0:
            self.radio_poll_timer.force_set(-10)
            response = requests.get("https://api.plaza.one/status")

            if response.status_code == 200:
                d = json.loads(response.text)
                if "song" in d:
                    if "artist" in d["song"] and "title" in d["song"]:
                        self.tag_meta = d["song"]["artist"] + " - " + d["song"]["title"]

        if self.tag_meta:
            if self.radio_rate_timer.get() > 10 and self.radio_meta_on != self.tag_meta:
                self.radio_rate_timer.set()
                self.radio_scrobble_trip = False
                self.radio_meta_timer.set()
                self.radio_meta_on = self.tag_meta

                radiobox.dummy_track.art_url_key = ""
                radiobox.dummy_track.title = ""
                radiobox.dummy_track.date = ""
                radiobox.dummy_track.artist = ""
                radiobox.dummy_track.album = ""
                radiobox.dummy_track.lyrics = ""
                radiobox.dummy_track.date = ""

                tags = pctl.found_tags
                if "title" in tags:
                    radiobox.dummy_track.title = tags["title"]
                    if "artist" in tags:
                        radiobox.dummy_track.artist = tags["artist"]
                    if "year" in tags:
                        radiobox.dummy_track.date = tags["year"]
                    if "album" in tags:
                        radiobox.dummy_track.album = tags["album"]

                elif self.tag_meta.count("-") == 1 and not ":" in self.tag_meta and not "advert" in self.tag_meta.lower():
                    artist, title = self.tag_meta.split("-")
                    radiobox.dummy_track.title = title.strip()
                    radiobox.dummy_track.artist = artist.strip()

                if self.tag_meta:
                    radiobox.song_key = self.tag_meta
                else:
                    radiobox.song_key = radiobox.dummy_track.artist + " - " + radiobox.dummy_track.title

                self.update_tag_history()
                if radiobox.loaded_url not in radiobox.websocket_source_urls:
                    pctl.radio_image_bin = None
                print("NEXT RADIO TRACK")

                try:
                    get_radio_art()
                except:
                    # raise
                    print("Get art error")

                if pctl.mpris:
                    pctl.mpris.update(force=True)

                lfm_scrobbler.listen_track(radiobox.dummy_track)
                lfm_scrobbler.start_queue()

            if self.radio_scrobble_trip is False and self.radio_scrobble_timer.get() > 45:
                self.radio_scrobble_trip = True
                lfm_scrobbler.scrob_full_track(copy.deepcopy(radiobox.dummy_track))


    def update_shuffle_pool(self, pl_id, track_list):

        if pl_id in self.shuffle_pools:
            self.shuffle_pools[pl_id] += track_list

    def notify_update_fire(self):
        if self.mpris is not None:
            self.mpris.update()
        if tauon.update_play_lock is not None:
            tauon.update_play_lock()
        # if self.tray_update is not None:
        #     self.tray_update()
        self.notify_in_progress = False

    def notify_update(self):
        tauon.tray_releases += 1
        try:
            tauon.tray_lock.release()
        except:
            pass

        if self.mpris is not None:
            while self.notify_in_progress:
                time.sleep(0.01)
            self.notify_in_progress = True
            shoot = threading.Thread(target=self.notify_update_fire)
            shoot.daemon = True
            shoot.start()
        if prefs.art_bg:
            tm.ready("style")


    def get_url(self, track_object):
        if track_object.file_ext == "PLEX":
            return plex.resolve_stream(track_object.url_key), None

        if track_object.file_ext == "JELY":
            return jellyfin.resolve_stream(track_object.url_key)

        if track_object.file_ext == "KOEL":
            return koel.resolve_stream(track_object.url_key)

        if track_object.file_ext == "SUB":
            return subsonic.resolve_stream(track_object.url_key)

        return None

    def playing_playlist(self):
        return self.multi_playlist[self.active_playlist_playing][2]

    def playing_ready(self):
        return len(self.track_queue) > 0

    def selected_ready(self):
        return default_playlist and playlist_selected < len(default_playlist)

    def render_playlist(self):

        if taskbar_progress and msys and self.windows_progress:
            self.windows_progress.update(True)
        gui.pl_update = 1

    def show_selected(self):

        if gui.playlist_view_length < 1:
            return 0


        global playlist_selected
        global shift_selection

        for i in range(len(self.multi_playlist[self.active_playlist_viewing][2])):

            if i == playlist_selected:

                if i < pctl.playlist_view_position:
                    pctl.playlist_view_position = i - random.randint(2, int((gui.playlist_view_length / 3) * 2) + int(
                        gui.playlist_view_length / 6))
                    console.print("DEBUG: Position changed show selected (a)")
                elif abs(pctl.playlist_view_position - i) > gui.playlist_view_length:
                    pctl.playlist_view_position = i
                    console.print("DEBUG: Position changed show selected (b)")
                    if i > 6:
                        pctl.playlist_view_position -= 5
                        console.print("DEBUG: Position changed show selected (c)")
                    if i > gui.playlist_view_length * 1 and i + (gui.playlist_view_length * 2) < len(
                            self.multi_playlist[self.active_playlist_viewing][2]) and i > 10:
                        pctl.playlist_view_position = i - random.randint(2, int(gui.playlist_view_length / 3) * 2)
                        console.print("DEBUG: Position changed show selected (d)")
                    break

        self.render_playlist()

        return 0

    def g(self, index):

        return self.master_library[index]

    def show_object(self):  # The track to show in the metadata side panel

        target_track = None

        if self.playing_state == 3:
            return radiobox.dummy_track

        if 3 > self.playing_state > 0:
            target_track = self.playing_object()

        elif self.playing_state == 0 and prefs.meta_shows_selected:
            if -1 < playlist_selected < len(self.multi_playlist[self.active_playlist_viewing][2]):
                target_track = self.g(self.multi_playlist[self.active_playlist_viewing][2][playlist_selected])

        elif self.playing_state == 0 and prefs.meta_persists_stop:
                target_track = self.master_library[self.track_queue[self.queue_step]]

        if prefs.meta_shows_selected_always:
            if -1 < playlist_selected < len(self.multi_playlist[self.active_playlist_viewing][2]):
                target_track = self.g(self.multi_playlist[self.active_playlist_viewing][2][playlist_selected])

        return target_track

    def playing_object(self):

        if self.playing_state == 3:
            return radiobox.dummy_track

        if len(self.track_queue) > 0:
            return self.master_library[self.track_queue[self.queue_step]]
        else:
            return None

    def title_text(self):

        line = ""
        track = pctl.playing_object()
        if track:
            title = track.title
            artist = track.artist

            if not title:
                line = clean_string(track.filename)
            else:
                if artist != "":
                    line += artist
                if title != "":
                    if line != "":
                        line += "  -  "
                    line += title

            if pctl.playing_state == 3 and not title and not artist:
                return pctl.tag_meta

        return line


    def show(self):

        global playlist_selected
        global shift_selection

        if not self.track_queue:
            return 0


    def show_current(self, select=True, playing=True, quiet=False, this_only=False, highlight=False, index=None, no_switch=False, folder_list=True):

        # print("show------")
        # print(select)
        # print(playing)
        # print(quiet)
        # print(this_only)
        # print(highlight)
        # print("--------")
        console.print("DEBUG: Position set by show playing")


        global playlist_selected
        global shift_selection
      
        if spot_ctl.coasting:
            sptr = tauon.dummy_track.misc.get("spotify-track-url")
            if sptr:
                
                for p in default_playlist:
                    tr = pctl.g(p)
                    if tr.misc.get("spotify-track-url") == sptr:
                        index = tr.index
                        break
                else:
                    for i, pl in enumerate(pctl.multi_playlist):
                        for p in pl[2]:
                            tr = pctl.g(p)
                            if tr.misc.get("spotify-track-url") == sptr:
                                index = tr.index
                                switch_playlist(i)
                                break
                        else:
                            continue
                        break
                    else:
                        return

        if not self.track_queue:
            return 0

        track_index = self.track_queue[self.queue_step]
        if index is not None:
            track_index = index

        # Switch to source playlist
        if not no_switch:
            if self.active_playlist_viewing != self.active_playlist_playing and (
                        track_index not in self.multi_playlist[self.active_playlist_viewing][2]):
                switch_playlist(self.active_playlist_playing)

        if gui.playlist_view_length < 1:
            return 0

        for i in range(len(self.multi_playlist[self.active_playlist_viewing][2])):
            if self.multi_playlist[self.active_playlist_viewing][2][i] == track_index:

                if self.playlist_playing_position < len(self.multi_playlist[self.active_playlist_viewing][2]) and \
                        self.active_playlist_viewing == self.active_playlist_playing and track_index == \
                        self.multi_playlist[self.active_playlist_viewing][2][self.playlist_playing_position] and \
                        i != self.playlist_playing_position:
                    #continue
                    i = self.playlist_playing_position

                if select:
                    playlist_selected = i

                if playing:
                    # Make the found track the playing track
                    self.playlist_playing_position = i
                    self.active_playlist_playing = self.active_playlist_viewing

                vl = gui.playlist_view_length
                if pctl.multi_playlist[pctl.active_playlist_viewing][6] == gui.playlist_current_visible_tracks_id:
                    vl = gui.playlist_current_visible_tracks

                if not (quiet and self.playing_object().length < 15): #or (abs(pctl.playlist_view_position - i) < vl - 1)):

                    # Align to album if in view range (and folder titles are active)
                    ap = get_album_info(i)[1][0]

                    if not (quiet and pctl.playlist_view_position <= i <= pctl.playlist_view_position + vl) and (not abs(i - ap) > vl - 2) and not pctl.multi_playlist[pctl.active_playlist_viewing][4]:
                        pctl.playlist_view_position = ap

                    else:
                        # Move to a random offset ---

                        if i == pctl.playlist_view_position - 1 and pctl.playlist_view_position > 1:
                            pctl.playlist_view_position -= 1

                        # Move a bit if its just out of range
                        elif pctl.playlist_view_position + vl - 2 == i and i < len(
                                self.multi_playlist[self.active_playlist_viewing][2]) - 5:
                            pctl.playlist_view_position += 3

                        # We know its out of range if above view postion
                        elif i < pctl.playlist_view_position:
                            pctl.playlist_view_position = i - random.randint(2, int((gui.playlist_view_length / 3) * 2) + int(
                                gui.playlist_view_length / 6))

                        # If its below we need to test if its in view. If playing track in view, don't jump.
                        elif abs(pctl.playlist_view_position - i) >= vl:
                            pctl.playlist_view_position = i
                            if i > 6:
                                pctl.playlist_view_position -= 5
                            if i > gui.playlist_view_length and i + (gui.playlist_view_length * 2) < len(
                                    self.multi_playlist[self.active_playlist_viewing][2]) and i > 10:
                                pctl.playlist_view_position = i - random.randint(2, int(gui.playlist_view_length / 3) * 2)

                break

        else:  # Search other all other playlists
            if not this_only:
                for i, playlist in enumerate(self.multi_playlist):
                    if track_index in playlist[2]:

                        switch_playlist(i, quiet=True)
                        self.show_current(select, playing, quiet, this_only=True, index=track_index)
                        break

        if pctl.playlist_view_position < 0:
            pctl.playlist_view_position = 0

        # if pctl.playlist_view_position > len(self.multi_playlist[self.active_playlist_viewing][2]) - 1:
        #     print("Run Over")

        if select:
            shift_selection = []

        self.render_playlist()

        if album_mode and not quiet:
            if highlight:
                gui.gallery_animate_highlight_on = goto_album(playlist_selected)
                gallery_select_animate_timer.set()
            else:
                goto_album(playlist_selected)

        if prefs.left_panel_mode == "artist list" and gui.lsp and not quiet:
            artist_list_box.locate_artist(pctl.playing_object())

        if folder_list and prefs.left_panel_mode == "folder view" and gui.lsp and not quiet and not tree_view_box.lock_pl:
            tree_view_box.show_track(pctl.playing_object())

        return 0

    def set_volume(self, notify=True):

        if (spot_ctl.coasting or spot_ctl.playing) and mouse_down:
            # Rate limit network volume change
            t = self.volume_update_timer.get()
            if t < 0.3:
                return

        self.volume_update_timer.set()
        self.playerCommand = 'volume'
        self.playerCommandReady = True
        if notify:
            self.notify_update()

    def revert(self):

        if self.queue_step == 0:
            return

        prev = 0
        while len(self.track_queue) > prev + 1 and prev < 5:
            if self.track_queue[len(self.track_queue) - 1 - prev] == self.left_index:
                self.queue_step = len(self.track_queue) - 1 - prev
                self.jump_time = self.left_time
                self.playing_time = self.left_time
                self.decode_time = self.left_time
                break
            prev += 1
        else:
            self.queue_step -= 1
            self.jump_time = 0
            self.playing_time = 0
            self.decode_time = 0

        if not len(self.track_queue) > self.queue_step >= 0:
            print("ERROR - there is no previous track?")
            return

        self.target_open = pctl.master_library[self.track_queue[self.queue_step]].fullpath
        self.target_object = pctl.master_library[self.track_queue[self.queue_step]]
        self.start_time = pctl.master_library[self.track_queue[self.queue_step]].start_time
        self.start_time_target = self.start_time
        self.playing_length = pctl.master_library[self.track_queue[self.queue_step]].length
        self.playerCommand = 'open'
        self.playerCommandReady = True
        self.playing_state = 1

        if tauon.stream_proxy.download_running:
            tauon.stream_proxy.stop()

        self.show_current()
        self.render_playlist()

    def play_target_rr(self):
        tm.ready_playback()
        self.playing_length = pctl.master_library[self.track_queue[self.queue_step]].length

        if self.playing_length > 2:
            random_start = random.randrange(1, int(self.playing_length) - 45 if self.playing_length > 50 else int(
                self.playing_length))
        else:
            random_start = 0

        self.playing_time = random_start
        self.target_open = pctl.master_library[self.track_queue[self.queue_step]].fullpath
        self.target_object = pctl.master_library[self.track_queue[self.queue_step]]
        self.start_time = pctl.master_library[self.track_queue[self.queue_step]].start_time
        self.start_time_target = self.start_time
        self.jump_time = random_start
        self.playerCommand = 'open'
        if not prefs.use_jump_crossfade:
            self.playerSubCommand = 'now'
        self.playerCommandReady = True
        self.playing_state = 1

        if tauon.stream_proxy.download_running:
            tauon.stream_proxy.stop()

        if update_title:
            update_title_do()


    def play_target(self, gapless=False, jump=False):

        tm.ready_playback()

        # print(self.track_queue)
        self.playing_time = 0
        self.decode_time = 0
        target = pctl.master_library[self.track_queue[self.queue_step]]
        self.target_open = target.fullpath
        self.target_object = target
        self.start_time = target.start_time
        self.start_time_target = self.start_time
        # if not gapless:
        self.playerCommand = 'open'
        self.playing_length = target.length
        self.last_playing_time = 0

        if tauon.stream_proxy.download_running:
            tauon.stream_proxy.stop()

        if jump: # and not prefs.use_jump_crossfade:
            self.playerSubCommand = 'now'

        self.playerCommandReady = True
        # else:
        #     self.playerCommand = 'gapless'
        self.playing_state = 1

        if update_title:
            update_title_do()
        self.notify_update()
        hit_discord()

        if (album_mode or not gui.rsp) and (gui.theme_name == "Carbon" or prefs.colour_from_image):

            if prefs.colour_from_image and target.parent_folder_path == colours.last_album:
                return

            album_art_gen.display(target, (0,0), (50, 50), theme_only=True)


    def jump(self, index, pl_position=None, jump=True):

        lfm_scrobbler.start_queue()
        pctl.auto_stop = False

        if self.force_queue and not pctl.pause_queue:
            if self.force_queue[0][4] == 1:
                if pctl.g(self.force_queue[0][0]).parent_folder_path != pctl.g(index).parent_folder_path:
                    del self.force_queue[0]

        if len(self.track_queue) > 0:
            self.left_time = self.playing_time
            self.left_index = self.track_queue[self.queue_step]

            if self.playing_state == 1 and self.left_time > 5 and self.playing_length - self.left_time > 15 :
                pctl.master_library[self.left_index].skips += 1

        global playlist_hold
        gui.update_spec = 0
        self.active_playlist_playing = self.active_playlist_viewing
        self.track_queue.append(index)
        self.queue_step = len(self.track_queue) - 1
        playlist_hold = False
        self.play_target(jump=jump)

        if pl_position is not None:
            self.playlist_playing_position = pl_position

        gui.pl_update = 1

    def back(self):

        if self.playing_state < 3 and prefs.back_restarts and pctl.playing_time > 2:
            self.seek_time(0)
            self.render_playlist()
            return

        if spot_ctl.coasting:
            spot_ctl.control("previous")
            spot_ctl.update_timer.set()
            self.playing_time = -2
            self.decode_time = -2
            return

        if len(self.track_queue) > 0:
            self.left_time = self.playing_time
            self.left_index = self.track_queue[self.queue_step]

        gui.update_spec = 0
        # Move up
        if self.random_mode is False and len(self.playing_playlist()) > self.playlist_playing_position > 0:

            if len(self.track_queue) > 0 and self.playing_playlist()[self.playlist_playing_position] != self.track_queue[
                self.queue_step]:
                try:
                    self.playlist_playing_position = self.playing_playlist().index(self.track_queue[self.queue_step])
                except:
                    random_jump = random.randrange(len(self.playing_playlist()))
                    self.playlist_playing_position = random_jump

            self.playlist_playing_position -= 1
            self.track_queue.append(self.playing_playlist()[self.playlist_playing_position])
            self.queue_step = len(self.track_queue) - 1
            self.play_target(jump=True)

        elif self.random_mode is True and self.queue_step > 0:
            self.queue_step -= 1
            self.play_target(jump=True)
        else:
            print("BACK: NO CASE!")
            self.show_current()

        if self.active_playlist_viewing == self.active_playlist_playing:
            self.show_current(False, True)

        if album_mode:
            goto_album(self.playlist_playing_position)
        if gui.combo_mode and self.active_playlist_viewing == self.active_playlist_playing:
            self.show_current()

        self.render_playlist()
        self.notify_update()
        notify_song()
        lfm_scrobbler.start_queue()
        gui.pl_update += 1


    def stop(self, block=False, run=False):
        self.playerCommand = 'stop'
        if run:
            self.playerCommand = 'runstop'
        if block:
            self.playerSubCommand = "return"

        self.playerCommandReady = True

        try:
            tm.player_lock.release()
        except:
            pass

        self.record_stream = False
        if len(self.track_queue) > 0:
            self.left_time = self.playing_time
            self.left_index = self.track_queue[self.queue_step]
        previous_state = self.playing_state
        self.playing_time = 0
        self.decode_time = 0
        self.playing_state = 0
        self.render_playlist()

        gui.update_spec = 0
        # gui.update_level = True  # Allows visualiser to enter decay sequence
        gui.update = True
        if update_title:
            update_title_do()  # Update title bar text

        if block:
            loop = 0
            while self.playerSubCommand != "stopped":
                time.sleep(0.05)
                loop += 1
                if loop > 200:
                    break

        if spot_ctl.playing or spot_ctl.coasting:
            print("Spotify stop")
            spot_ctl.control("stop")

        if tauon.stream_proxy.download_running:
            tauon.stream_proxy.stop()

        self.notify_update()
        lfm_scrobbler.start_queue()
        return previous_state

    def pause(self):

        if self.playing_state == 3:
            if spot_ctl.coasting:
                if spot_ctl.paused:
                    spot_ctl.control("resume")
                else:
                    spot_ctl.control("pause")
            return

        if spot_ctl.playing:
            if self.playing_state == 2:
                spot_ctl.control("resume")
                self.playing_state = 1
            elif self.playing_state == 1:
                spot_ctl.control("pause")
                self.playing_state = 2
            self.render_playlist()
            return

        if self.playing_state == 1:
            self.playerCommand = 'pauseon'
            self.playing_state = 2
        elif self.playing_state == 2:
            self.playerCommand = 'pauseoff'
            self.playing_state = 1
            notify_song()

        self.playerCommandReady = True

        self.render_playlist()
        self.notify_update()

    def pause_only(self):
        if self.playing_state == 1:
            self.playerCommand = 'pauseon'
            self.playing_state = 2

            self.playerCommandReady = True
            self.render_playlist()
            self.notify_update()

    def play_pause(self):

        if self.playing_state > 0:
            self.pause()
        else:
            self.play()

    def seek_decimal(self, decimal):

        if self.playing_state == 1 or self.playing_state == 2 or (self.playing_state == 3 and spot_ctl.coasting):
            if decimal > 1:
                decimal = 1
            elif decimal < 0:
                decimal = 0
            self.new_time = pctl.playing_length * decimal
            # print('seek to:' + str(pctl.new_time))
            self.playerCommand = 'seek'
            self.playerCommandReady = True
            self.playing_time = self.new_time

            if msys and taskbar_progress and self.windows_progress:
                self.windows_progress.update(True)

            if self.mpris is not None:
                self.mpris.seek_do(self.playing_time)

    def seek_time(self, new):

        if self.playing_state == 1 or self.playing_state == 2 or (self.playing_state == 3 and spot_ctl.coasting):

            if new > self.playing_length - 0.5:
                self.advance()
                return

            if new < 0.4:
                new = 0

            self.new_time = new
            self.playing_time = new

            self.playerCommand = 'seek'
            self.playerCommandReady = True

            if self.mpris is not None:
                self.mpris.seek_do(self.playing_time)

    def play(self):

        if spot_ctl.playing:
            if self.playing_state == 2:
                self.play_pause()
            return

        # Unpause if paused
        if self.playing_state == 2:
            self.playerCommand = 'pauseoff'
            self.playerCommandReady = True
            self.playing_state = 1
            self.notify_update()

        # If stopped...
        elif pctl.playing_state == 0:

            # If the queue is empty
            if self.track_queue == [] and len(self.multi_playlist[self.active_playlist_playing][2]) > 0:
                self.track_queue.append(self.multi_playlist[self.active_playlist_playing][2][0])
                self.queue_step = 0
                self.playlist_playing_position = 0
                self.active_playlist_playing = 0

                self.play_target()

            # If the queue is not empty, play?
            elif len(self.track_queue) > 0:
                self.play_target()

        self.render_playlist()


    def spot_test_progress(self):
        if (self.playing_state == 1 or self.playing_state == 2) and spot_ctl.playing:
            th = 5  # the rate to poll the spotify API
            if self.playing_time > self.playing_length:
                th = 1
            if not spot_ctl.paused:
                if spot_ctl.start_timer.get() < 0.5:
                    spot_ctl.progress_timer.set()
                    return
                add_time = spot_ctl.progress_timer.get()
                if add_time > 5:
                    add_time = 0
                self.playing_time += add_time
                self.decode_time = self.playing_time
                #self.test_progress()
                spot_ctl.progress_timer.set()
                if len(self.track_queue) > 0 and 2 > add_time > 0:
                    star_store.add(self.track_queue[self.queue_step], add_time)
            if spot_ctl.update_timer.get() > th:
                spot_ctl.update_timer.set()
                spot_ctl.monitor()
            else:
                self.test_progress()

        elif self.playing_state == 3 and spot_ctl.coasting:
            th = 7
            if self.playing_time > self.playing_length or self.playing_time < 2.5:
                th = 1
            if spot_ctl.update_timer.get() < th:
                if not spot_ctl.paused:
                    self.playing_time += spot_ctl.progress_timer.get()
                    self.decode_time = self.playing_time
                spot_ctl.progress_timer.set()

            else:
                tauon.spot_ctl.update_timer.set()
                tauon.spot_ctl.update()

    def purge_track(self, track_id, fast=False):  # Remove a track from the database
        # Remove from all playlists
        if not fast:
            for playlist in self.multi_playlist:
                while track_id in playlist[2]:
                    album_dex.clear()
                    playlist[2].remove(track_id)
        # Stop if track is playing track
        if self.track_queue and self.track_queue[self.queue_step] == track_id and self.playing_state != 0:
            self.stop(block=True)
        # Remove from playback history
        while track_id in self.track_queue:
            self.track_queue.remove(track_id)
            self.queue_step -= 1
        # Remove track from force queue
        for i in reversed(range(len(self.force_queue))):
            if self.force_queue[i][0] == track_id:
                del self.force_queue[i]
        del self.master_library[track_id]

    def test_progress(self):

        global playlist_selected

        # Fuzzy reload lastfm for rescrobble
        if lfm_scrobbler.a_sc and self.playing_time < 1:
            lfm_scrobbler.a_sc = False
            self.a_time = 0

        # Update the UI if playing time changes a whole number
        # next_round = int(pctl.playing_time)
        # if self.playing_time_int != next_round:
        #     #if not prefs.power_save:
        #     #gui.update += 1
        #     self.playing_time_int = next_round

        gap_extra = 2 #2

        if spot_ctl.playing:
            gap_extra = 3

        if msys and taskbar_progress and self.windows_progress:
            self.windows_progress.update(True)

        if self.playing_state == 1 and self.decode_time + gap_extra >= self.playing_length and self.decode_time > 0.2:

            # Allow some time for spotify playing time to update?
            if spot_ctl.playing and spot_ctl.start_timer.get() < 3:
                return

            # Allow some time for backend to provide a length
            if self.playing_time < 6 and self.playing_length == 0:
                return
            if not spot_ctl.playing and pctl.a_time < 2:
                return

            self.decode_time = 0

            pp = self.playing_playlist()

            if pctl.auto_stop: # and not pctl.force_queue and not (pctl.force_queue and pctl.pause_queue):
                self.stop(run=True)
                if pctl.force_queue or (not pctl.force_queue and not pctl.random_mode and not pctl.repeat_mode):
                    self.advance(play=False)
                gui.update += 2
                pctl.auto_stop = False

            elif self.force_queue and not self.pause_queue:
                self.advance(end=True, quiet=True)

            elif self.repeat_mode is True:

                if self.album_repeat_mode:

                    if self.playlist_playing_position > len(pp) - 1:
                        self.playlist_playing_position = 0  # Hack fix, race conditon bug?

                    ti = self.g(pp[self.playlist_playing_position])

                    i = self.playlist_playing_position

                    # Test if next track is in same folder
                    if i + 1 < len(pp):
                        nt = self.g(pp[i + 1])
                        if ti.parent_folder_path == nt.parent_folder_path:
                            # The next track is in the same folder
                            # so advance normaly
                            self.advance(quiet=True, end=True)
                            return

                    # We need to backtrack to see where the folder begins
                    i -= 1
                    while i >= 0:
                        nt = self.g(pp[i])
                        if ti.parent_folder_path != nt.parent_folder_path:
                            i += 1
                            break
                        i -= 1
                    if i < 0:
                        i = 0

                    playlist_selected = i
                    shift_selection = [i]

                    self.jump(pp[i], i, jump=False)

                elif prefs.playback_follow_cursor and self.playing_ready() \
                        and self.multi_playlist[pctl.active_playlist_viewing][2][
                    playlist_selected] != self.playing_object().index \
                        and -1 < playlist_selected < len(default_playlist):

                    print("Repeat follow cursor")

                    self.playing_time = 0
                    self.decode_time = 0
                    self.active_playlist_playing = self.active_playlist_viewing
                    self.playlist_playing_position = playlist_selected

                    self.track_queue.append(default_playlist[playlist_selected])
                    self.queue_step = len(self.track_queue) - 1
                    self.play_target(jump=False)
                    self.render_playlist()
                    lfm_scrobbler.start_queue()

                else:
                    self.play_target(jump=False)
                    # self.playing_time = 0
                    # self.decode_time = 0
                    # self.new_time = 0
                    # self.playerCommand = 'seek'
                    # self.playerCommandReady = True

                    self.render_playlist()
                    lfm_scrobbler.start_queue()

                    # Reload lastfm for rescrobble
                    if lfm_scrobbler.a_sc:
                        lfm_scrobbler.a_sc = False
                        self.a_time = 0

            elif self.random_mode is False and len(pp) > self.playlist_playing_position + 1 and \
                    self.master_library[pp[self.playlist_playing_position]].is_cue is True \
                    and self.master_library[pp[self.playlist_playing_position + 1]].filename == \
                    self.master_library[pp[self.playlist_playing_position]].filename and int(
                self.master_library[pp[self.playlist_playing_position]].track_number) == int(
                self.master_library[pp[self.playlist_playing_position + 1]].track_number) - 1:

                #  not (self.force_queue and not self.pause_queue) and \

                # We can shave it closer
                if not self.playing_time + 0.1 >= self.playing_length:
                    return

                print("Do transition CUE")
                self.playlist_playing_position += 1
                self.queue_step += 1
                self.track_queue.append(pp[self.playlist_playing_position])
                self.playing_state = 1
                self.playing_time = 0
                self.decode_time = 0
                self.playing_length = self.master_library[self.track_queue[self.queue_step]].length
                self.start_time = self.master_library[self.track_queue[self.queue_step]].start_time
                self.start_time_target = self.start_time
                lfm_scrobbler.start_queue()

                gui.update += 1
                gui.pl_update = 1

                if update_title:
                    update_title_do()
                self.notify_update()
            else:
                self.advance(quiet=True, end=True)

                self.playing_time = 0
                self.decode_time = 0

    def advance_broadcast(self, start=False):

        pl_id = id_to_pl(pctl.broadcast_playlist)
        if pl_id is None or not pctl.multi_playlist[pl_id][2]:
            pctl.broadcastCommand = "encstop"
            pctl.broadcastCommandReady = True
            show_message("Broadcast stopped", "The broadcasting playlist no longer has any tracks")
            return

        pctl.broadcast_position += 1
        playlist = pctl.multi_playlist[pl_id][2]

        if pctl.broadcast_position > len(playlist) - 1:
            pctl.broadcast_position = 0

        for p in range(len(playlist)):

            pp = (p + pctl.broadcast_position) % len(playlist)

            track = self.g(playlist[pp])
            found = pp

            if track.is_network:
                show_message("Broadcast of network tracks not currently supported", mode="error")
                continue

            track.found = os.path.isfile(track.fullpath)
            if track.found:
                break

        else:
            if start:
                show_message("No tracks in this playlist can be broadcasted")
            else:
                show_message("Broadcast stopped", "No tracks in the playlist could be streamed", mode="warning")
                pctl.broadcastCommand = "encstop"
                pctl.broadcastCommandReady = True
            return

        pctl.broadcast_position = found
        pctl.broadcast_index = track.index
        pctl.broadcast_time = 0

        pctl.target_open = track.fullpath
        pctl.b_start_time = track.start_time
        pctl.broadcast_line = track.artist + " - " + track.title

        if start:
            pctl.broadcast_update_train.clear()
            pctl.broadcastCommand = "encstart"
            pctl.broadcastCommandReady = True
        else:
            pctl.broadcastCommand = "cast-next"
            pctl.broadcastCommandReady = True

        gui.pl_update += 1
        tm.ready("caster")

    def advance(self, rr=False, quiet=False, inplace=False, end=False, force=False, play=True, dry=False):

        # Spotify remote control mode
        if not dry:
            if spot_ctl.coasting:
                spot_ctl.control("next")
                spot_ctl.update_timer.set()
                self.playing_time = -2
                self.decode_time = -2
                return

        # Temporary Workaround for UI block causing unwanted dragging
        if not dry:
            quick_d_timer.set()

        if prefs.show_current_on_transition:
            quiet = False

        # Trim the history if it gets too long
        while len(self.track_queue) > 250:
            self.queue_step -= 1
            del self.track_queue[0]

        # Save info about the track we are leaving
        if not dry:
            if len(self.track_queue) > 0:
                self.left_time = self.playing_time
                self.left_index = self.track_queue[self.queue_step]

        # Test to register skip (not currently used for anything)
        if not dry:
            if self.playing_state == 1 and 1 < self.left_time < 45:
                pctl.master_library[self.left_index].skips += 1
                # print('skip registered')

        if not dry:
            pctl.playing_time = 0
            pctl.decode_time = 0
            pctl.playing_length = 100
            gui.update_spec = 0

        old = self.queue_step
        end_of_playlist = False

        # Force queue (middle click on track)
        if len(self.force_queue) > 0 and not self.pause_queue:

            q = self.force_queue[0]
            target_index = q[0]

            if q[3] == 1:
                # This is an album type

                if q[4] == 0:
                    # We have not started playing the album yet
                    # So we go to that track
                    # (This is a copy of the track code, but we don't delete the item)

                    if not dry:

                        pl = id_to_pl(q[2])
                        if pl is not None:
                            self.active_playlist_playing = pl

                        if target_index not in self.playing_playlist():
                            del self.force_queue[0]
                            self.advance()
                            return

                    if dry:
                        return target_index

                    self.playlist_playing_position = q[1]
                    self.track_queue.append(target_index)
                    self.queue_step = len(self.track_queue) - 1
                    #self.queue_target = len(self.track_queue) - 1
                    if play:
                        self.play_target(jump=not end)

                    #  Set the flag that we have entered the album
                    self.force_queue[0][4] = 1

                    # This code is mirrored below -------
                    ok_continue = True

                    # Check if we are at end of playlist
                    pl = pctl.multi_playlist[pctl.active_playlist_playing][2]
                    if self.playlist_playing_position > len(pl) - 3:
                        ok_continue = False

                    # Check next song is in album
                    if ok_continue:
                        if self.g(pl[self.playlist_playing_position + 1]).parent_folder_path != pctl.g(target_index).parent_folder_path:
                            ok_continue = False

                    # -----------


                elif q[4] == 1:
                    # We have previously started playing this album

                    # Check to see if we still are:
                    ok_continue = True

                    if pctl.g(target_index).parent_folder_path != pctl.playing_object().parent_folder_path:
                        # Remember to set jumper check this too (leave album if we jump to some other track, i.e. double click))
                        ok_continue = False

                    pl = pctl.multi_playlist[pctl.active_playlist_playing][2]

                    # Check next song is in album
                    if ok_continue:

                        # Check if we are at end of playlist, or already at end of album
                        if self.playlist_playing_position >= len(pl) - 1 or self.playlist_playing_position < len(pl) - 1 and \
                                 self.g(pl[self.playlist_playing_position + 1]).parent_folder_path != pctl.g(target_index).parent_folder_path:

                            if dry:
                                return None

                            del self.force_queue[0]
                            self.advance()
                            return


                        # Check if 2 songs down is in album, remove entry in queue if not
                        elif self.playlist_playing_position < len(pl) - 2 and \
                                self.g(pl[self.playlist_playing_position + 2]).parent_folder_path != pctl.g(target_index).parent_folder_path:
                            ok_continue = False

                    #if ok_continue:
                    # We seem to be still in the album. Step down one and play
                    if not dry:
                        self.playlist_playing_position += 1

                    if len(pl) <= self.playlist_playing_position:
                        if dry:
                            return None
                        print("END OF PLAYLIST!")
                        del self.force_queue[0]
                        self.advance()
                        return

                    if dry:
                        return pl[self.playlist_playing_position]
                    self.track_queue.append(pl[self.playlist_playing_position])
                    self.queue_step = len(self.track_queue) - 1
                    #self.queue_target = len(self.track_queue) - 1
                    if play:
                        self.play_target(jump=not end)

                if not ok_continue:
                    # It seems this item has expired, remove it and call advance again

                    if dry:
                        return None

                    print("Remove expired album from queue")
                    del self.force_queue[0]

                    if q[6]:
                        pctl.auto_stop = True
                    if prefs.stop_end_queue and not self.force_queue:
                        pctl.auto_stop = True

                    if queue_box.scroll_position > 0:
                        queue_box.scroll_position -= 1

                        #self.advance()
                        #return

            else:
                # This is track type
                pl = id_to_pl(q[2])
                if not dry:
                    if pl is not None:
                        self.active_playlist_playing = pl

                if target_index not in self.playing_playlist():
                    if dry:
                        return None
                    del self.force_queue[0]
                    self.advance()
                    return

                if dry:
                    return target_index

                self.playlist_playing_position = q[1]
                self.track_queue.append(target_index)
                self.queue_step = len(self.track_queue) - 1
                #self.queue_target = len(self.track_queue) - 1
                if play:
                    self.play_target(jump= not end)
                del self.force_queue[0]
                if q[6]:
                    pctl.auto_stop = True
                if prefs.stop_end_queue and not self.force_queue:
                    pctl.auto_stop = True
                if queue_box.scroll_position > 0:
                    queue_box.scroll_position -= 1

        # Stop if playlist is empty
        elif len(self.playing_playlist()) == 0:
            if dry:
                return None
            self.stop()
            return 0

        # Playback follow cursor
        elif prefs.playback_follow_cursor and self.playing_ready() \
                and self.multi_playlist[pctl.active_playlist_viewing][2][playlist_selected] != self.playing_object().index \
                and -1 < playlist_selected < len(default_playlist):

            if dry:
                return default_playlist[playlist_selected]

            self.active_playlist_playing = self.active_playlist_viewing
            self.playlist_playing_position = playlist_selected

            self.track_queue.append(default_playlist[playlist_selected])
            self.queue_step = len(self.track_queue) - 1
            if play:
                self.play_target(jump=not end)

        # If random, jump to random track
        elif (self.random_mode or rr) and len(self.playing_playlist()) > 0 and not (self.album_shuffle_mode or self.album_shuffle_lock_mode):
            #self.queue_step += 1
            new_step = self.queue_step + 1

            if new_step == len(self.track_queue):

                if self.album_repeat_mode and self.repeat_mode:
                    # Album shuffle mode
                    pp = self.playing_playlist()
                    k = self.playlist_playing_position
                    #ti = self.g(pp[k])
                    ti = self.master_library[self.track_queue[self.queue_step]]

                    if ti.index not in pp:
                        print("No tracks to repeat!")
                        return 0


                    matches = []
                    for i, p in enumerate(pp):

                        if self.g(p).parent_folder_path == ti.parent_folder_path:
                            matches.append((i, p))

                    if matches:
                        # Avoid a repeat of same track
                        if len(matches) > 1 and (k, ti.index) in matches:
                            matches.remove((k, ti.index))

                        i, p = random.choice(matches)

                        if prefs.true_shuffle:

                            id = ti.parent_folder_path

                            while True:
                                if id in pctl.shuffle_pools:

                                     pool = pctl.shuffle_pools[id]

                                     if not pool:
                                         del pctl.shuffle_pools[id]  # Trigger a refill
                                         continue

                                     ref = random.choice(pool)
                                     pool.remove(ref)

                                     if ref[1] not in pp:  # Check track still in the live playlist
                                         print("Track not in pool")
                                         continue

                                     i, p = ref  # Find position of reference in playlist
                                     break

                                else:
                                     # Refill the pool
                                     pctl.shuffle_pools[id] = matches
                                     print("Refill folder shuffle pool")


                        self.playlist_playing_position = i
                        self.track_queue.append(p)

                else:
                    # Normal select from playlist

                    if prefs.true_shuffle:
                        # True shuffle avoides repeats by using a pool

                        pl = pctl.multi_playlist[pctl.active_playlist_playing]
                        id = pl[6]

                        while True:

                            if id in pctl.shuffle_pools:

                                pool = pctl.shuffle_pools[id]

                                if not pool:
                                    del pctl.shuffle_pools[id]  # Trigger a refill
                                    continue

                                ref = random.choice(pool)
                                pool.remove(ref)

                                if ref not in pl[2]:  # Check track still in the live playlist
                                    continue

                                random_jump = pl[2].index(ref)  # Find position of reference in playlist
                                break

                            else:
                                # Refill the pool
                                pctl.shuffle_pools[id] = copy.deepcopy(pl[2])
                                print("Refill shuffle pool")

                    else:
                        random_jump = random.randrange(len(self.playing_playlist()))
                    self.playlist_playing_position = random_jump
                    self.track_queue.append(self.playing_playlist()[random_jump])


            if inplace and self.queue_step > 1:
                del self.track_queue[self.queue_step]
            else:
                self.queue_step = new_step


            if rr:
                self.play_target_rr()
            else:
                if play:
                    self.play_target(jump=not end)


        # If not random mode, Step down 1 on the playlist
        elif self.random_mode is False and len(self.playing_playlist()) > 0:

            # Stop at end of playlist
            if self.playlist_playing_position == len(self.playing_playlist()) - 1:
                if prefs.end_setting == 'stop':
                    self.playing_state = 0
                    self.playerCommand = 'runstop'
                    self.playerCommandReady = True
                    end_of_playlist = True

                elif prefs.end_setting == 'advance' or prefs.end_setting == 'cycle':

                    # If at end playlist and not cycle mode, stop playback
                    if pctl.active_playlist_playing == len(pctl.multi_playlist) - 1 and not prefs.end_setting == 'cycle':
                        self.playing_state = 0
                        self.playerCommand = 'runstop'
                        self.playerCommandReady = True
                        end_of_playlist = True

                    else:

                        p = pctl.active_playlist_playing
                        for i in range(len(pctl.multi_playlist)):

                            k = (p + i + 1) %  len(pctl.multi_playlist)

                            # Skip a playlist if empty
                            if not (pctl.multi_playlist[k][2]):
                                continue

                            # Skip a playlist if hidden
                            if pctl.multi_playlist[k][8] and prefs.tabs_on_top:
                                continue

                            # Set found playlist as playing the first track
                            pctl.active_playlist_playing = k
                            pctl.playlist_playing_position = -1
                            pctl.advance(end=end, force=True, play=play)
                            break

                        else:
                            # Restart current if no other eligible playlist found
                            pctl.playlist_playing_position = -1
                            pctl.advance(end=end, force=True, play=play)

                        return

                elif prefs.end_setting == 'repeat':
                    pctl.playlist_playing_position = -1
                    pctl.advance(end=end, force=True, play=play)
                    return

                gui.update += 3

            else:
                if self.playlist_playing_position > len(self.playing_playlist()) - 1:
                    self.playlist_playing_position = 0

                elif not force and len(self.track_queue) > 0 and self.playing_playlist()[self.playlist_playing_position] != self.track_queue[
                    self.queue_step]:
                    try:
                        self.playlist_playing_position = self.playing_playlist().index(self.track_queue[self.queue_step])
                    except:
                        pass

                if len(self.playing_playlist()) == self.playlist_playing_position + 1:
                    return

                self.playlist_playing_position += 1
                self.track_queue.append(self.playing_playlist()[self.playlist_playing_position])

                # print("standand advance")
                #self.queue_target = len(self.track_queue) - 1
                # if end:
                #     self.play_target_gapless(jump= not end)
                # else:
                self.queue_step = len(self.track_queue) - 1
                if play:
                    self.play_target(jump= not end)

        else:

            if self.random_mode and (self.album_shuffle_mode or self.album_shuffle_lock_mode):

                # Album shuffle mode
                print("Album shuffle mode")

                po = self.playing_object()

                redraw = False

                # Checks
                if po is not None and len(self.playing_playlist()) > 0:

                    # If we at end of playlist, we'll go to a new album
                    if len(self.playing_playlist()) == self.playlist_playing_position + 1:
                        redraw = True
                    # If the next track is a new album, go to a new album
                    elif po.parent_folder_path != pctl.g(self.playing_playlist()[self.playlist_playing_position + 1]).parent_folder_path:
                        redraw = True
                    # Always redraw on press in album shuffle lockdown
                    if self.album_shuffle_lock_mode and not end:
                        redraw = True

                    if not redraw:
                        self.playlist_playing_position += 1
                        self.track_queue.append(self.playing_playlist()[self.playlist_playing_position])
                        self.queue_step = len(self.track_queue) - 1
                        #self.queue_target = len(self.track_queue) - 1
                        if play:
                            self.play_target(jump=not end)

                    else:

                        albums = []
                        current_folder = ""
                        for i in range(len(self.playing_playlist())):
                            if i == 0:
                                albums.append(i)
                                current_folder = self.master_library[self.playing_playlist()[i]].parent_folder_path
                            else:
                                if pctl.master_library[self.playing_playlist()[i]].parent_folder_path != current_folder:
                                    current_folder = self.master_library[self.playing_playlist()[i]].parent_folder_path
                                    albums.append(i)

                        random.shuffle(albums)

                        for a in albums:

                            if self.g(self.playing_playlist()[a]).parent_folder_path != self.playing_object().parent_folder_path:
                                self.playlist_playing_position = a
                                self.track_queue.append(self.playing_playlist()[a])
                                self.queue_step = len(self.track_queue) - 1
                                #self.queue_target = len(self.track_queue) - 1
                                if play:
                                    self.play_target(jump=not end)
                                break
                            else:
                                a = 0
                                self.playlist_playing_position = a
                                self.track_queue.append(self.playing_playlist()[a])
                                self.queue_step = len(self.track_queue) - 1
                                if play:
                                    self.play_target(jump=not end)
                                # print("THERS ONLY ONE ALBUM IN THE PLAYLIST")
                                # self.stop()

            else:
                print("ADVANCE ERROR - NO CASE!")

        if dry:
            return None

        if self.active_playlist_viewing == self.active_playlist_playing:
            self.show_current(quiet=quiet)
        elif prefs.auto_goto_playing:
            self.show_current(quiet=quiet, this_only=True, playing=False, highlight=True, no_switch=True)

        # if album_mode:
        #     goto_album(self.playlist_playing)

        self.render_playlist()

        if spot_ctl.playing and end_of_playlist:
            spot_ctl.control("stop")

        self.notify_update()
        lfm_scrobbler.start_queue()
        if play:
            notify_song(end_of_playlist, delay=1.3)

    def reset_missing_flags(self):
        for value in self.master_library.values():
            value.found = True
        gui.pl_update += 1

pctl = PlayerCtl()

notify_change = pctl.notify_change

def auto_name_pl(target_pl):

    if not pctl.multi_playlist[target_pl][2]:
        return

    albums = []
    artists = []
    parents = []

    track = None

    for index in pctl.multi_playlist[target_pl][2]:
        track = pctl.g(index)
        albums.append(track.album)
        if track.album_artist:
            artists.append(track.album_artist)
        else:
            artists.append(track.artist)
        parents.append(track.parent_folder_path)

    nt = ""
    artist = ""

    if track:
        artist = track.artist
        if track.album_artist:
            artist = track.album_artist

    if track and albums and albums[0] and albums.count(albums[0]) == len(albums):
        nt = artist + " - " + track.album

    elif track and artists and artists[0] and artists.count(artists[0]) == len(artists):
        nt = artists[0]

    else:
        nt = os.path.basename(commonprefix(parents))

    pctl.multi_playlist[target_pl][0] = nt



def get_object(index):
    return pctl.master_library[index]

def update_title_do():

    if pctl.playing_state > 0:
        if len(pctl.track_queue) > 0:
            line = pctl.master_library[pctl.track_queue[pctl.queue_step]].artist + " - " + \
                   pctl.master_library[pctl.track_queue[pctl.queue_step]].title
            #line += "   : :   Tauon Music Box"
            line = line.encode('utf-8')
            SDL_SetWindowTitle(t_window, line)
    else:
        line = "Tauon Music Box"
        line = line.encode('utf-8')
        SDL_SetWindowTitle(t_window, line)


def open_encode_out():
    if system == 'windows' or msys:
        line = r'explorer ' + prefs.encoder_output.replace("/", "\\")
        subprocess.Popen(line)
    else:
        line = prefs.encoder_output
        line += "/"
        if macos:
            subprocess.Popen(['open', line])
        else:
            subprocess.Popen(['xdg-open', line])

def g_open_encode_out(a, b, c):
    open_encode_out()
#

if system == 'linux' and not macos and not msys:

    try:
        Notify.init("Tauon Music Box")
        g_tc_notify = Notify.Notification.new("Tauon Music Box",
                                              "Transcoding has finished.")
        value = GLib.Variant("s", t_id)
        g_tc_notify.set_hint("desktop-entry", value)

        g_tc_notify.add_action(
            "action_click",
            "Open Output Folder",
            g_open_encode_out,
            None
        )

        de_notify_support = True

    except:
        print("Failed init notifications")


    if de_notify_support:
        song_notification = Notify.Notification.new("Next track notification")
        value = GLib.Variant("s", t_id)
        song_notification.set_hint("desktop-entry", value)



def notify_song_fire(notification, delay, id):

    time.sleep(delay)
    notification.show()

    if id is None:
        return

    time.sleep(15)
    if id == gui.notify_main_id:
        notification.close()


def notify_song(notify_of_end=False, delay=0.0):

    if not de_notify_support:
        return

    if notify_of_end and prefs.end_setting != "stop":
        return

    if prefs.show_notifications and pctl.playing_object() is not None and not window_is_focused():

        if prefs.stop_notifications_mini_mode and gui.mode == 3:
            return

        track = pctl.playing_object()

        if not track or not (track.title or track.artist or track.album):
            return  # only display if we have at least one piece of metadata avaliable

        i_path = ""
        try:
            if not notify_of_end:
                i_path = thumb_tracks.path(track)
        except:
            print(track.fullpath)
            print("Thumbnail error")

        top_line = track.title
        if not top_line:
            top_line = clean_string(track.filename)

        if prefs.notify_include_album:
            bottom_line = (track.artist + " | " + track.album).strip("| ")
        else:
            bottom_line = track.artist

        gui.notify_main_id = uid_gen()
        id = gui.notify_main_id

        if notify_of_end:
            bottom_line = "Tauon Music Box"
            top_line = _("End of playlist")

            song_notification.update(top_line, bottom_line, i_path)
            id = None

        elif track.album:
            song_notification.update(top_line, bottom_line, i_path)

        else:
            song_notification.update(bottom_line, "", i_path)

        shoot_dl = threading.Thread(target=notify_song_fire, args=([song_notification, delay, id]))
        shoot_dl.daemon = True
        shoot_dl.start()


# Last.FM -----------------------------------------------------------------
class LastFMapi:

    API_SECRET = "6e433964d3ff5e817b7724d16a9cf0cc"
    connected = False
    API_KEY = "bfdaf6357f1dddd494e5bee1afe38254"
    scanning_username = ""

    network = None
    lastfm_network = None
    tries = 0

    scanning_friends = False
    scanning_loves = False
    scanning_scrobbles = False

    def __init__(self):
        self.sg = None
        self.url = None

    def get_network(self):
        if prefs.use_libre_fm:
            return pylast.LibreFMNetwork
        else:
            return pylast.LastFMNetwork

    def auth1(self):

        # This is step one where the user clicks "login"

        if self.network is None:
            self.no_user_connect()

        self.sg = pylast.SessionKeyGenerator(self.network)
        self.url = self.sg.get_web_auth_url()
        show_message(_("Web auth page opened"), _("Once authorised click the 'done' button."), mode='arrow')
        webbrowser.open(self.url, new=2, autoraise=True)

    def auth2(self):

        # This is step 2 where the user clicks "Done"

        if self.sg is None:
            show_message(_("You need to log in first"))
            return

        try:
            # session_key = self.sg.get_web_auth_session_key(self.url)
            session_key, username = self.sg.get_web_auth_session_key_username(self.url)
            prefs.last_fm_token = session_key
            self.network = self.get_network()(api_key=self.API_KEY, api_secret=
                self.API_SECRET, session_key=prefs.last_fm_token)
            # user = self.network.get_authenticated_user()
            # username = user.get_name()
            prefs.last_fm_username = username

        except Exception as e:
            if 'Unauthorized Token' in str(e):
                show_message("Error - Not authorized", mode='error')
            else:
                show_message("Error", 'Unknown error.', mode='error')

        if not toggle_lfm_auto(mode=1):
            toggle_lfm_auto()

    def auth3(self):

        # This is used for "logout"

        prefs.last_fm_token = None
        prefs.last_fm_username = ""
        show_message("Logout will complete on app restart.")

    def connect(self, m_notify=True):

        if not last_fm_enable:
            return False

        if self.connected is True:
            if m_notify:
                show_message("Already connected to Last.fm")
            return True

        if prefs.last_fm_token is None:
            show_message("No Last.Fm account registered", "Authorise an account in settings", mode='info')
            return

        print('Attempting to connect to Last.fm network')

        try:

            self.network = self.get_network()(api_key=self.API_KEY, api_secret=
                self.API_SECRET, session_key=prefs.last_fm_token) #, username=lfm_username, password_hash=lfm_hash)

            self.connected = True
            if m_notify:
                show_message("Connection to Last.fm was successful.", mode='done')

            print('Connection to lastfm appears successful')
            return True

        except Exception as e:
            show_message("Error connecting to Last.fm network", str(e), mode='warning')
            # print(e)
            return False

    def toggle(self):
        prefs.scrobble_hold ^= True

    def details_ready(self):
        if prefs.last_fm_token:
            return True
        else:
            return False

    def last_fm_only_connect(self):

        try:
            self.lastfm_network = pylast.LastFMNetwork(api_key=self.API_KEY, api_secret=self.API_SECRET)
            print('Connection appears successful')
            return True

        except Exception as e:
            show_message("Error communicating with Last.fm network", str(e), mode='warning')
            print(e)
            return False

    def no_user_connect(self):

        try:
            self.network = self.get_network()(api_key=self.API_KEY, api_secret=self.API_SECRET)
            print('Connection appears successful')
            return True

        except Exception as e:
            show_message("Error communicating with Last.fm network", str(e), mode='warning')
            print(e)
            return False

    def get_all_scrobbles_estimate_time(self):

        if not self.connected:
            self.connect(False)
        if not self.connected or not prefs.last_fm_username:
            return

        user = pylast.User(prefs.last_fm_username, self.network)
        total = user.get_playcount()

        if total:
            return 0.04364 * total
        return 0


    def get_all_scrobbles(self):

        if not self.connected:
            self.connect(False)
        if not self.connected or not prefs.last_fm_username:
            return

        try:
            self.scanning_scrobbles = True
            self.network.enable_rate_limit()
            user = pylast.User(prefs.last_fm_username, self.network)
            # username = user.get_name()
            perf_timer.set()
            tracks = user.get_recent_tracks(None)

            counts = {}

            # Count up the unique pairs
            for track in tracks:
                key = (str(track.track.artist), str(track.track.title))
                c = counts.get(key, 0)
                counts[key] = c + 1

            touched = []

            # Add counts to matching tracks
            for key, value in counts.items():
                artist, title = key
                artist = artist.lower()
                title = title.lower()

                for track in pctl.master_library.values():
                    t_artist = track.artist.lower()
                    artists = [x.lower() for x in get_split_artists(track)]
                    if t_artist == artist or artist in artists or (track.album_artist and track.album_artist.lower() == artist):
                        if track.title.lower() == title:
                            if track.index in touched:
                                track.lfm_scrobbles += value
                            else:
                                track.lfm_scrobbles = value
                                touched.append(track.index)
        except:
            gui.pl_update += 1
            #raise
            self.scanning_scrobbles = False
            show_message(_("Scanning failed. Try again?"), mode="error")
            return

        print(perf_timer.get())
        gui.pl_update += 1
        self.scanning_scrobbles = False
        tauon.bg_save()
        show_message(_("Scanning scrobbles complete"), mode="done")

    def artist_info(self, artist):

        if self.lastfm_network is None:
            if self.last_fm_only_connect() is False:
                return False, "", ""

        try:
            if artist != "":
                l_artist = pylast.Artist(artist.replace("/", "").replace("\\", "").replace(" & ", " and ").replace("&", " "), self.lastfm_network)
                bio = l_artist.get_bio_content()
                #cover_link = l_artist.get_cover_image()
                mbid = l_artist.get_mbid()

                return True, bio, "", mbid
        except:
            print("last.fm get artist info failed")

        return False, "", "", ""

    def artist_mbid(self, artist):

        if self.lastfm_network is None:
            if self.last_fm_only_connect() is False:
                return ""

        try:
            if artist != "":
                l_artist = pylast.Artist(artist.replace("/", "").replace("\\", "").replace(" & ", " and ").replace("&", " "), self.lastfm_network)
                mbid = l_artist.get_mbid()
                return mbid
        except:
            print("last.fm get artist mbid info failed")

        return ""

    def scrobble(self, track_object, timestamp=None):
        if prefs.scrobble_hold:
            return True
        if prefs.auto_lfm:
            self.connect(False)

        if timestamp is None:
            timestamp = int(time.time())

        # lastfm_user = self.network.get_user(self.username)

        title = track_object.title
        album = track_object.album
        artist = get_artist_strip_feat(track_object)
        album_artist = track_object.album_artist

        print("submitting scrobble...")

        # Act
        try:
            if title != "" and artist != "":
                if album != "":
                    if album_artist and album_artist != artist:
                        self.network.scrobble(artist=artist, title=title, album=album, album_artist=album_artist, timestamp=timestamp)
                    else:
                        self.network.scrobble(artist=artist, title=title, album=album, timestamp=timestamp)
                else:
                    self.network.scrobble(artist=artist, title=title, timestamp=timestamp)
                # print('Scrobbled')
            else:
                print("Not sent, incomplete metadata")

        except Exception as e:

            if 'retry' in str(e):
                print("Retrying...")
                time.sleep(7)

                try:
                    self.network.scrobble(artist=artist, title=title, timestamp=timestamp)
                    # print('Scrobbled')
                    return True
                except:
                    pass

            #show_message("Error: Could not scrobble. ", str(e), mode='warning')
            console.print("Error connecting to last.fm", level=5)
            console.print("-- " + str(e), level=5)
            scrobble_warning_timer.set()
            gui.update += 1
            gui.delay_frame(5)

            # print(e)
            return False
        return True

    def get_bio(self, artist):
        #if self.connected:
        if self.lastfm_network is None:
            if self.last_fm_only_connect() is False:
                return ""

        artist_object = pylast.Artist(artist, self.lastfm_network)
        bio = artist_object.get_bio_summary(language="en")
        # print(artist_object.get_cover_image())
        # print("\n\n")
        # print(bio)
        # print("\n\n")
        # print(artist_object.get_bio_content())
        return bio
        #else:
        #    return ""

    def love(self, artist, title):
        if not self.connected and prefs.auto_lfm:
            self.connect(False)
            prefs.scrobble_hold = True
        if self.connected and artist != "" and title != "":
            track = self.network.get_track(artist, title)
            track.love()

    def unlove(self, artist, title):
        if not self.connected and prefs.auto_lfm:
            self.connect(False)
            prefs.scrobble_hold = True
        if self.connected and artist != "" and title != "":
            track = self.network.get_track(artist, title)
            track.love()
            track.unlove()

    def clear_friends_love(self):

        count = 0
        for index, tr in pctl.master_library.items():
            count += len(tr.lfm_friend_likes)
            tr.lfm_friend_likes.clear()

        show_message("Removed {} loves.".format(count))

    def get_friends_love(self):

        self.scanning_friends = True

        try:
            username = prefs.last_fm_username

            if not username:
                return

            if self.network is None:
                self.no_user_connect()

            self.network.enable_rate_limit()
            lastfm_user = self.network.get_user(username)
            friends = lastfm_user.get_friends(limit=None)
            show_message(_("Getting friend data..."), _("This may take a very long time."), mode='info')
            for friend in friends:
                self.scanning_username = friend.name
                print("Getting friend loves: " + friend.name)

                try:
                    loves = friend.get_loved_tracks(limit=None)
                except:
                    continue

                for track in loves:
                    title = track.track.title.casefold()
                    artist = track.track.artist.name.casefold()
                    for index, tr in pctl.master_library.items():

                        if tr.title.casefold() == title and tr.artist.casefold() == artist:
                            tr.lfm_friend_likes.add(friend.name)
                            print("MATCH")
                            print("     " + artist + " - " + title)
                            print("      ----- " + friend.name)

        except:
            show_message("There was an error getting friends loves", "", mode='warning')

        self.scanning_friends = False

    def dl_love(self):

        username = prefs.last_fm_username
        show_message(_("Scanning loved tracks for: %s" % username), mode="info")
        self.scanning_username = username

        if not username:
            show_message("No username found", mode='error')
            return

        if len(username) > 25:
            return

        self.scanning_loves = True

        try:
            if self.network is None:
                self.no_user_connect()

            self.network.enable_rate_limit()

            lastfm_user = self.network.get_user(username)
            tracks = lastfm_user.get_loved_tracks(limit=None)

            matches = 0
            updated = 0

            for track in tracks:
                title = track.track.title.casefold()
                artist = track.track.artist.name.casefold()

                for index, tr in pctl.master_library.items():
                    if tr.title.casefold() == title and tr.artist.casefold() == artist:
                        matches += 1
                        print("MATCH:")
                        print("     " + artist + " - " + title)
                        star = star_store.full_get(index)
                        if star is None:
                            star = star_store.new_object()
                        if "L" not in star[1]:
                            updated += 1
                            print("     NEW LOVE")
                            # star = [star[0], star[1] + "L"]
                            star[1] = star[1] + "L"

                        star_store.insert(index, star)

            self.scanning_loves = False
            if len(tracks) == 0:
                show_message("User has no loved tracks.")
                return
            if matches > 0 and updated == 0:
                show_message(str(matches) + " matched tracks are up to date.")
                return
            if matches > 0 and updated > 0:
                show_message(str(matches) + " tracks matched. " + str(updated) + " were updated.")
                return
            else:
                show_message("Of " + str(len(tracks)) + " loved tracks, no matches were found in local db")
                return
        except:
            show_message("This doesn't seem to be working :(", mode='error')
        self.scanning_loves = False

    def update(self, track_object):
        if prefs.scrobble_hold:
            return 0
        if prefs.auto_lfm:
            if self.connect(False) is False:
                prefs.auto_lfm = False
        else:
            return 0

        #print('Updating Now Playing')

        title = track_object.title
        album = track_object.album
        artist = get_artist_strip_feat(track_object)

        try:
            if title != "" and artist != "":
                self.network.update_now_playing(
                    artist=artist, title=title, album=album)
                return 0
            else:
                print("Not sent, incomplete metadata")
                return 0
        except Exception as e:

            console.print("Error connecting to last.fm.", level=3)
            console.print("-- " + str(e), level=3)
            # print(e)
            if 'retry' in str(e):
                return 2
                # show_message("Could not update Last.fm. ", str(e), mode='warning')
            pctl.b_time -= 5000
            return 1


def get_backend_time(path):
    pctl.time_to_get = path

    pctl.playerCommand = 'time'
    pctl.playerCommandReady = True

    while pctl.playerCommand != 'done':
        time.sleep(0.005)

    return pctl.time_to_get


lastfm = LastFMapi()

class ListenBrainz:

    def __init__(self):

        self.enable = prefs.enable_lb
        # self.url = "https://api.listenbrainz.org/1/submit-listens"

    def url(self):
        url = prefs.listenbrainz_url
        if not url:
            url = "https://api.listenbrainz.org/"
        if not url.endswith("/"):
            url += "/"
        return url + "1/submit-listens"

    def listen_full(self, track_object, time):

        if self.enable is False:
            return True
        if prefs.scrobble_hold is True:
            return True
        if prefs.lb_token is None:
            show_message("ListenBrains is enabled but there is no token.", "How did this even happen.", mode='error')

        title = track_object.title
        album = track_object.album
        artist = get_artist_strip_feat(track_object)

        if title == "" or artist == "":
            return True

        data = {"listen_type": "single", "payload": []}
        metadata = {"track_name": title, "artist_name": artist}

        additional = {}

        # MusicBrainz Artist IDs
        if 'musicbrainz_artistids' in track_object.misc:
            additional['artist_mbids'] = track_object.misc['musicbrainz_artistids']

        # MusicBrainz Release ID
        if 'musicbrainz_albumid' in track_object.misc:
            additional['release_mbid'] = track_object.misc['musicbrainz_albumid']

        # MusicBrainz Recording ID
        if 'musicbrainz_recordingid' in track_object.misc:
            additional['recording_mbid'] = track_object.misc['musicbrainz_recordingid']

        # MusicBrainz Track ID
        if 'musicbrainz_trackid' in track_object.misc:
            additional['track_mbid'] = track_object.misc['musicbrainz_trackid']

        if additional:
            metadata['additional_info'] = additional

        #print(additional)
        data["payload"].append({"track_metadata": metadata})
        data["payload"][0]["listened_at"] = time

        r = requests.post(self.url(), headers={"Authorization": "Token " + prefs.lb_token}, data=json.dumps(data))
        if r.status_code != 200:
            show_message("There was an error submitting data to ListenBrainz", r.text, mode='warning')
            return False
        return True

    def listen_playing(self, track_object):
        if self.enable is False:
            return
        if prefs.scrobble_hold is True:
            return
        if prefs.lb_token is None:
            show_message("ListenBrains is enabled but there is no token.", "How did this even happen.", mode='error')
        title = track_object.title
        album = track_object.album
        artist = get_artist_strip_feat(track_object)

        if title == "" or artist == "":
            return

        data = {"listen_type": "playing_now", "payload": []}
        metadata = {"track_name": title, "artist_name": artist}

        additional = {}

        # MusicBrainz Artist IDs
        if 'musicbrainz_artistids' in track_object.misc:
            additional['artist_mbids'] = track_object.misc['musicbrainz_artistids']

        # MusicBrainz Release ID
        if 'musicbrainz_albumid' in track_object.misc:
            additional['release_mbid'] = track_object.misc['musicbrainz_albumid']

        # MusicBrainz Recording ID
        if 'musicbrainz_recordingid' in track_object.misc:
            additional['recording_mbid'] = track_object.misc['musicbrainz_recordingid']

        # MusicBrainz Track ID
        if 'musicbrainz_trackid' in track_object.misc:
            additional['track_mbid'] = track_object.misc['musicbrainz_trackid']

        if additional:
            metadata['additional_info'] = additional

        data["payload"].append({"track_metadata": metadata})
        #data["payload"][0]["listened_at"] = int(time.time())

        r = requests.post(self.url(), headers={"Authorization": "Token " + prefs.lb_token}, data=json.dumps(data))
        if r.status_code != 200:
            show_message("There was an error submitting data to ListenBrainz", r.text, mode='warning')
            print("error")
            print(r.status_code)
            print(r.json())

    def paste_key(self):

        text = copy_from_clipboard()
        if text == "":
            show_message("There is no text in the clipboard", mode="error")
            return

        if prefs.listenbrainz_url:
            prefs.lb_token = text
            return

        if len(text) == 36 and text[8] == "-":
            prefs.lb_token = text
        else:
            show_message("That is not a valid token.", mode='error')

    def clear_key(self):

        prefs.lb_token = ""
        save_prefs()
        self.enable = False


lb = ListenBrainz()

def get_love(track_object):

    star = star_store.full_get(track_object.index)
    if star is None:
        return False

    if 'L' in star[1]:
        return True
    else:
        return False

def get_love_index(index):

    star = star_store.full_get(index)
    if star is None:
        return False

    if 'L' in star[1]:
        return True
    else:
        return False


def love(set=True, track_id=None, no_delay=False, notify=False):

    if len(pctl.track_queue) < 1:
        return False

    if track_id is not None and track_id < 0:
        return False

    if track_id is None:
        track_id = pctl.track_queue[pctl.queue_step]

    loved = False
    star = star_store.full_get(track_id)

    if star is not None:
        if 'L' in star[1]:
            loved = True

    if set is False:
        return loved

    # global lfm_username
    # if len(lfm_username) > 0 and not lastfm.connected and not prefs.auto_lfm:
    #     show_message("You have a last.fm account ready but it is not enabled.", 'info',
    #                  'Either connect, enable auto connect, or remove the account.')
    #     return

    if star is None:
        star = star_store.new_object()

    loved ^= True

    if notify:
        gui.toast_love_object = pctl.g(track_id)
        gui.toast_love_added = loved
        toast_love_timer.set()
        gui.delay_frame(1.81)

    delay = 0.3
    if not lastfm.details_ready() or no_delay:
        delay = 0

    if loved:
        time.sleep(delay)
        gui.update += 1
        gui.pl_update += 1
        star = [star[0], star[1] + "L", star[2]]
        star_store.insert(track_id, star)
        if prefs.last_fm_token:
            try:
                lastfm.love(pctl.master_library[track_id].artist, pctl.master_library[track_id].title)
            except:
                show_message(_("Failed updating last.fm love status"), mode='warning')
                star = [star[0], star[1].strip("L"), star[2]]
                star_store.insert(track_id, star)
                show_message(_("Error updating love to last.fm!"), _("Maybe check your internet connection and try again?"), mode="error")

        if pctl.master_library[track_id].file_ext == "JELY":
            jellyfin.favorite(pctl.master_library[track_id])

    else:
        time.sleep(delay)
        gui.update += 1
        gui.pl_update += 1
        star = [star[0], star[1].replace("L", ""), star[2]]
        star_store.insert(track_id, star)
        if prefs.last_fm_token:
            try:
                lastfm.unlove(pctl.master_library[track_id].artist, pctl.master_library[track_id].title)
            except:
                show_message("Failed updating last.fm love status", mode='warning')
                star = [star[0], star[1] + "L", star[2]]
                star_store.insert(track_id, star)
        if pctl.master_library[track_id].file_ext == "JELY":
            jellyfin.favorite(pctl.master_library[track_id], un=True)

    gui.pl_update = 2
    gui.update += 1
    if pctl.mpris is not None:
        pctl.mpris.update(force=True)

def maloja_get_scrobble_counts():

    if lastfm.scanning_scrobbles is True or not prefs.maloja_url:
        return

    url = prefs.maloja_url
    if not url.endswith("/"):
        url += "/"
    url += "apis/mlj_1/scrobbles"
    lastfm.scanning_scrobbles = True
    try:
        r = requests.get(url)

        if r.status_code != 200:
            show_message("There was an error with the Maloja server", r.text, mode='warning')
            lastfm.scanning_scrobbles = False
            return
    except:
        show_message("There was an error reaching the Maloja server", mode='warning')
        lastfm.scanning_scrobbles = False
        return

    try:
        data = json.loads(r.text)
        l = data["list"]

        counts = {}

        for item in l:
            artists = item.get("artists")
            title = item.get("title")
            if title and artists:
                key = (title, tuple(artists))
                c = counts.get(key, 0)
                counts[key] = c + 1

        touched = []

        for key, value in counts.items():
            title, artists = key
            artists = [x.lower() for x in artists]
            title = title.lower()
            for track in pctl.master_library.values():
                if track.artist.lower() in artists and track.title.lower() == title:
                    if track.index in touched:
                        track.lfm_scrobbles += value
                    else:
                        track.lfm_scrobbles = value
                        touched.append(track.index)
        show_message(_("Scanning scrobbles complete"), mode="done")

    except:
        show_message("There was an error parsing the data", mode="warning")

    gui.pl_update += 1
    lastfm.scanning_scrobbles = False
    tauon.bg_save()


def maloja_scrobble(track):

    url = prefs.maloja_url

    if not track.artist or not track.title:
        return

    if not url.endswith("/newscrobble"):
        if not url.endswith("/"):
            url += "/"
        url += "apis/mlj_1/newscrobble"

    d = {}
    d["artist"] = track.artist
    d["title"] = track.title
    d["key"] = prefs.maloja_key

    try:
        r = requests.post(url, data=d)
        if r.status_code != 200:
            show_message("There was an error submitting data to Maloja server", r.text, mode='warning')
            return False
    except:
        show_message("There was an error submitting data to Maloja server", mode='warning')
        return False
    return True

class LastScrob:

    def __init__(self):

        self.a_index = -1
        self.a_sc = False
        self.a_pt = False
        self.queue = []
        self.running = False

    def start_queue(self):

        self.running = True
        mini_t = threading.Thread(target=self.process_queue)
        mini_t.daemon = True
        mini_t.start()


    def process_queue(self):

        time.sleep(0.4)

        while self.queue:

            try:
                tr = self.queue.pop()

                gui.pl_update = 1
                print("Submit Scrobble " + tr[0].artist + " - " + tr[0].title)

                success = True

                if tr[2] == "lfm" and prefs.auto_lfm and (lastfm.connected or lastfm.details_ready()):
                    success = lastfm.scrobble(tr[0], tr[1])
                elif tr[2] == "lb" and lb.enable:
                    success = lb.listen_full(tr[0], tr[1])
                elif tr[2] == "maloja":
                    success = maloja_scrobble(tr[0])
                elif tr[2] == "air":
                    success = subsonic.listen(tr[0], submit=True)
                elif tr[2] == "koel":
                    success = koel.listen(tr[0], submit=True)

                if not success:
                    print("Re-queue scrobble")
                    self.queue.append(tr)
                    time.sleep(10)
                    break

            except:
                print("SCROBBLE QUEUE ERROR")
                #raise

        if not self.queue:
            scrobble_warning_timer.force_set(1000)

        self.running = False


    def update(self, add_time):

        if pctl.queue_step > len(pctl.track_queue) - 1:
            print("Queue step error 1")
            return

        if self.a_index != pctl.track_queue[pctl.queue_step]:
            pctl.a_time = 0
            pctl.b_time = 0
            self.a_index = pctl.track_queue[pctl.queue_step]
            self.a_pt = False
            self.a_sc = False
        if pctl.playing_time == 0 and self.a_sc is True:
            print("Reset scrobble timer")
            pctl.a_time = 0
            pctl.b_time = 0
            self.a_pt = False
            self.a_sc = False

        if pctl.a_time > 6 and self.a_pt is False and pctl.master_library[self.a_index].length > 30:
            self.a_pt = True
            self.listen_track(pctl.master_library[self.a_index])
            # if prefs.auto_lfm and (lastfm.connected or lastfm.details_ready()) and not prefs.scrobble_hold:
            #     mini_t = threading.Thread(target=lastfm.update, args=([pctl.master_library[self.a_index]]))
            #     mini_t.daemon = True
            #     mini_t.start()
            #
            # if lb.enable and not prefs.scrobble_hold:
            #     mini_t = threading.Thread(target=lb.listen_playing, args=([pctl.master_library[self.a_index]]))
            #     mini_t.daemon = True
            #     mini_t.start()

        if pctl.a_time > 6 and self.a_pt:
            pctl.b_time += add_time
            if pctl.b_time > 20:
                pctl.b_time = 0
                self.listen_track(pctl.master_library[self.a_index])

        send_full = False
        if pctl.master_library[self.a_index].length > 30 and pctl.a_time > pctl.master_library[self.a_index].length \
                * 0.50 and self.a_sc is False:
            self.a_sc = True
            send_full = True

        if self.a_sc is False and pctl.master_library[self.a_index].length > 30 and pctl.a_time > 240:
            self.a_sc = True
            send_full = True

        if send_full:
            self.scrob_full_track(pctl.master_library[self.a_index])

    def listen_track(self, track_object):
        #print("LISTEN")

        if track_object.is_network:
            if track_object.file_ext == "SUB":
                subsonic.listen(track_object, submit=False)

        if not prefs.scrobble_hold:
            if prefs.auto_lfm and (lastfm.connected or lastfm.details_ready()):
                mini_t = threading.Thread(target=lastfm.update, args=([track_object]))
                mini_t.daemon = True
                mini_t.start()

            if lb.enable:
                mini_t = threading.Thread(target=lb.listen_playing, args=([track_object]))
                mini_t.daemon = True
                mini_t.start()

    def scrob_full_track(self, track_object):
        #print("SCROBBLE")
        track_object.lfm_scrobbles += 1
        gui.pl_update += 1

        if track_object.is_network:
            if track_object.file_ext == "SUB":
                self.queue.append((track_object, int(time.time()), "air"))
            if track_object.file_ext == "KOEL":
                self.queue.append((track_object, int(time.time()), "koel"))

        if not prefs.scrobble_hold:
            if prefs.auto_lfm and (lastfm.connected or lastfm.details_ready()):
                self.queue.append((track_object, int(time.time()), "lfm"))
            if lb.enable:
                self.queue.append((track_object, int(time.time()), "lb"))
            if prefs.maloja_url and prefs.maloja_enable:
                self.queue.append((track_object, int(time.time()), "maloja"))


lfm_scrobbler = LastScrob()

from t_modules.t_draw import TDraw
from t_modules.t_draw import QuickThumbnail
QuickThumbnail.renderer = renderer


class Strings:

    def __init__(self):
        self.spotify_likes = _("Spotify Likes")
        self.spotify_albums = _("Spotify Albums")
        self.spotify_un_liked = _("Track removed from liked tracks")
        self.spotify_already_un_liked = _("Track was already un-liked")
        self.spotify_already_liked = _("Track is already liked")
        self.spotify_like_added = _("Track added to liked tracks")
        self.spotify_account_connected = _("Spotify account connected")
        self.spotify_not_playing = _("This Spotify account isn't currently playing anything")
        self.spotify_error_starting = _("Error starting Spotify")
        self.spotify_request_auth = _("Please authorise Spotify in settings!")
        self.spotify_need_enable = _("Please authorise and click the enable toggle first!")
        self.spotify_import_complete = _("Spotify import complete")

        self.day = _("day")
        self.days = _("days")

        self.scan_chrome = _("Scanning for Chromecasts...")
        self.cast_to = _("Cast to: %s")
        self.no_chromecasts = _("No Chromecast devices found")

        self.web_server_stopped = _("Web server stopped.")

strings = Strings()

def id_to_pl(id):
    for i, item in enumerate(pctl.multi_playlist):
        if item[6] == id:
            return i
    return None

def pl_to_id(pl):
    return pctl.multi_playlist[pl][6]

class Chunker:

    def __init__(self):
        self.master_count = 0
        self.chunks = {}
        self.header = None
        self.headers = []
        self.h2 = None

        self.clients = {}

class Tauon:

    def __init__(self):

        self.t_title = t_title
        self.t_version = t_version
        self.t_agent = t_agent
        self.t_id = t_id
        self.desktop = desktop
        self.device = socket.gethostname()

        self.dummy_event = SDL_Event()
        self.translate = _
        self.strings = strings
        self.pctl = pctl
        self.lfm_scrobbler = lfm_scrobbler
        self.star_store = star_store
        self.gui = gui
        self.prefs = prefs
        self.cache_directory = cache_directory
        self.temp_audio = os.path.join(cache_directory, "stream-audio")
        self.user_directory = user_directory
        self.music_directory = music_directory
        self.worker_save_state = False
        self.launch_prefix = launch_prefix
        self.whicher = whicher
        self.load_orders = load_orders
        self.switch_playlist = None
        self.open_uri = open_uri
        self.love = love
        self.snap_mode = snap_mode
        self.console = console
        self.msys = msys
        self.TrackClass = TrackClass
        self.pl_gen = pl_gen
        self.QuickThumbnail = QuickThumbnail
        self.pl_to_id = pl_to_id
        self.id_to_pl = id_to_pl
        self.chunker = Chunker()
        self.stream_proxy = StreamEnc(self)
        self.level_train = []
        self.radio_server = None
        self.mod_formats = MOD_Formats

        self.tray_lock = threading.Lock()
        self.tray_releases = 0

        self.play_lock = None
        self.update_play_lock = None
        self.sleep_lock = None
        self.shutdown_lock = None
        self.quick_close = False

        self.cargo_playtime = None
        self.macos = macos
        self.aud = None

        self.recorded_songs = []

    def bg_save(self):
        self.worker_save_state = True
        tm.ready("worker")

    def exit(self):
        pctl.running = False

    def min_to_tray(self):
        SDL_HideWindow(t_window)
        gui.mouse_unknown = True

    def raise_window(self):
        SDL_ShowWindow(t_window)
        SDL_RaiseWindow(t_window)
        SDL_RestoreWindow(t_window)
        gui.lowered = False
        gui.update += 1


    def focus_window(self):
        SDL_RaiseWindow(t_window)

    def get_playing_playlist_id(self):
        return pl_to_id(pctl.active_playlist_playing)

    def wake(self):
        SDL_PushEvent(ctypes.byref(self.dummy_event))


tauon = Tauon()

if prefs.backend == 1:
    prefs.backend = 2

chrome = None

try:
    from t_modules.t_chrome import Chrome
    chrome = Chrome(tauon)
except:
    print("Pychromecast not found")


class PlexService:

    def __init__(self):
        self.connected = False
        self.resource = None
        self.scanning = False

    def connect(self):

        if not prefs.plex_username or not prefs.plex_password or not prefs.plex_servername:
            show_message(_("Missing username, password and/or server name"), mode='warning')
            self.scanning = False
            return

        try:
            from plexapi.myplex import MyPlexAccount
        except:
            show_message("Error importing python-plexapi", mode='error')
            self.scanning = False
            return

        try:
            account = MyPlexAccount(prefs.plex_username, prefs.plex_password)
            self.resource = account.resource(prefs.plex_servername).connect()  # returns a PlexServer instance
        except:
            show_message(_("Error connecting to PLEX server"),
                         _("Try check login credentials and that server is accessible."), mode='error')
            self.scanning = False
            return

        # from plexapi.server import PlexServer
        # baseurl = 'http://localhost:32400'
        # token = ''

        # self.resource = PlexServer(baseurl, token)

        self.connected = True

    def resolve_stream(self, location):
        print("Get plex stream")
        if not self.connected:
            self.connect()

        #return self.resource.url(location, True)
        return self.resource.library.fetchItem(location).getStreamURL()

    def resolve_thumbnail(self, location):

        if not self.connected:
            self.connect()
        if self.connected:
            return self.resource.url(location, True)
        return None

    def get_albums(self, return_list=False):

        gui.update += 1
        self.scanning = True

        if not self.connected:
            self.connect()

        if not self.connected:
            self.scanning = False
            return []

        playlist = []

        existing = {}
        for track_id, track in pctl.master_library.items():
            if track.is_network and track.file_ext == "PLEX":
                existing[track.url_key] = track_id

        albums = self.resource.library.section('Music').albums()
        gui.to_got = 0

        for album in albums:
            year = album.year
            album_artist = album.parentTitle
            album_title = album.title

            parent = (album_artist + " - " + album_title).strip("- ")

            for track in album.tracks():

                if not track.duration:
                    print("Warning: Skipping track with invalid duration - " + track.title + " - " + track.grandparentTitle)
                    continue

                id = pctl.master_count
                replace_existing = False

                e = existing.get(track.key)
                if e is not None:
                    id = e
                    replace_existing = True

                title = track.title
                track_artist = track.grandparentTitle
                duration = track.duration / 1000

                nt = TrackClass()
                nt.index = id
                nt.track_number = track.index
                nt.file_ext = "PLEX"
                nt.parent_folder_path = parent
                nt.parent_folder_name = parent
                nt.album_artist = album_artist
                nt.artist = track_artist
                nt.title = title
                nt.album = album_title
                nt.length = duration
                if hasattr(track, "locations") and track.locations:
                    nt.fullpath = track.locations[0]

                nt.is_network = True

                if track.thumb:
                    nt.art_url_key = track.thumb

                nt.url_key = track.key
                nt.date = str(year)

                pctl.master_library[id] = nt

                if not replace_existing:
                    pctl.master_count += 1

                playlist.append(nt.index)

            gui.to_got += 1
            gui.update += 1

        self.scanning = False

        if return_list:
            return playlist

        pctl.multi_playlist.append(pl_gen(title="PLEX Collection", playlist=playlist))
        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "plex path"
        switch_playlist(len(pctl.multi_playlist) - 1)


plex = PlexService()
tauon.plex = plex

from t_modules.t_jellyfin import Jellyfin

jellyfin = Jellyfin(tauon)
tauon.jellyfin = jellyfin


class SubsonicService:

    def __init__(self):
        self.scanning = False
        self.playlists = prefs.subsonic_playlists

    def r(self, point, p=None, binary=False, get_url=False):
        salt = secrets.token_hex(8)
        server = prefs.subsonic_server.rstrip("/") + "/"

        params = {
            "u": prefs.subsonic_user,
            'v': "1.13.0",
            'c': t_title,
            'f': "json"
        }

        if prefs.subsonic_password_plain:
            params["p"] = prefs.subsonic_password
        else:
            params["t"] = hashlib.md5((prefs.subsonic_password + salt).encode()).hexdigest()
            params['s'] = salt

        if p:
            params.update(p)

        point = "rest/" + point

        url = server + point

        if get_url:
            return url, params

        response = requests.get(url, params=params)

        if binary:
            return response.content

        d = json.loads(response.text)
        #print(d)

        if d["subsonic-response"]["status"] != "ok":
            print("Subsonic Error: " + response.text)

        return d

    def get_cover(self, track_object):
        response = self.r("getCoverArt", p={"id": track_object.art_url_key}, binary=True)
        return io.BytesIO(response)

    def resolve_stream(self, key):

        p = {"id": key}
        if prefs.network_stream_bitrate > 0:
            p['maxBitRate'] = prefs.network_stream_bitrate

        return self.r("stream", p={"id": key}, get_url=True)
        #print(responce.content)

    def listen(self, track_object, submit=False):

        try:
            a = self.r("scrobble", p={"id": track_object.url_key, "submission": submit})
        except:
            print("Error connect for scrobble on airsonic")
        return True

    def set_rating(self, track_object, rating):

        try:
            a = self.r("setRating", p={"id": track_object.url_key, "rating": math.ceil(rating / 2)})
        except:
            print("Error connect for set rating on airsonic")
        return True

    def set_album_rating(self, track_object, rating):
        id = track_object.misc.get("subsonic-folder-id")
        if id is not None:
            try:
                a = self.r("setRating", p={"id": id, "rating": math.ceil(rating / 2)})
            except:
                print("Error connect for set rating on airsonic")
        return True

    def get_music3(self, return_list=False):

        self.scanning = True
        gui.to_got = 0

        existing = {}

        for track_id, track in pctl.master_library.items():
            if track.is_network and track.file_ext == "SUB":
                existing[track.url_key] = track_id

        try:
            a = self.r("getIndexes")
        except:
            show_message("Error connecting to Airsonic server", mode="error")
            self.scanning = False
            return []

        b = a["subsonic-response"]["indexes"]["index"]

        folders = []

        for letter in b:
            artists = letter["artist"]
            for artist in artists:
                folders.append((
                    artist["id"],
                    artist["name"]
                ))

        playlist = []

        songsets = []
        for i in range(len(folders)):
            songsets.append([])
        statuses = [0] * len(folders)
        dupes = []

        def getsongs(index, folder_id, name, inner=False, parent=None):

            try:
                d = self.r("getMusicDirectory", p={"id": folder_id})
                if "child" not in d["subsonic-response"]["directory"]:
                    if not inner:
                        statuses[index] = 2
                    return

            except json.decoder.JSONDecodeError:
                print("Error reading Airsonic directory")
                if not inner:
                    statuses[index] = 2
                show_message("Error reading Airsonic directory!", mode="warning")
                return

            items = d["subsonic-response"]["directory"]["child"]

            gui.update = 2

            for item in items:

                if item["isDir"]:

                    if "userRating" in item and "artist" in item:
                        rating = item["userRating"]
                        if album_star_store.get_rating_artist_title(item["artist"], item["title"]) == 0 and rating == 0:
                            pass
                        else:
                            album_star_store.set_rating_artist_title(item["artist"], item["title"], int(rating * 2))

                    getsongs(index, item["id"], item["title"], inner=True, parent=item)
                    continue

                gui.to_got += 1
                song = item
                nt = TrackClass()

                if parent and "artist" in parent:
                    nt.album_artist = parent["artist"]

                if "title" in song:
                    nt.title = song["title"]
                if "artist" in song:
                    nt.artist = song["artist"]
                if "album" in song:
                    nt.album = song["album"]
                if "track" in song:
                    nt.track_number = song["track"]
                if "year" in song:
                    nt.date = str(song["year"])
                if "duration" in song:
                    nt.length = song["duration"]

                nt.file_ext = "SUB"
                nt.parent_folder_name = name
                if "path" in song:
                    nt.fullpath = song["path"]
                    nt.parent_folder_path = os.path.dirname(song["path"])
                if "coverArt" in song:
                    nt.art_url_key = song["id"]
                nt.url_key = song["id"]
                nt.misc["subsonic-folder-id"] = folder_id
                nt.is_network = True

                rating = 0
                if "userRating" in song:
                    rating = int(song["userRating"])

                songsets[index].append((nt, name, song["id"], rating))

            if inner:
                return
            statuses[index] = 2

        i = -1
        for id, name in folders:
            i += 1
            while statuses.count(1) > 3:
                time.sleep(0.1)

            statuses[i] = 1
            t = threading.Thread(target=getsongs, args=([i, id, name]))
            t.daemon = True
            t.start()

        while statuses.count(2) != len(statuses):
            time.sleep(0.1)

        for sset in songsets:
            for nt, name, song_id, rating in sset:

                id = pctl.master_count

                replace_existing = False
                ex = existing.get(song_id)
                if ex is not None:
                    id = ex
                    replace_existing = True

                nt.index = id
                pctl.master_library[id] = nt
                if not replace_existing:
                    pctl.master_count += 1

                playlist.append(nt.index)

                if star_store.get_rating(nt.index) == 0 and rating == 0:
                    pass
                else:
                    star_store.set_rating(nt.index, rating * 2)

        self.scanning = False
        if return_list:
            return playlist

        pctl.multi_playlist.append(pl_gen(title="Airsonic Collection", playlist=playlist))
        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "air"
        switch_playlist(len(pctl.multi_playlist) - 1)


    # def get_music2(self, return_list=False):
    #
    #     self.scanning = True
    #     gui.to_got = 0
    #
    #     existing = {}
    #
    #     for track_id, track in pctl.master_library.items():
    #         if track.is_network and track.file_ext == "SUB":
    #             existing[track.url_key] = track_id
    #
    #     try:
    #         a = self.r("getIndexes")
    #     except:
    #         show_message("Error connecting to Airsonic server", mode="error")
    #         self.scanning = False
    #         return []
    #
    #     b = a["subsonic-response"]["indexes"]["index"]
    #
    #     folders = []
    #
    #     for letter in b:
    #         artists = letter["artist"]
    #         for artist in artists:
    #             folders.append((
    #                 artist["id"],
    #                 artist["name"]
    #             ))
    #
    #     playlist = []
    #
    #     def get(folder_id, name):
    #
    #         try:
    #             d = self.r("getMusicDirectory", p={"id": folder_id})
    #             if "child" not in d["subsonic-response"]["directory"]:
    #                 return
    #
    #         except json.decoder.JSONDecodeError:
    #             print("Error reading Airsonic directory")
    #             show_message("Error reading Airsonic directory!", mode="warning")
    #             return
    #
    #         items = d["subsonic-response"]["directory"]["child"]
    #
    #         gui.update = 1
    #
    #         for item in items:
    #
    #             gui.to_got += 1
    #
    #             if item["isDir"]:
    #                 get(item["id"], item["title"])
    #                 continue
    #
    #             song = item
    #             id = pctl.master_count
    #
    #             replace_existing = False
    #             ex = existing.get(song["id"])
    #             if ex is not None:
    #                 id = ex
    #                 replace_existing = True
    #
    #             nt = TrackClass()
    #
    #             if "title" in song:
    #                 nt.title = song["title"]
    #             if "artist" in song:
    #                 nt.artist = song["artist"]
    #             if "album" in song:
    #                 nt.album = song["album"]
    #             if "track" in song:
    #                 nt.track_number = song["track"]
    #             if "year" in song:
    #                 nt.date = str(song["year"])
    #             if "duration" in song:
    #                 nt.length = song["duration"]
    #
    #             # if "bitRate" in song:
    #             #     nt.bitrate = song["bitRate"]
    #
    #             nt.file_ext = "SUB"
    #
    #             nt.index = id
    #
    #             nt.parent_folder_name = name
    #             if "path" in song:
    #                 nt.fullpath = song["path"]
    #                 nt.parent_folder_path = os.path.dirname(song["path"])
    #
    #             if "coverArt" in song:
    #                 nt.art_url_key = song["id"]
    #
    #             nt.url_key = song["id"]
    #             nt.is_network = True
    #
    #             pctl.master_library[id] = nt
    #
    #             if not replace_existing:
    #                 pctl.master_count += 1
    #
    #             playlist.append(nt.index)
    #
    #     for id, name in folders:
    #         get(id, name)
    #
    #     self.scanning = False
    #     if return_list:
    #         return playlist
    #
    #     pctl.multi_playlist.append(pl_gen(title="Airsonic Collection", playlist=playlist))
    #     pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "air"
    #     switch_playlist(len(pctl.multi_playlist) - 1)

subsonic = SubsonicService()

class KoelService:

    def __init__(self):
        self.connected = False
        self.resource = None
        self.scanning = False
        self.server = ""

        self.token = ""

    def connect(self):

        if not prefs.koel_username or not prefs.koel_password or not prefs.koel_server_url:
            show_message(_("Missing username, password and/or server URL"), mode='warning')
            self.scanning = False
            return

        if self.token:
            self.connected = True
            print("Already authorised")
            return

        password = prefs.koel_password
        username = prefs.koel_username
        server = prefs.koel_server_url
        self.server = server

        target = server + "/api/me"

        headers = {
            "Accept": "application/json",
            "Content-Type": "application/json",
        }
        body = {
            "email": username,
            "password": password,
        }

        try:
            r = requests.post(target, json=body, headers=headers)
        except:
            gui.show_message(_("Could not establish connection"), mode="error")

            return

        if r.status_code == 200:
            # print(r.json())
            self.token = r.json()["token"]
            if self.token:
                print("GOT KOEL TOKEN")
                self.connected = True
            else:
                print("AUTH ERROR")

        else:
            error = ""
            j = r.json()
            if "message" in j:
                error = j["message"]

            gui.show_message(_("Could not establish connection/authorisation"), error, mode="error")


    def resolve_stream(self, id):

        if not self.connected:
            self.connect()

        if prefs.network_stream_bitrate > 0:
            target = f"{self.server}/api/{id}/play/1/{prefs.network_stream_bitrate}"
        else:
            target = f"{self.server}/api/{id}/play/0/0"
        params = {"jwt-token": self.token,}

        # print(target)
        # print(urllib.parse.urlencode(params))

        return target, params

    def listen(self, track_object, submit=False):
        if submit:
            try:
                target = self.server + "/api/interaction/play"
                headers = {
                    "Authorization": "Bearer " + self.token,
                    "Accept": "application/json",
                    "Content-Type": "application/json",
                }

                r = requests.post(target, headers=headers, json={"song": track_object.url_key})
                # print(r.status_code)
                # print(r.text)
            except:
                print("error submitting listen to koel")

    def get_albums(self, return_list=False):

        gui.update += 1
        self.scanning = True

        if not self.connected:
            self.connect()

        if not self.connected:
            self.scanning = False
            return []

        playlist = []

        target = self.server + "/api/data"
        headers = {
            "Authorization": "Bearer " + self.token,
            "Accept": "application/json",
            "Content-Type": "application/json",
        }

        r = requests.get(target, headers=headers)
        data = r.json()

        artists = data["artists"]
        albums = data["albums"]
        songs = data["songs"]

        artist_ids = {}
        for artist in artists:
            id = artist["id"]
            if id not in artist_ids:
                artist_ids[id] = artist["name"]

        album_ids = {}
        covers = {}
        for album in albums:
            id = album["id"]
            if id not in album_ids:
                album_ids[id] = album["name"]
                if "cover" in album:
                    covers[id] = album["cover"]

        existing = {}

        for track_id, track in pctl.master_library.items():
            if track.is_network and track.file_ext == "KOEL":
                existing[track.url_key] = track_id

        for song in songs:

            id = pctl.master_count
            replace_existing = False

            e = existing.get(song["id"])
            if e is not None:
                id = e
                replace_existing = True

            nt = TrackClass()

            nt.title = song["title"]
            nt.index = id
            if "track" in song and song["track"] is not None:
                nt.track_number = song["track"]
            if "disc" in song and song["disc"] is not None:
                nt.disc = song["disc"]
            nt.length = float(song["length"])

            nt.artist = artist_ids.get(song["artist_id"], "")
            nt.album = album_ids.get(song["album_id"], "")
            nt.parent_folder_name = (nt.artist + " - " + nt.album).strip("- ")
            nt.parent_folder_path = nt.album + "/" + nt.parent_folder_name

            nt.art_url_key = covers.get(song["album_id"], "")
            nt.url_key = song["id"]

            nt.is_network = True
            nt.file_ext = "KOEL"

            pctl.master_library[id] = nt

            if not replace_existing:
                pctl.master_count += 1

            playlist.append(nt.index)

        self.scanning = False

        if return_list:
            return playlist

        pctl.multi_playlist.append(pl_gen(title="Koel Collection", playlist=playlist))
        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "koel path tn"
        standard_sort(len(pctl.multi_playlist) - 1)
        switch_playlist(len(pctl.multi_playlist) - 1)


koel = KoelService()
tauon.koel = koel


def get_network_thumbnail_url(track_object):
    if track_object.file_ext == "SPTY":
        return track_object.art_url_key
    if track_object.file_ext == "PLEX":
        url = plex.resolve_thumbnail(track_object.art_url_key)
        assert url is not None
        return url
    if track_object.file_ext == "JELY":
        url = jellyfin.resolve_thumbnail(track_object.art_url_key)
        assert url is not None
        assert url != ""
        return url
    if track_object.file_ext == "KOEL":
        url = track_object.art_url_key
        assert url
        return url

    return None

def jellyfin_get_library_thread():

    pref_box.close()
    save_prefs()
    if jellyfin.scanning:
        inp.mouse_click = False
        show_message("Already scanning!")
        return

    jellyfin.scanning = True
    shoot_dl = threading.Thread(target=jellyfin.ingest_library)
    shoot_dl.daemon = True
    shoot_dl.start()


def plex_get_album_thread():

    pref_box.close()
    save_prefs()
    if plex.scanning:
        inp.mouse_click = False
        show_message("Already scanning!")
        return
    plex.scanning = True

    shoot_dl = threading.Thread(target=plex.get_albums)
    shoot_dl.daemon = True
    shoot_dl.start()

def sub_get_album_thread():

    # if prefs.backend != 1:
    #     show_message("This feature is currently only available with the BASS backend")
    #     return

    pref_box.close()
    save_prefs()
    if subsonic.scanning:
        inp.mouse_click = False
        show_message("Already scanning!")
        return
    subsonic.scanning = True

    shoot_dl = threading.Thread(target=subsonic.get_music3)
    shoot_dl.daemon = True
    shoot_dl.start()

def koel_get_album_thread():

    # if prefs.backend != 1:
    #     show_message("This feature is currently only available with the BASS backend")
    #     return

    pref_box.close()
    save_prefs()
    if koel.scanning:
        inp.mouse_click = False
        show_message("Already scanning!")
        return
    koel.scanning = True

    shoot_dl = threading.Thread(target=koel.get_albums)
    shoot_dl.daemon = True
    shoot_dl.start()


if system == "windows" or msys:
    from infi.systray import SysTrayIcon

class STray:

    def __init__(self):

        self.active = False

    def up(self, systray):
        SDL_ShowWindow(t_window)
        SDL_RaiseWindow(t_window)
        SDL_RestoreWindow(t_window)
        gui.lowered = False

    def down(self):
        if self.active:
            SDL_HideWindow(t_window)

    def advance(self, systray):
        pctl.advance()

    def back(self, systray):
        pctl.back()

    def pause(self, systray):
         pctl.play_pause()

    def track_stop(self, systray):
         pctl.stop()

    def on_quit_callback(self, systray):
        pctl.running = False

    def start(self):
        menu_options = (("Show", None, self.up),
                        ("Play/Pause", None, self.pause),
                        ("Stop", None, self.track_stop),
                        ("Forward", None, self.advance),
                        ("Back", None, self.back))
        self.systray = SysTrayIcon(install_directory + "\\assets\\" + "icon.ico", "Tauon Music Box", menu_options, on_quit=self.on_quit_callback)
        self.systray.start()
        self.active = True

    def stop(self):
        self.systray.shutdown()
        self.active = False

tray = STray()

if system == "linux" and not macos and not msys:

    gnome = Gnome(tauon)

    try:
        gnomeThread = threading.Thread(target=gnome.main)
        gnomeThread.daemon = True
        gnomeThread.start()
    except:
        print("ERROR: Could not start Dbus thread")



if system == "windows" or msys:

    tray.start()
    
    import keyboard
    
    def key_callback(event):
        if event.event_type == "down":
            if event.scan_code == 179:
                inp.media_key = 'play'
            elif event.scan_code == 178:
                inp.media_key = 'stop'
            elif event.scan_code == 177:
                inp.media_key = 'back'
            elif event.scan_code == 176:
                inp.media_key = 'forward'
            gui.update += 1
        
    keyboard.hook_key(179, key_callback)
    keyboard.hook_key(178, key_callback)
    keyboard.hook_key(177, key_callback)
    keyboard.hook_key(176, key_callback)

    # def keyboard_hook():
        # from collections import namedtuple

        # KeyboardEvent = namedtuple('KeyboardEvent', ['event_type', 'key_code',
                                                     # 'scan_code', 'alt_pressed',
                                                     # 'time'])

        # handlers = []

        # def listen():
            # # Adapted from http://www.hackerthreads.org/Topic-42395

            # event_types = {win32con.WM_KEYDOWN: 'key down',
                           # win32con.WM_KEYUP: 'key up',
                           # 0x104: 'key down',  # WM_SYSKEYDOWN, used for Alt key.
                           # 0x105: 'key up',  # WM_SYSKEYUP, used for Alt key.
                           # }

            # def low_level_handler(nCode, wParam, lParam):

                # event = KeyboardEvent(event_types[wParam], lParam[0], lParam[1],
                                      # lParam[2] == 32, lParam[3])

                # if event[1] == 179 and event[0] == 'key down':
                    # input.media_key = 'play'
                # elif event[1] == 178 and event[0] == 'key down':
                    # input.media_key = 'stop'
                # elif event[1] == 177 and event[0] == 'key down':
                    # input.media_key = 'back'
                # elif event[1] == 176 and event[0] == 'key down':
                    # input.media_key = 'forward'
                # if input.media_key:
                    # gui.update += 1
                # # Be a good neighbor and call the next hook.
                # return windll.user32.CallNextHookEx(hook_id, nCode, wParam, lParam)

            # # Our low level handler signature.
            # CMPFUNC = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p))
            # # Convert the Python handler into C pointer.
            # pointer = CMPFUNC(low_level_handler)

            # # Hook both key up and key down events for common keys (non-system).
            # hook_id = windll.user32.SetWindowsHookExA(win32con.WH_KEYBOARD_LL, pointer,
                                                      # win32api.GetModuleHandle(None), 0)

            # # Register to remove the hook when the interpreter exits. Unfortunately a
            # # try/finally block doesn't seem to work here.
            # atexit.register(windll.user32.UnhookWindowsHookEx, hook_id)

            # while True:
                # msg = win32gui.GetMessage(None, 0, 0)
                # win32gui.TranslateMessage(byref(msg))
                # win32gui.DispatchMessage(byref(msg))
                # time.sleep(5)

        # listen()

#if system == 'windows' or msys:
#    print('Starting hook thread for Windows')
#    keyboardHookThread = threading.Thread(target=keyboard_hook)
#    keyboardHookThread.daemon = True
#    keyboardHookThread.start()

# macos multimedia keys
# if macos:
#     try:
#         import osxmmkeys
#         tap = osxmmkeys.Tap()
#         tap.on('play_pause', inp.m_key_play)
#         tap.on('next_track', inp.m_key_next)
#         tap.on('prev_track', inp.m_key_previous)
#         tap.start()
#     except ImportError:
#         print("Could not import osxmmkeys")


class GStats:
    def __init__(self):

        self.last_db = 0
        self.last_pl = 0
        self.artist_list = []
        self.album_list = []
        self.genre_list = []
        self.genre_dict = {}

    def update(self, playlist):

        pt = 0

        if pctl.master_count != self.last_db or self.last_pl != playlist:
            self.last_db = pctl.master_count
            self.last_pl = playlist

            artists = {}

            for index in pctl.multi_playlist[playlist][2]:
                artist = pctl.master_library[index].artist

                if artist == "":
                    artist = "<Artist Unspecified>"

                pt = int(star_store.get(index))
                if pt < 30:
                    continue

                if artist in artists:
                    artists[artist] += pt
                else:
                    artists[artist] = pt

            art_list = artists.items()

            sorted_list = sorted(art_list, key=lambda x: x[1], reverse=True)

            self.artist_list = copy.deepcopy(sorted_list)

            genres = {}
            genre_dict = {}

            for index in pctl.multi_playlist[playlist][2]:
                genre_r = pctl.master_library[index].genre

                pt = int(star_store.get(index))

                gn = []
                if ',' in genre_r:
                    for g in genre_r.split(","):
                        g = g.rstrip(" ").lstrip(" ")
                        if len(g) > 0:
                            gn.append(g)
                elif ';' in genre_r:
                    for g in genre_r.split(";"):
                        g = g.rstrip(" ").lstrip(" ")
                        if len(g) > 0:
                            gn.append(g)
                elif '/' in genre_r:
                    for g in genre_r.split("/"):
                        g = g.rstrip(" ").lstrip(" ")
                        if len(g) > 0:
                            gn.append(g)
                elif ' & ' in genre_r:
                    for g in genre_r.split(" & "):
                        g = g.rstrip(" ").lstrip(" ")
                        if len(g) > 0:
                            gn.append(g)
                else:
                    gn = [genre_r]

                pt = int(pt / len(gn))

                for genre in gn:

                    if genre.lower() in {"", 'other', 'unknown', 'misc'}:
                        genre = "<Genre Unspecified>"
                    if genre.lower() in {'jpop', 'japanese pop'}:
                        genre = 'J-Pop'
                    if genre.lower() in {'jrock', 'japanese rock'}:
                        genre = 'J-Rock'
                    if genre.lower() in {'alternative music', 'alt-rock', 'alternative', 'alternrock', 'alt'}:
                        genre = 'Alternative Rock'
                    if genre.lower() in {'jpunk', 'japanese punk'}:
                        genre = 'J-Punk'
                    if genre.lower() in {'post rock', 'post-rock'}:
                        genre = 'Post-Rock'
                    if genre.lower() in {'video game', 'game', 'game music', 'video game music', 'game ost'}:
                        genre = "Video Game Soundtrack"
                    if genre.lower() in {'general soundtrack', 'ost', 'Soundtracks'}:
                        genre = "Soundtrack"
                    if genre.lower() in ('anime', 'アニメ', 'anime ost'):
                        genre = 'Anime Soundtrack'
                    if genre.lower() in {'同人'}:
                        genre = 'Doujin'
                    if genre.lower() in {'chill, chill out', 'chill-out'}:
                        genre = 'Chillout'

                    genre = genre.title()

                    if len(genre) == 3 and genre[2] == 'm':
                        genre = genre.upper()

                    if genre in genres:

                        genres[genre] += pt
                    else:
                        genres[genre] = pt

                    if genre in genre_dict:
                        genre_dict[genre].append(index)
                    else:
                        genre_dict[genre] = [index]

            art_list = genres.items()
            sorted_list = sorted(art_list, key=lambda x: x[1], reverse=True)

            self.genre_list = copy.deepcopy(sorted_list)
            self.genre_dict = genre_dict

            # print('\n-----------------------\n')

            g_albums = {}

            for index in pctl.multi_playlist[playlist][2]:
                album = pctl.master_library[index].album

                if album == "":
                    album = "<Album Unspecified>"

                pt = int(star_store.get(index))

                if pt < 30:
                    continue

                if album in g_albums:
                    g_albums[album] += pt
                else:
                    g_albums[album] = pt

            art_list = g_albums.items()

            sorted_list = sorted(art_list, key=lambda x: x[1], reverse=True)

            self.album_list = copy.deepcopy(sorted_list)


stats_gen = GStats()

def do_exit_button():
    if mouse_up or ab_click:
        if gui.tray_active and prefs.min_to_tray and not key_shift_down:
            tauon.min_to_tray()
        elif gui.sync_progress and not gui.stop_sync:
            show_message(_("Stop the sync before exiting!"))
        else:
            pctl.running = False

def do_maximize_button():
    global mouse_down
    global drag_mode
    if gui.maximized:
        gui.maximized = False
        SDL_RestoreWindow(t_window)
    else:
        gui.maximized = True
        SDL_MaximizeWindow(t_window)

    mouse_down = False
    inp.mouse_click = False
    drag_mode = False

def do_minimize_button():
    # if tray.active and prefs.min_to_tray:
    #     tray.down()
    global mouse_down
    global drag_mode
    if macos:
        # hack
        SDL_SetWindowBordered(t_window, True)
        SDL_MinimizeWindow(t_window)
        SDL_SetWindowBordered(t_window, False)
    else:
        SDL_MinimizeWindow(t_window)

    mouse_down = False
    inp.mouse_click = False
    drag_mode = False

mac_circle = asset_loader("macstyle.png", True)

def draw_window_tools():

    global mouse_down
    global drag_mode

    #rect = (window_size[0] - 55 * gui.scale, window_size[1] - 35 * gui.scale, 53 * gui.scale, 33 * gui.scale)
    #fields.add(rect)
    # prefs.left_window_control = not key_shift_down
    macstyle = gui.macstyle

    bg_off = colours.window_buttons_bg
    bg_on = colours.window_buttons_bg_over
    fg_off = colours.window_button_icon_off
    fg_on = colours.window_buttons_icon_over
    x_on = colours.window_button_x_on
    x_off = colours.window_button_x_off

    h = round(28 * gui.scale)
    y = round(1 * gui.scale)
    if macstyle:
        y = round(9 * gui.scale)

    x_width = round(26 * gui.scale)
    ma_width = round(33 * gui.scale)
    mi_width = round(35 * gui.scale)
    re_width = round(30 * gui.scale)
    last_width = 0

    xx = 0
    l = prefs.left_window_control
    r = not l
    focused = window_is_focused()

    # Close
    if r:
        xx = window_size[0] - x_width
        xx -= round(2 * gui.scale)

    if macstyle:
        xx = window_size[0] - 27 * gui.scale
        if l:
            xx = round(4 * gui.scale)
        rect = (xx + 5, y - 1, 14 * gui.scale, 14 * gui.scale)
        fields.add(rect)
        colour = mac_close
        if not focused:
            colour = (86, 85, 86, 255)
        mac_circle.render(xx + 6 * gui.scale, y, colour)
        if coll(rect) and not gui.mouse_unknown:
            if coll_point(last_click_location, rect):
                do_exit_button()
    else:
        rect = (xx, y, x_width, h)
        last_width = x_width
        ddt.rect((rect[0], rect[1], rect[2], rect[3]), bg_off)
        fields.add(rect)
        if coll(rect) and not gui.mouse_unknown:
            ddt.rect((rect[0], rect[1], rect[2], rect[3]), bg_on)
            top_panel.exit_button.render(rect[0] + 8 * gui.scale, rect[1] + 8 * gui.scale, x_on)
            if coll_point(last_click_location, rect):
                do_exit_button()
        else:
            top_panel.exit_button.render(rect[0] + 8 * gui.scale, rect[1] + 8 * gui.scale, x_off)


    # Macstyle restore
    if gui.mode == 3:
        if macstyle:
            if r:
                xx -= round(20 * gui.scale)
            if l:
                xx += round(20 * gui.scale)
            rect = (xx + 5, y - 1, 14 * gui.scale, 14 * gui.scale)

            fields.add(rect)
            colour = (160, 55, 225, 255)
            if not focused:
                colour = (86, 85, 86, 255)
            mac_circle.render(xx + 6 * gui.scale, y, colour)
            if coll(rect) and not gui.mouse_unknown:
                if (mouse_up or ab_click) and coll_point(last_click_location, rect):
                    restore_full_mode()
                    gui.update += 2

    # maximize

    if draw_max_button and not gui.mode == 3:
        if macstyle:
            if r:
                xx -= round(20 * gui.scale)
            if l:
                xx += round(20 * gui.scale)
            rect = (xx + 5, y - 1, 14 * gui.scale, 14 * gui.scale)

            fields.add(rect)
            colour = mac_maximize
            if not focused:
                colour = (86, 85, 86, 255)
            mac_circle.render(xx + 6 * gui.scale, y, colour)
            if coll(rect) and not gui.mouse_unknown:
                if (mouse_up or ab_click) and coll_point(last_click_location, rect):
                    do_minimize_button()

        else:
            if r:
                xx -= ma_width
            if l:
                xx += last_width
            rect = (xx, y, ma_width, h)
            last_width = ma_width
            ddt.rect_a((rect[0], rect[1]), (rect[2], rect[3]), bg_off)
            fields.add(rect)
            if coll(rect):
                ddt.rect_a((rect[0], rect[1]), (rect[2], rect[3]), bg_on)
                top_panel.maximize_button.render(rect[0] + 10 * gui.scale, rect[1] + 10 * gui.scale, fg_on)
                if (mouse_up or ab_click) and coll_point(last_click_location, rect):
                    do_maximize_button()
            else:
                top_panel.maximize_button.render(rect[0] + 10 * gui.scale, rect[1] + 10 * gui.scale, fg_off)

    # minimize

    if draw_min_button:

        # x = window_size[0] - round(65 * gui.scale)
        # if draw_max_button and not gui.mode == 3:
        #     x -= round(34 * gui.scale)
        if macstyle:
            if r:
                xx -= round(20 * gui.scale)
            if l:
                xx += round(20 * gui.scale)
            rect = (xx + 5, y - 1, 14 * gui.scale, 14 * gui.scale)

            fields.add(rect)
            colour = mac_minimize
            if not focused:
                colour = (86, 85, 86, 255)
            mac_circle.render(xx + 6 * gui.scale, y, colour)
            if coll(rect) and not gui.mouse_unknown:
                if (mouse_up or ab_click) and coll_point(last_click_location, rect):
                    do_maximize_button()

        else:
            if r:
                xx -= mi_width
            if l:
                xx += last_width

            rect = (xx, y, mi_width, h)
            last_width = mi_width
            ddt.rect_a((rect[0], rect[1]), (rect[2], rect[3]), bg_off)
            fields.add(rect)
            if coll(rect):
                ddt.rect_a((rect[0], rect[1]), (rect[2], rect[3]), bg_on)
                ddt.rect_a((rect[0] + 11 * gui.scale, rect[1] + 16 * gui.scale), (14 * gui.scale, 3 * gui.scale), fg_on)
                if (mouse_up or ab_click) and coll_point(last_click_location, rect):
                    do_minimize_button()
            else:
                ddt.rect_a((rect[0] + 11 * gui.scale, rect[1] + 16 * gui.scale), (14 * gui.scale, 3 * gui.scale), fg_off)

    # restore

    if gui.mode == 3:

        # bg_off = [0, 0, 0, 50]
        # bg_on = [255, 255, 255, 10]
        # fg_off =(255, 255, 255, 40)
        # fg_on = (255, 255, 255, 60)
        if macstyle:
            pass
        else:
            if r:
                xx -= re_width
            if l:
                xx += last_width

            rect = (xx, y, re_width, h)
            ddt.rect_a((rect[0], rect[1]), (rect[2], rect[3]), bg_off)
            fields.add(rect)
            if coll(rect):
                ddt.rect_a((rect[0], rect[1]), (rect[2], rect[3]), bg_on)
                top_panel.restore_button.render(rect[0] + 8 * gui.scale, rect[1] + 9 * gui.scale, fg_on)
                if (inp.mouse_click or ab_click) and coll_point(click_location, rect):
                    restore_full_mode()
                    gui.update += 2
            else:
                top_panel.restore_button.render(rect[0] + 8 * gui.scale, rect[1] + 9 * gui.scale, fg_off)





def draw_window_border():

    corner_icon.render(window_size[0] - corner_icon.w, window_size[1] - corner_icon.h, colours.corner_icon)

    corner_rect = (window_size[0] - 20 * gui.scale, window_size[1] - 20 * gui.scale, 20, 20)
    fields.add(corner_rect)

    right_rect = (window_size[0] - 1 * gui.scale, 20 * gui.scale, 10, window_size[1] - 40 * gui.scale)
    fields.add(right_rect)

    top_rect = (20 * gui.scale, 0, window_size[0] - 40 * gui.scale, 1 * gui.scale)
    fields.add(top_rect)

    left_rect = (0, 10 * gui.scale, 4 * gui.scale, window_size[1] - 50 * gui.scale)
    fields.add(left_rect)

    bottom_rect = (20 * gui.scale, window_size[1] - 6, window_size[0] - 40 * gui.scale, 7 * gui.scale)
    fields.add(bottom_rect)

    if coll(corner_rect):
        gui.cursor_want = 4
    elif coll(right_rect):
        gui.cursor_want = 8
    elif coll(top_rect):
        gui.cursor_want = 9
    elif coll(left_rect):
        gui.cursor_want = 10
    elif coll(bottom_rect):
        gui.cursor_want = 11

    colour = colours.window_frame

    ddt.rect((0, 0, window_size[0], 1 * gui.scale), colour)
    ddt.rect((0, 0, 1 * gui.scale, window_size[1]), colour)
    ddt.rect((0, window_size[1] - 1 * gui.scale, window_size[0], 1 * gui.scale), colour)
    ddt.rect((window_size[0] - 1 * gui.scale, 0, 1 * gui.scale, window_size[1]), colour)

# -------------------------------------------------------------------------------------------
# initiate SDL2 --------------------------------------------------------------------C-IS-----



cursor_hand = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND)
cursor_standard = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW)
cursor_shift = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE)
cursor_text = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM)


if system == "linux" and not macos and not msys and import_cursors:
    cursor_br_corner = cairo_cursor_to_sdl(*c_br, fallback=cursor_standard)
    cursor_right_side = cairo_cursor_to_sdl(*c_rs, fallback=cursor_standard)
    cursor_top_side = cairo_cursor_to_sdl(*c_ts, fallback=cursor_standard)
    cursor_left_side = cairo_cursor_to_sdl(*c_ls, fallback=cursor_standard)
    cursor_bottom_side = cairo_cursor_to_sdl(*c_bs, fallback=cursor_standard)
else:
    cursor_br_corner = cursor_standard
    cursor_right_side = cursor_standard
    cursor_top_side = cursor_standard
    cursor_left_side = cursor_standard
    cursor_bottom_side = cursor_standard


if not maximized and gui.maximized:
    SDL_MaximizeWindow(t_window)

#print(SDL_GetError())

# t_window = SDL_CreateShapedWindow(window_title,
#                              SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
#                              window_size[0], window_size[1],
#                              flags)

# print(SDL_GetError())

if system == 'windows' or msys:
    gui.window_id = sss.info.win.window


# try:
#     SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, b"1")
#
# except:
#     print("old version of SDL detected")

# get window surface and set up renderer
#renderer = SDL_CreateRenderer(t_window, 0, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)

# renderer = SDL_CreateRenderer(t_window, 0, SDL_RENDERER_ACCELERATED)
#
# # window_surface = SDL_GetWindowSurface(t_window)
#
# SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND)
#
# display_index = SDL_GetWindowDisplayIndex(t_window)
# display_bounds = SDL_Rect(0, 0)
# SDL_GetDisplayBounds(display_index, display_bounds)
#
# icon = IMG_Load(os.path.join(asset_directory, "icon-64.png").encode())
# SDL_SetWindowIcon(t_window, icon)
# SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best".encode())
#
# SDL_SetWindowMinimumSize(t_window, round(560 * gui.scale), round(330 * gui.scale))
#
#
# gui.max_window_tex = 1000
# if window_size[0] > gui.max_window_tex or window_size[1] > gui.max_window_tex:
#
#     while window_size[0] > gui.max_window_tex:
#         gui.max_window_tex += 1000
#     while window_size[1] > gui.max_window_tex:
#         gui.max_window_tex += 1000
#
# gui.ttext = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.max_window_tex, gui.max_window_tex)
#
# # gui.pl_surf = SDL_CreateRGBSurfaceWithFormat(0, gui.max_window_tex, gui.max_window_tex, 32, SDL_PIXELFORMAT_RGB888)
#
# SDL_SetTextureBlendMode(gui.ttext, SDL_BLENDMODE_BLEND)
#
# gui.spec2_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.spec2_w, gui.spec2_y)
# gui.spec1_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.spec_w, gui.spec_h)
# gui.spec4_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.spec4_w, gui.spec4_h)
# gui.spec_level_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.level_ww, gui.level_hh)
#
# SDL_SetTextureBlendMode(gui.spec4_tex, SDL_BLENDMODE_BLEND)
#
# SDL_SetRenderTarget(renderer, None)
#
# gui.main_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.max_window_tex, gui.max_window_tex)
# gui.main_texture_overlay_temp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.max_window_tex, gui.max_window_tex)
#
# SDL_SetRenderTarget(renderer, gui.main_texture)
# SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255)
#
# SDL_SetRenderTarget(renderer, gui.main_texture_overlay_temp)
# SDL_SetTextureBlendMode(gui.main_texture_overlay_temp, SDL_BLENDMODE_BLEND)
# SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255)
#
# SDL_RenderClear(renderer)
#
# gui.abc = SDL_Rect(0, 0, gui.max_window_tex, gui.max_window_tex)
# gui.pl_update = 2
#
# SDL_SetWindowOpacity(t_window, prefs.window_opacity)

# gui.spec1_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.spec_w, gui.spec_h)
# gui.spec4_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.spec4_w, gui.spec4_h)
# gui.spec_level_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.level_ww, gui.level_hh)
# SDL_SetTextureBlendMode(gui.spec4_tex, SDL_BLENDMODE_BLEND)


def bass_player_thread(player):

    # logging.basicConfig(filename=user_directory + '/crash.log', level=logging.ERROR,
    #                     format='%(asctime)s %(levelname)s %(name)s %(message)s')

    try:
        player(pctl, gui, prefs, lfm_scrobbler, star_store, tauon)
    except:
        #logging.exception('Exception on player thread')
        show_message("Playback thread has crashed. Sorry about that.", "App will need to be restarted.", mode='error')
        time.sleep(1)
        show_message("Playback thread has crashed. Sorry about that.", "App will need to be restarted.", mode='error')
        time.sleep(1)
        show_message("Playback thread has crashed. Sorry about that.", "App will need to be restarted.", mode='error')
        raise

if (system == 'windows' or msys) and taskbar_progress:

    class WinTask:

        def __init__(self, ):
            self.start = time.time()
            self.updated_state = 0
            self.window_id = gui.window_id
            import comtypes.client as cc
            cc.GetModule(install_directory + "\\TaskbarLib.tlb")
            import comtypes.gen.TaskbarLib as tbl
            self.taskbar = cc.CreateObject(
                "{56FDF344-FD6D-11d0-958A-006097C9A090}",
                interface=tbl.ITaskbarList3)
            self.taskbar.HrInit()

            self.d_timer = Timer()

        def update(self, force=False):
            if self.d_timer.get() > 2 or force:
                self.d_timer.set()

                if pctl.playing_state == 1 and self.updated_state != 1:
                    self.taskbar.SetProgressState(self.window_id, 0x2)

                if pctl.playing_state == 1:
                    self.updated_state = 1
                    if pctl.playing_length > 2:
                        perc = int(pctl.playing_time * 100 / int(pctl.playing_length))
                        if perc < 2:
                            perc = 1
                        elif perc > 100:
                            prec = 100
                    else:
                        perc = 0

                    self.taskbar.SetProgressValue(self.window_id, perc, 100)

                elif pctl.playing_state == 2 and self.updated_state != 2:
                    self.updated_state = 2
                    self.taskbar.SetProgressState(self.window_id, 0x8)

                elif pctl.playing_state == 0 and self.updated_state != 0:
                    self.updated_state = 0
                    self.taskbar.SetProgressState(self.window_id, 0x2)
                    self.taskbar.SetProgressValue(self.window_id, 0, 100)


    if os.path.isfile(install_directory + "/TaskbarLib.tlb"):
        print("Taskbar progress enabled")
        pctl.windows_progress = WinTask()

    else:
        pctl.taskbar_progress = False
        print("Could not find TaskbarLib.tlb")

# ---------------------------------------------------------------------------------------------
# ABSTRACT SDL DRAWING FUNCTIONS -----------------------------------------------------


def coll_point(l, r):
    # rect point collision detection
    return r[0] < l[0] <= r[0] + r[2] and r[1] <= l[1] <= r[1] + r[3]

def coll(r):
    return r[0] < mouse_position[0] <= r[0] + r[2] and r[1] <= mouse_position[1] <= r[1] + r[3]


ddt = TDraw(renderer)
ddt.scale = gui.scale
ddt.force_subpixel_text = prefs.force_subpixel_text


from t_modules.t_launch import *
launch = Launch(tauon, pctl, gui, ddt)

class Drawing:

    def button(self, text, x, y, w=None, h=None, font=212, text_highlight_colour=None, text_colour=None,
               background_colour=None, background_highlight_colour=None, press=None, tooltip=""):

        if w is None:
            w = ddt.get_text_w(text, font) + 18 * gui.scale
        if h is None:
            h = 22 * gui.scale

        rect = (x, y, w, h)
        fields.add(rect)

        if text_highlight_colour is None:
            text_highlight_colour = colours.box_button_text_highlight
        if text_colour is None:
            text_colour = colours.box_button_text
        if background_colour is None:
            background_colour = colours.box_button_background
        if background_highlight_colour is None:
            background_highlight_colour = colours.box_button_background_highlight

        click = False


        if press is None:
            press = inp.mouse_click

        if coll(rect):
            if tooltip:
                tool_tip.test(x + 15 * gui.scale, y - 28 * gui.scale, tooltip)
            ddt.rect(rect, background_highlight_colour)

            # if background_highlight_colour[3] != 255:
            #     background_highlight_colour = None

            ddt.text((rect[0] + int(rect[2] / 2), rect[1] + 2 * gui.scale, 2), text, text_highlight_colour, font,
                     bg=background_highlight_colour)
            if press:
                click = True
        else:
            ddt.rect(rect, background_colour)
            if background_highlight_colour[3] != 255:
                background_colour = None
            ddt.text((rect[0] + int(rect[2] / 2), rect[1] + 2 * gui.scale, 2), text, text_colour, font,
                     bg=background_colour)
        return click

draw = Drawing()

def prime_fonts():
        standard_font = prefs.linux_font
        if msys:
            standard_font = prefs.linux_font + ", Sans"  # The CJK ones dont appear to be working
        ddt.prime_font(standard_font, 8, 9)
        ddt.prime_font(standard_font, 8, 10)
        ddt.prime_font(standard_font, 8.5, 11)
        ddt.prime_font(standard_font, 8.7, 11.5)
        ddt.prime_font(standard_font, 9, 12)
        ddt.prime_font(standard_font, 10, 13)
        ddt.prime_font(standard_font, 10, 14)
        ddt.prime_font(standard_font, 10.2, 14.5)
        ddt.prime_font(standard_font, 11, 15)
        ddt.prime_font(standard_font, 12, 16)
        ddt.prime_font(standard_font, 12, 17)
        ddt.prime_font(standard_font, 12, 18)
        ddt.prime_font(standard_font, 13, 19)
        ddt.prime_font(standard_font, 14, 20)
        ddt.prime_font(standard_font, 24, 30)

        ddt.prime_font(standard_font, 9, 412)
        ddt.prime_font(standard_font, 10, 413)

        standard_font = prefs.linux_font_semibold
        if msys:
            standard_font = prefs.linux_font_semibold + ", Noto Sans Med, Sans" #, Noto Sans CJK JP Medium, Noto Sans CJK Medium, Sans"

        ddt.prime_font(standard_font, 8, 309)
        ddt.prime_font(standard_font, 8, 310)
        ddt.prime_font(standard_font, 8.5, 311)
        ddt.prime_font(standard_font, 9, 312)
        ddt.prime_font(standard_font, 10, 313)
        ddt.prime_font(standard_font, 10.5, 314)
        ddt.prime_font(standard_font, 11, 315)
        ddt.prime_font(standard_font, 12, 316)
        ddt.prime_font(standard_font, 12, 317)
        ddt.prime_font(standard_font, 12, 318)
        ddt.prime_font(standard_font, 13, 319)
        ddt.prime_font(standard_font, 24, 330)

        standard_font = prefs.linux_font_bold
        if msys:
            standard_font = prefs.linux_font_bold + ", Noto Sans, Sans Bold"

        ddt.prime_font(standard_font, 6, 209)
        ddt.prime_font(standard_font, 7, 210)
        ddt.prime_font(standard_font, 8, 211)
        ddt.prime_font(standard_font, 9, 212)
        ddt.prime_font(standard_font, 10, 213)
        ddt.prime_font(standard_font, 11, 214)
        ddt.prime_font(standard_font, 12, 215)
        ddt.prime_font(standard_font, 13, 216)
        ddt.prime_font(standard_font, 14, 217)
        ddt.prime_font(standard_font, 17, 218)
        ddt.prime_font(standard_font, 19, 219)
        ddt.prime_font(standard_font, 20, 220)
        ddt.prime_font(standard_font, 25, 228)

        standard_font = prefs.linux_font_condensed
        if msys:
            standard_font = "Noto Sans ExtCond, Sans"
        ddt.prime_font(standard_font, 10, 413)
        ddt.prime_font(standard_font, 11, 414)
        ddt.prime_font(standard_font, 12, 415)
        ddt.prime_font(standard_font, 13, 416)


        standard_font = prefs.linux_font_condensed_bold #"Noto Sans, ExtraCondensed Bold"
        if msys:
            standard_font = "Noto Sans ExtCond, Sans Bold"
        #ddt.prime_font(standard_font, 9, 512)
        ddt.prime_font(standard_font, 10, 513)
        ddt.prime_font(standard_font, 11, 514)
        ddt.prime_font(standard_font, 12, 515)
        ddt.prime_font(standard_font, 13, 516)


if system == "linux":
    prime_fonts()

else:
    #standard_font = "Meiryo"
    standard_font = "Arial"
    #semibold_font = "Meiryo Semibold"
    semibold_font = "Arial Bold"
    standard_weight = 500
    bold_weight = 600
    ddt.win_prime_font(standard_font, 14, 10, weight=standard_weight, y_offset=0)
    ddt.win_prime_font(standard_font, 15, 11, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 15, 11.5, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 15, 12, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 15, 13, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 16, 14, weight=standard_weight, y_offset=0)
    ddt.win_prime_font(standard_font, 16, 14.5, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 17, 15, weight=standard_weight, y_offset=-1)
    ddt.win_prime_font(standard_font, 20, 16, weight=standard_weight, y_offset=-2)
    ddt.win_prime_font(standard_font, 20, 17, weight=standard_weight, y_offset=-1)

    ddt.win_prime_font(standard_font, 30 + 4, 30, weight=standard_weight, y_offset=-12)
    ddt.win_prime_font(semibold_font, 9, 209, weight=bold_weight, y_offset=1)
    ddt.win_prime_font('Arial', 10 + 4, 210, weight=600, y_offset=2)
    ddt.win_prime_font('Arial', 11 + 3, 211, weight=600, y_offset=2)
    ddt.win_prime_font(semibold_font, 12 + 4, 212, weight=bold_weight, y_offset=1)
    ddt.win_prime_font(semibold_font, 13 + 3, 213, weight=bold_weight, y_offset=-1)
    ddt.win_prime_font(semibold_font, 14 + 2, 214, weight=bold_weight, y_offset=1)
    ddt.win_prime_font(semibold_font, 15 + 2, 215, weight=bold_weight, y_offset=1)
    ddt.win_prime_font(semibold_font, 16 + 2, 216, weight=bold_weight, y_offset=1)
    ddt.win_prime_font(semibold_font, 17 + 2, 218, weight=bold_weight, y_offset=1)
    ddt.win_prime_font(semibold_font, 18 + 2, 218, weight=bold_weight, y_offset=1)
    ddt.win_prime_font(semibold_font, 19 + 2, 220, weight=bold_weight, y_offset=1)
    ddt.win_prime_font(semibold_font, 28 + 2, 228, weight=bold_weight, y_offset=1)


    standard_weight = 550
    ddt.win_prime_font(standard_font, 14, 310, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 15, 311, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 16, 312, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 17, 313, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 18, 314, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 19, 315, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 20, 316, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 21, 317, weight=standard_weight, y_offset=1)

    standard_font = "Arial Narrow"
    standard_weight = 500

    ddt.win_prime_font(standard_font, 14, 410, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 15, 411, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 16, 412, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 17, 413, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 18, 414, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 19, 415, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 20, 416, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 21, 417, weight=standard_weight, y_offset=1)

    standard_weight = 600

    ddt.win_prime_font(standard_font, 14, 510, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 15, 511, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 16, 512, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 17, 513, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 18, 514, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 19, 515, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 20, 516, weight=standard_weight, y_offset=1)
    ddt.win_prime_font(standard_font, 21, 517, weight=standard_weight, y_offset=1)


class DropShadow:

    def __init__(self):

        self.readys = {}
        self.underscan = int(15 * gui.scale)
        self.radius = 4
        self.grow = 2 * gui.scale
        self.opacity = 90

    def prepare(self, w, h):

        fh = h + self.underscan
        fw = w + self.underscan

        im = Image.new("RGBA", (round(fw), round(fh)), 0x00000000)
        draw = ImageDraw.Draw(im)
        draw.rectangle(((self.underscan, self.underscan), (w + 2, h + 2)), fill="black")

        im = im.filter(ImageFilter.GaussianBlur(self.radius))

        g = io.BytesIO()
        g.seek(0)
        im.save(g, 'PNG')
        g.seek(0)

        wop = rw_from_object(g)
        s_image = IMG_Load_RW(wop, 0)
        c = SDL_CreateTextureFromSurface(renderer, s_image)
        SDL_SetTextureAlphaMod(c, self.opacity)

        tex_w = pointer(c_int(0))
        tex_h = pointer(c_int(0))
        SDL_QueryTexture(c, None, None, tex_w, tex_h)

        dst = SDL_Rect(0, 0)
        dst.w = int(tex_w.contents.value)
        dst.h = int(tex_h.contents.value)

        SDL_FreeSurface(s_image)
        g.close()
        im.close()

        unit = (dst, c)
        self.readys[(w, h)] = unit

    def render(self, x, y, w, h):

        if (w, h) not in self.readys:
            self.prepare(w, h)

        unit = self.readys[(w, h)]
        unit[0].x = round(x) - round(self.underscan)
        unit[0].y = round(y) - round(self.underscan)
        SDL_RenderCopy(renderer, unit[1], None, unit[0])


drop_shadow = DropShadow()


class LyricsRenMini:

    def __init__(self):

        self.index = -1
        self.text = ""


        self.lyrics_position = 0

    def generate(self, index, w):

        self.text = pctl.master_library[index].lyrics
        self.lyrics_position = 0

    def render(self, index, x, y, w, h, p):

        if index != self.index or self.text != pctl.master_library[index].lyrics:
            self.index = index
            self.generate(index, w)

        colour = colours.side_bar_line1

        ddt.text((x, y, 4, w), self.text, colour, 15, w - (w % 2), colours.side_panel_background)


lyrics_ren_mini = LyricsRenMini()

class LyricsRen:

    def __init__(self):

        self.index = -1
        self.text = ""

        self.lyrics_position = 0

    def test_update(self, track_object):

        if track_object.index != self.index or self.text != track_object.lyrics:
            self.index = track_object.index
            self.text = track_object.lyrics
            self.lyrics_position = 0

    def render(self, x, y, w, h, p):

        colour = colours.lyrics
        if test_lumi(colours.gallery_background) < 0.5:
            colour = colours.grey(40)

        ddt.text((x, y, 4, w), self.text, colour, 17, w, colours.playlist_panel_background)


lyrics_ren = LyricsRen()


def find_synced_lyric_data(track):
    if track.is_network:
        return None

    direc = track.parent_folder_path
    name = os.path.splitext(track.filename)[0] + ".lrc"

    if len(track.lyrics) > 20 and track.lyrics[0] == "[" and ":" in track.lyrics[:20] and "." in track.lyrics[:20]:
        return track.lyrics.splitlines()

    try:
        if os.path.isfile(os.path.join(direc, name)):
            with open(os.path.join(direc, name), 'r') as f:
                data = f.readlines()
        else:
            return None
    except:
        print("Read lyrics file error")
        return None

    return data

class TimedLyricsToStatic:

    def __init__(self):
        self.cache_key = None
        self.cache_lyrics = ""

    def get(self, track):
        if track.lyrics:
            return track.lyrics
        if track.is_network:
            return ""
        if track == self.cache_key:
            return self.cache_lyrics
        else:
            data = find_synced_lyric_data(track)

            if data is None:
                self.cache_lyrics = ""
                self.cache_key = track
                return ""
            text = ""

            for line in data:
                if len(line) < 10:
                    continue

                if line[0] != "[" or line[9] != "]" or ":" not in line or "." not in line:
                    continue

                text += line.split("]")[-1].rstrip("\n") + "\n"

            self.cache_lyrics = text
            self.cache_key = track
            return text

tauon.synced_to_static_lyrics = TimedLyricsToStatic()


def get_real_time():
    offset = pctl.decode_time - (prefs.sync_lyrics_time_offset / 1000)
    if prefs.backend == 4:
        offset -= (prefs.device_buffer - 120) / 1000
    elif prefs.backend == 2:
        offset += 0.1
    return max(0, offset)


class TimedLyricsRen:

    def __init__(self):

        self.index = -1

        self.scanned = {}
        self.ready = False
        self.data = []

        self.scroll_position = 0

    def generate(self, track):

        if self.index == track.index:
            return self.ready

        self.ready = False
        self.index = track.index
        self.scroll_position = 0
        self.data.clear()

        data = find_synced_lyric_data(track)
        if data is None:
            return

        for line in data:
            if len(line) < 10:
                continue

            if line[0] != "[" or "]" not in line or ":" not in line or "." not in line:
                continue

            try:

                text = line.split("]")[-1].rstrip("\n")
                t = line

                while t[0] == "[" and t[9] == "]" and ":" in t and "." in t:

                    a = t.lstrip("[")
                    t = t.split("]")[1] + "]"

                    a = a.split("]")[0]
                    mm, b = a.split(":")
                    ss, ms = b.split(".")

                    s = int(mm) * 60 + int(ss)
                    if len(ms) == 2:
                        s += int(ms) / 100
                    elif len(ms) == 3:
                        s += int(ms) / 1000

                    self.data.append((s, text))

                    if len(t) < 10:
                        break
            except:
                continue

        self.data = sorted(self.data, key=lambda x: x[0])
        # print(self.data)

        self.ready = True
        return True


    def render(self, index, x, y, side_panel=False, w=0, h=0):

        if index != self.index:
            self.ready = False
            self.generate(pctl.master_library[index])

        if not self.ready:
            return False


        if mouse_wheel and (pctl.playing_state != 1 or pctl.track_queue[pctl.queue_step] != index):
            if side_panel:
                if coll((x, y, w, h)):
                    self.scroll_position += int(mouse_wheel * 30 * gui.scale)
            else:
                self.scroll_position += int(mouse_wheel * 30 * gui.scale)

        line_active = -1
        last = -1

        highlight = True

        if side_panel:
            bg = colours.top_panel_background
            font_size = 15
            spacing = round(17 * gui.scale)
        else:
            bg = colours.playlist_panel_background
            font_size = 17
            spacing = round(23 * gui.scale)

        test_time = get_real_time()

        if pctl.track_queue[pctl.queue_step] == index:

            for i, line in enumerate(self.data):
                if line[0] < test_time:

                    last = i

                if line[0] > test_time:

                    pctl.wake_past_time = line[0]
                    line_active = last
                    break
            else:
                line_active = len(self.data) - 1


            if pctl.playing_state == 1:
                self.scroll_position = (max(0, line_active)) * spacing * -1

        yy = y + self.scroll_position


        for i, line in enumerate(self.data):

            if 0 < yy < window_size[1]:

                colour = colours.lyrics
                if test_lumi(colours.gallery_background) < 0.5:
                    colour = colours.grey(40)

                if i == line_active and highlight:
                    colour = [255, 210, 50, 255]
                    if colours.lm:
                        colour = [180, 130, 210, 255]

                h = ddt.text((x, yy, 4, w - 20 * gui.scale), line[1], colour, font_size, w - 20 * gui.scale, bg)
                yy += max(h - round(6 * gui.scale), spacing)
            else:
                yy += spacing


timed_lyrics_ren = TimedLyricsRen()



def draw_internel_link(x, y, text, colour, font):

    tweak = font
    while tweak > 100:
        tweak -= 100

    if gui.scale == 2:
        tweak *= 2
        tweak += 4
    if gui.scale == 1.25:
        tweak = round(tweak * 1.25)
        tweak += 1

    sp = ddt.text((x, y), text, colour, font)

    rect = [x - 5 * gui.scale, y - 2 * gui.scale, sp + 11 * gui.scale, 23 * gui.scale]
    fields.add(rect)

    if coll(rect):
        if not inp.mouse_click:
            gui.cursor_want = 3
        ddt.line(x, y + tweak + 2, x + sp, y + tweak + 2, alpha_mod(colour, 180))
        if inp.mouse_click:
            return True
    return False




# No hit detect
def draw_linked_text(location, text, colour, font, force=False, replace=""):
    base = ""
    link_text = ""
    rest = ""
    on_base = True

    if force:
        on_base = False
        base = ""
        link_text = text
        rest = ""
    else:
        for i in range(len(text)):
            if text[i:i + 7] == "http://" or text[i:i + 4] == "www." or text[i:i + 8] == "https://":
                on_base = False
            if on_base:
                base += text[i]
            else:
                if i == len(text) or text[i] in '\\) "\'':
                    rest = text[i:]
                    break
                else:
                    link_text += text[i]

    target_link = link_text
    if replace:
        link_text = replace

    left = ddt.get_text_w(base, font)
    right = ddt.get_text_w(base + link_text, font)

    x = location[0]
    y = location[1]

    ddt.text((x, y), base, colour, font)
    ddt.text((x + left, y), link_text, colours.link_text, font)
    ddt.text((x + right, y), rest, colour, font)

    tweak = font
    while tweak > 100:
        tweak -= 100

    if gui.scale == 2:
        tweak *= 2
        tweak += 4
    elif gui.scale != 1:
        tweak = round(tweak * gui.scale)
        tweak += 2

    if system == "windows":
        tweak += 1

    #ddt.line(x + left, y + tweak + 2, x + right, y + tweak + 2, alpha_mod(colours.link_text, 120))
    ddt.rect((x + left, y + tweak + 2, right - left, round(1 * gui.scale)), alpha_mod(colours.link_text, 120))

    return left, right - left, target_link

def draw_linked_text2(x, y, text, colour, font, click=False, replace=""):
    link_pa = draw_linked_text((x, y), text,
                               colour, font, replace=replace)
    link_rect = [x + link_pa[0], y, link_pa[1], 18 * gui.scale]
    if coll(link_rect):
        if not click:
            gui.cursor_want = 3
        if click:
            webbrowser.open(link_pa[2], new=2, autoraise=True)
    fields.add(link_rect)

def link_activate(x, y, link_pa, click=None):

    link_rect = [x + link_pa[0], y - 2 * gui.scale, link_pa[1], 20 * gui.scale]

    if click is None:
        click = inp.mouse_click

    fields.add(link_rect)
    if coll(link_rect):
        if not click:
            gui.cursor_want = 3
        if click:
            webbrowser.open(link_pa[2], new=2, autoraise=True)
            track_box = True



text_box_canvas_rect = SDL_Rect(0, 0, round(2000 * gui.scale), round(40 * gui.scale))
text_box_canvas_hide_rect = SDL_Rect(0, 0, round(2000 * gui.scale), round(40 * gui.scale))
text_box_canvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, text_box_canvas_rect.w, text_box_canvas_rect.h)
SDL_SetTextureBlendMode(text_box_canvas, SDL_BLENDMODE_BLEND)

class TextBox2:
    cursor = True

    def __init__(self):

        self.text = ""
        self.cursor_position = 0
        self.selection = 0
        self.offset = 0
        self.down_lock = False
        self.paste_text = ""

    def paste(self):

        if SDL_HasClipboardText():
            clip = SDL_GetClipboardText().decode('utf-8')
            self.paste_text = clip

    def copy(self):

        text = self.get_selection()
        if not text:
            text = self.text
        if text != "":
            SDL_SetClipboardText(text.encode('utf-8'))

    def set_text(self, text):

        self.text = text
        self.cursor_position = 0
        self.selection = 0

    def clear(self):
        self.text = ""

    def highlight_all(self):

        self.selection = len(self.text)
        self.cursor_position = 0

    def eliminate_selection(self):
        if self.selection != self.cursor_position:
            if self.selection > self.cursor_position:
                self.text = self.text[0: len(self.text) - self.selection] + self.text[
                                                                            len(self.text) - self.cursor_position:]
                self.selection = self.cursor_position
            else:
                self.text = self.text[0: len(self.text) - self.cursor_position] + self.text[
                                                                                  len(self.text) - self.selection:]
                self.cursor_position = self.selection

    def get_selection(self, p=1):
        if self.selection != self.cursor_position:
            if p == 1:
                if self.selection > self.cursor_position:
                    return self.text[len(self.text) - self.selection: len(self.text) - self.cursor_position]

                else:
                    return self.text[len(self.text) - self.cursor_position: len(self.text) - self.selection]
            if p == 0:
                return self.text[0: len(self.text) - max(self.cursor_position, self.selection)]
            if p == 2:
                return self.text[len(self.text) - min(self.cursor_position, self.selection):]

        else:
            return ""

    def draw(self, x, y, colour, active=True, secret=False, font=13, width=0, click=False, selection_height=18,
             big=False):

        # A little bit messy.
        # For now, this is set up so where 'width' is set > 0, the cursor position becomes editable,
        # otherwise it is fixed to end

        SDL_SetRenderTarget(renderer, text_box_canvas)
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE)
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0)

        text_box_canvas_rect.x = 0
        text_box_canvas_rect.y = 0
        SDL_RenderFillRect(renderer, text_box_canvas_rect)

        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND)

        selection_height *= gui.scale

        if click is False:
            click = inp.mouse_click
        if mouse_down:
            gui.update = 2  # todo, more elegant fix

        rect = (x - 3, y - 2, width - 3, 21 * gui.scale)
        select_rect = (x - 20 * gui.scale, y - 2, width + 20 * gui.scale, 21 * gui.scale)

        fields.add(rect)

        # Activate Menu
        if coll(rect):
            if right_click or level_2_right_click:
                field_menu.activate(self)

        if width > 0 and active:

            if click and field_menu.active:
                # field_menu.click()
                click = False

            # Add text from input
            if input_text != "":
                self.eliminate_selection()
                self.text = self.text[0: len(self.text) - self.cursor_position] + input_text + self.text[len(
                    self.text) - self.cursor_position:]

            def g():
                if len(self.text) == 0 or self.cursor_position == len(self.text):
                    return None
                return self.text[len(self.text) - self.cursor_position - 1]
            def g2():
                if len(self.text) == 0 or self.cursor_position == 0:
                    return None
                return self.text[len(self.text) - self.cursor_position]
            def d():
                self.text = self.text[0: len(self.text) - self.cursor_position - 1] + self.text[
                                                                            len(self.text) - self.cursor_position:]
                self.selection = self.cursor_position

            # Ctrl + Backspace to delete word
            if inp.backspace_press and (key_ctrl_down or key_rctrl_down) and \
                self.cursor_position == self.selection and len(self.text) > 0 and self.cursor_position < len(self.text):
                while g() == " ":
                    d()
                while g() != " " and g() != None:
                    d()

            # Ctrl + left to move cursor back a word
            elif (key_ctrl_down or key_rctrl_down) and key_left_press:
                while g() == " ":
                    self.cursor_position += 1
                    if not key_shift_down:
                        self.selection = self.cursor_position
                while g() != None and g() not in " !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~":
                    self.cursor_position += 1
                    if not key_shift_down:
                        self.selection = self.cursor_position
                    if g() == " ":
                        self.cursor_position -= 1
                        if not key_shift_down:
                            self.selection = self.cursor_position
                        break

            # Ctrl + right to move cursor forward a word
            elif (key_ctrl_down or key_rctrl_down) and key_right_press:
                while g2() == " ":
                    self.cursor_position -= 1
                    if not key_shift_down:
                        self.selection = self.cursor_position
                while g2() != None and g2() not in " !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~":
                    self.cursor_position -= 1
                    if not key_shift_down:
                        self.selection = self.cursor_position
                    if g2() == " ":
                        self.cursor_position += 1
                        if not key_shift_down:
                            self.selection = self.cursor_position
                        break

            # Handle normal backspace
            elif inp.backspace_press and len(self.text) > 0 and self.cursor_position < len(self.text):
                while inp.backspace_press and len(self.text) > 0 and self.cursor_position < len(self.text):
                    if self.selection != self.cursor_position:
                        self.eliminate_selection()
                    else:
                        self.text = self.text[0:len(self.text) - self.cursor_position - 1] + self.text[len(
                            self.text) - self.cursor_position:]
                    inp.backspace_press -= 1
            elif inp.backspace_press and len(self.get_selection()) > 0:
                self.eliminate_selection()

            # Left and right arrow keys to move cursor
            if key_right_press:
                if self.cursor_position > 0:
                    self.cursor_position -= 1
                if not key_shift_down and not key_shiftr_down:
                    self.selection = self.cursor_position


            if key_left_press:
                if self.cursor_position < len(self.text):
                    self.cursor_position += 1
                if not key_shift_down and not key_shiftr_down:
                    self.selection = self.cursor_position

            if self.paste_text:
                if 'http://' in self.text and 'http://' in self.paste_text:
                    self.text = ""

                self.paste_text = self.paste_text.rstrip(" ").lstrip(" ")
                self.paste_text = self.paste_text.replace('\n', ' ').replace('\r', '')

                self.eliminate_selection()
                self.text = self.text[0: len(self.text) - self.cursor_position] + self.paste_text + self.text[len(
                    self.text) - self.cursor_position:]
                self.paste_text = ""

            # Paste via ctrl-v
            if key_ctrl_down and key_v_press:
                clip = SDL_GetClipboardText().decode('utf-8')
                self.eliminate_selection()
                self.text = self.text[0: len(self.text) - self.cursor_position] + clip + self.text[len(
                    self.text) - self.cursor_position:]

            if key_ctrl_down and key_c_press:
                self.copy()

            if key_ctrl_down and key_x_press:
                if len(self.get_selection()) > 0:
                    text = self.get_selection()
                    if text != "":
                        SDL_SetClipboardText(text.encode('utf-8'))
                    self.eliminate_selection()

            if key_ctrl_down and key_a_press:
                self.cursor_position = 0
                self.selection = len(self.text)

            #ddt.rect(rect, [255, 50, 50, 80], True)
            if coll(rect) and not field_menu.active:
                gui.cursor_want = 2

            # Delete key to remove text in front of cursor
            if key_del:
                if self.selection != self.cursor_position:
                    self.eliminate_selection()
                else:
                    self.text = self.text[0:len(self.text) - self.cursor_position] + self.text[len(
                        self.text) - self.cursor_position + 1:]
                    if self.cursor_position > 0:
                        self.cursor_position -= 1
                    self.selection = self.cursor_position

            if key_home_press:
                self.cursor_position = len(self.text)
                if not key_shift_down and not key_shiftr_down:
                    self.selection = self.cursor_position
            if key_end_press:
                self.cursor_position = 0
                if not key_shift_down and not key_shiftr_down:
                    self.selection = self.cursor_position

            width -= round(15 * gui.scale)
            t_len = ddt.get_text_w(self.text, font)
            if active and editline and editline != input_text:
                t_len += ddt.get_text_w(editline, font)
            if not click and not self.down_lock:
                cursor_x = ddt.get_text_w(self.text[:len(self.text) - self.cursor_position], font)
                if self.cursor_position == 0 or cursor_x < self.offset + round(15 * gui.scale) or cursor_x > self.offset + width:
                    if t_len > width:
                        self.offset = t_len - width

                        if cursor_x < self.offset:
                            self.offset = cursor_x - round(15 * gui.scale)

                            if self.offset < 0:
                                self.offset = 0
                    else:
                        self.offset = 0

            x -= self.offset


            if coll(select_rect): #coll((x - 15, y, width + 16, selection_height + 1)):
                # ddt.rect_r((x - 15, y, width + 16, 19), [50, 255, 50, 50], True)
                if click:
                    pre = 0
                    post = 0
                    if mouse_position[0] < x + 1:
                        self.cursor_position = len(self.text)
                    else:
                        for i in range(len(self.text)):
                            post = ddt.get_text_w(self.text[0:i + 1], font)
                            # pre_half = int((post - pre) / 2)

                            if x + pre - 0 <= mouse_position[0] <= x + post + 0:
                                diff = post - pre
                                if mouse_position[0] >= x + pre + int(diff / 2):
                                    self.cursor_position = len(self.text) - i - 1
                                else:
                                    self.cursor_position = len(self.text) - i
                                break
                            pre = post
                        else:
                            self.cursor_position = 0
                    self.selection = 0
                    self.down_lock = True

            if mouse_up:
                self.down_lock = False
            if self.down_lock:
                pre = 0
                post = 0
                text = self.text
                if secret:
                    text = '●' * len(self.text)
                if mouse_position[0] < x + 1:
                    self.selection = len(text)
                else:

                    for i in range(len(text)):
                        post = ddt.get_text_w(text[0:i + 1], font)
                        # pre_half = int((post - pre) / 2)

                        if x + pre - 0 <= mouse_position[0] <= x + post + 0:
                            diff = post - pre

                            if mouse_position[0] >= x + pre + int(diff / 2):
                                self.selection = len(text) - i - 1

                            else:
                                self.selection = len(text) - i

                            break
                        pre = post

                    else:
                        self.selection = 0

            text = self.text[0: len(self.text) - self.cursor_position]
            if secret:
                text = '●' * len(text)
            a = ddt.get_text_w(text, font)

            text = self.text[0: len(self.text) - self.selection]
            if secret:
                text = '●' * len(text)
            b = ddt.get_text_w(text, font)

            top = y
            if big:
                top -= 12 * gui.scale

            ddt.rect([a, 0, b - a, selection_height], [40, 120, 180, 255])

            if self.selection != self.cursor_position:
                inf_comp = 0
                text = self.get_selection(0)
                if secret:
                    text = '●' * len(text)
                space = ddt.text((0, 0), text, colour, font)
                text = self.get_selection(1)
                if secret:
                    text = '●' * len(text)
                space += ddt.text((0 + space - inf_comp, 0), text, [240, 240, 240, 255], font,
                                  bg=[40, 120, 180, 255], )
                text = self.get_selection(2)
                if secret:
                    text = '●' * len(text)
                ddt.text((0 + space - (inf_comp * 2), 0), text, colour, font)
            else:
                text = self.text
                if secret:
                    text = '●' * len(text)
                ddt.text((0, 0), text, colour, font)

            text = self.text[0: len(self.text) - self.cursor_position]
            if secret:
                text = '●' * len(text)
            space = ddt.get_text_w(text, font)

            if TextBox.cursor and self.selection == self.cursor_position:
                # ddt.line(x + space, y + 2, x + space, y + 15, colour)

                ddt.rect((0 + space, 0 + 2, 1 * gui.scale, 14 * gui.scale), colour)

            if click:
                self.selection = self.cursor_position

        else:
            width -= round(15 * gui.scale)
            text = self.text
            if secret:
                text = '●' * len(text)
            t_len = ddt.get_text_w(text, font)
            ddt.text((0, 0), text, colour, font)
            self.offset = 0
            if coll(rect) and not field_menu.active:
                gui.cursor_want = 2

        if active and editline != "" and editline != input_text:

            ex = ddt.text((space + 4, 0), editline, [240, 230, 230, 255], font)
            tw, th = ddt.get_text_wh(editline, font, max_x=2000)
            ddt.rect((space + round(4 * gui.scale), th + round(2 * gui.scale), ex, round(1 * gui.scale)),
                     [245, 245, 245, 255])

            rect = SDL_Rect(round(x + space + tw + 5 * gui.scale), round(y + th + 4 * gui.scale), 1, 1)
            SDL_SetTextInputRect(rect)

        animate_monitor_timer.set()

        text_box_canvas_hide_rect.x = 0
        text_box_canvas_hide_rect.y = 0

        #if self.offset:
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE)

        text_box_canvas_hide_rect.w = round(self.offset)
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0)
        SDL_RenderFillRect(renderer, text_box_canvas_hide_rect)

        text_box_canvas_hide_rect.w = round(t_len)
        text_box_canvas_hide_rect.x = round(self.offset + width + round(5 * gui.scale))
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0)
        SDL_RenderFillRect(renderer, text_box_canvas_hide_rect)


        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND)
        SDL_SetRenderTarget(renderer, gui.main_texture)

        text_box_canvas_rect.x = round(x)
        text_box_canvas_rect.y = round(y)
        SDL_RenderCopy(renderer, text_box_canvas, None, text_box_canvas_rect)


class TextBox:

    cursor = True

    def __init__(self):

        self.text = ""
        self.cursor_position = 0
        self.selection = 0
        self.down_lock = False



    def paste(self):

        if SDL_HasClipboardText():
            clip = SDL_GetClipboardText().decode('utf-8')

            if 'http://' in self.text and 'http://' in clip:
                self.text = ""

            clip = clip.rstrip(" ").lstrip(" ")
            clip = clip.replace('\n', ' ').replace('\r', '')

            self.eliminate_selection()
            self.text = self.text[0: len(self.text) - self.cursor_position] + clip + self.text[len(
                self.text) - self.cursor_position:]

    def copy(self):

        text = self.get_selection()
        if not text:
            text = self.text
        if text != "":
            SDL_SetClipboardText(text.encode('utf-8'))

    def set_text(self, text):

        self.text = text
        self.cursor_position = 0
        self.selection = 0

    def clear(self):
        self.text = ""

    def highlight_all(self):

        self.selection = len(self.text)
        self.cursor_position = 0

    def highlight_none(self):
        self.selection = 0
        self.cursor_position = 0

    def eliminate_selection(self):
        if self.selection != self.cursor_position:
            if self.selection > self.cursor_position:
                self.text = self.text[0: len(self.text) - self.selection] + self.text[len(self.text) - self.cursor_position:]
                self.selection = self.cursor_position
            else:
                self.text = self.text[0: len(self.text) - self.cursor_position] + self.text[len(self.text) - self.selection:]
                self.cursor_position = self.selection

    def get_selection(self, p=1):
        if self.selection != self.cursor_position:
            if p == 1:
                if self.selection > self.cursor_position:
                    return self.text[len(self.text) - self.selection : len(self.text) - self.cursor_position]

                else:
                    return self.text[len(self.text) -  self.cursor_position: len(self.text) - self.selection]
            if p == 0:
                return self.text[0: len(self.text) - max(self.cursor_position, self.selection)]
            if p == 2:
                return self.text[len(self.text) - min(self.cursor_position, self.selection):]

        else:
            return ""

    def draw(self, x, y, colour, active=True, secret=False, font=13, width=0, click=False, selection_height=18, big=False):

        # A little bit messy.
        # For now, this is set up so where 'width' is set > 0, the cursor position becomes editable,
        # otherwise it is fixed to end

        selection_height *= gui.scale

        if click is False:
            click = inp.mouse_click



        if width > 0 and active:

            rect = (x - 3, y - 2, width - 3, 21 * gui.scale)
            select_rect = (x - 20 * gui.scale, y - 2, width + 20 * gui.scale, 21 * gui.scale)
            if big:
                rect = (x - 3, y - 15 * gui.scale, width - 3, 35 * gui.scale)
                select_rect = (x - 50 * gui.scale, y - 15 * gui.scale, width + 50 * gui.scale, 35 * gui.scale)

            # Activate Menu
            if coll(rect):
                if right_click or level_2_right_click:
                    field_menu.activate(self)

            if click and field_menu.active:
                # field_menu.click()
                click = False

            # Add text from input
            if input_text != "":
                self.eliminate_selection()
                self.text = self.text[0: len(self.text) - self.cursor_position] + input_text + self.text[len(self.text) - self.cursor_position:]

            def g():
                if len(self.text) == 0 or self.cursor_position == len(self.text):
                    return None
                return self.text[len(self.text) - self.cursor_position - 1]
            def g2():
                if len(self.text) == 0 or self.cursor_position == 0:
                    return None
                return self.text[len(self.text) - self.cursor_position]
            def d():
                self.text = self.text[0: len(self.text) - self.cursor_position - 1] + self.text[
                                                                            len(self.text) - self.cursor_position:]
                self.selection = self.cursor_position

            # Ctrl + Backspace to delete word
            if inp.backspace_press and (key_ctrl_down or key_rctrl_down) and \
                self.cursor_position == self.selection and len(self.text) > 0 and self.cursor_position < len(self.text):
                while g() == " ":
                    d()
                while g() != " " and g() != None:
                    d()

            # Ctrl + left to move cursor back a word
            elif (key_ctrl_down or key_rctrl_down) and key_left_press:
                while g() == " ":
                    self.cursor_position += 1
                    if not key_shift_down:
                        self.selection = self.cursor_position
                while g() != None and g() not in " !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~":
                    self.cursor_position += 1
                    if not key_shift_down:
                        self.selection = self.cursor_position
                    if g() == " ":
                        self.cursor_position -= 1
                        if not key_shift_down:
                            self.selection = self.cursor_position
                        break

            # Ctrl + right to move cursor forward a word
            elif (key_ctrl_down or key_rctrl_down) and key_right_press:
                while g2() == " ":
                    self.cursor_position -= 1
                    if not key_shift_down:
                        self.selection = self.cursor_position
                while g2() != None and g2() not in " !\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~":
                    self.cursor_position -= 1
                    if not key_shift_down:
                        self.selection = self.cursor_position
                    if g2() == " ":
                        self.cursor_position += 1
                        if not key_shift_down:
                            self.selection = self.cursor_position
                        break

            # Handle normal backspace
            elif inp.backspace_press and len(self.text) > 0 and self.cursor_position < len(self.text):
                while inp.backspace_press and len(self.text) > 0 and self.cursor_position < len(self.text):
                    if self.selection != self.cursor_position:
                        self.eliminate_selection()
                    else:
                        self.text = self.text[0:len(self.text) - self.cursor_position- 1] + self.text[len(self.text) - self.cursor_position:]
                    inp.backspace_press -= 1
            elif inp.backspace_press and len(self.get_selection()) > 0:
                self.eliminate_selection()

            # Left and right arrow keys to move cursor
            if key_right_press:
                if self.cursor_position > 0:
                    self.cursor_position -= 1
                if not key_shift_down and not key_shiftr_down:
                    self.selection = self.cursor_position

            if key_left_press:
                if self.cursor_position < len(self.text):
                    self.cursor_position += 1
                if not key_shift_down and not key_shiftr_down:
                    self.selection = self.cursor_position

            # Paste via ctrl-v
            if key_ctrl_down and key_v_press:
                clip = SDL_GetClipboardText().decode('utf-8')
                self.eliminate_selection()
                self.text = self.text[0: len(self.text) - self.cursor_position] + clip + self.text[len(
                    self.text) - self.cursor_position:]

            if key_ctrl_down and key_c_press:
                self.copy()

            if key_ctrl_down and key_x_press:
                if len(self.get_selection()) > 0:
                    text = self.get_selection()
                    if text != "":
                        SDL_SetClipboardText(text.encode('utf-8'))
                    self.eliminate_selection()

            if key_ctrl_down and key_a_press:
                self.cursor_position = 0
                self.selection = len(self.text)


            # ddt.rect_r(rect, [255, 50, 50, 80], True)
            if coll(rect) and not field_menu.active:
                gui.cursor_want = 2


            fields.add(rect)


            # Delete key to remove text in front of cursor
            if key_del:
                if self.selection != self.cursor_position:
                    self.eliminate_selection()
                else:
                    self.text = self.text[0:len(self.text) - self.cursor_position] + self.text[len(
                        self.text) - self.cursor_position + 1:]
                    if self.cursor_position > 0:
                        self.cursor_position -= 1
                    self.selection = self.cursor_position

            if key_home_press:
                self.cursor_position = len(self.text)
                if not key_shift_down and not key_shiftr_down:
                    self.selection = self.cursor_position
            if key_end_press:
                self.cursor_position = 0
                if not key_shift_down and not key_shiftr_down:
                    self.selection = self.cursor_position

            if coll(select_rect):
                #ddt.rect_r((x - 15, y, width + 16, 19), [50, 255, 50, 50], True)
                if click:
                    pre = 0
                    post = 0
                    if mouse_position[0] < x + 1:
                        self.cursor_position = len(self.text)
                    else:
                        for i in range(len(self.text)):
                            post = ddt.get_text_w(self.text[0:i+1], font)
                            # pre_half = int((post - pre) / 2)

                            if x + pre - 0 <= mouse_position[0] <= x + post + 0:
                                diff = post - pre
                                if mouse_position[0] >= x + pre + int(diff / 2):
                                    self.cursor_position = len(self.text) - i - 1
                                else:
                                    self.cursor_position = len(self.text) - i
                                break
                            pre = post
                        else:
                            self.cursor_position = 0
                    self.selection = 0
                    self.down_lock = True



            if mouse_up:
                self.down_lock = False
            if self.down_lock:
                pre = 0
                post = 0
                if mouse_position[0] < x + 1:

                    self.selection = len(self.text)
                else:

                    for i in range(len(self.text)):
                        post = ddt.get_text_w(self.text[0:i + 1], font)
                        # pre_half = int((post - pre) / 2)

                        if x + pre - 0 <= mouse_position[0] <= x + post + 0:
                            diff = post - pre

                            if mouse_position[0] >= x + pre + int(diff / 2):
                                self.selection = len(self.text) - i - 1

                            else:
                                self.selection = len(self.text) - i

                            break
                        pre = post

                    else:
                        self.selection = 0

            a = ddt.get_text_w(self.text[0: len(self.text) - self.cursor_position], font)
            # print("")
            # print(self.selection)
            # print(self.cursor_position)

            b = ddt.get_text_w(self.text[0: len(self.text) - self.selection], font)

            #rint((a, b))

            top = y
            if big:
                top -= 12 * gui.scale

            ddt.rect([x + a, top, b - a, selection_height], [40, 120, 180, 255])

            if self.selection != self.cursor_position:
                inf_comp = 0
                space = ddt.text((x, y), self.get_selection(0), colour, font)
                space += ddt.text((x + space - inf_comp, y), self.get_selection(1), [240, 240, 240, 255], font, bg=[40, 120, 180, 255], )
                ddt.text((x + space - (inf_comp * 2), y), self.get_selection(2), colour, font)
            else:
                ddt.text((x, y), self.text, colour, font)


            space = ddt.get_text_w(self.text[0: len(self.text) - self.cursor_position], font)

            if TextBox.cursor and self.selection == self.cursor_position:
                #ddt.line(x + space, y + 2, x + space, y + 15, colour)

                if big:
                    #ddt.rect_r((xx + 1 , yy - 12 * gui.scale, 2 * gui.scale, 27 * gui.scale), colour, True)
                    ddt.rect((x + space, y - 15 * gui.scale + 2, 1 * gui.scale, 30 * gui.scale), colour)
                else:
                    ddt.rect((x + space, y + 2, 1 * gui.scale, 14 * gui.scale), colour)

            if click:
                self.selection = self.cursor_position

        else:
            if active:
                self.text += input_text
                if input_text != "":
                    self.cursor = True

                while inp.backspace_press and len(self.text) > 0:
                    self.text = self.text[:-1]
                    inp.backspace_press -= 1

                if key_ctrl_down and key_v_press:
                    self.paste()

            if secret:
                space = ddt.text((x, y), '●' * len(self.text), colour, font)
            else:
                space = ddt.text((x, y), self.text, colour, font)

            if active and TextBox.cursor:
                xx = x + space + 1
                yy = y + 3
                if big:
                    ddt.rect((xx + 1, yy - 12 * gui.scale, 2 * gui.scale, 27 * gui.scale), colour)
                else:
                    ddt.rect((xx, yy, 1 * gui.scale, 14 * gui.scale), colour)

        if active and editline != "" and editline != input_text:
            ex = ddt.text((x + space + 4, y), editline, [240, 230, 230, 255], font)
            tw, th = ddt.get_text_wh(editline, font, max_x=2000)
            ddt.rect((x + space + round(4 * gui.scale), (y + th) - round(4 * gui.scale), ex, round(1 * gui.scale)),
                     [245, 245, 245, 255])

            rect = SDL_Rect(round(round(x + space + tw + 5 * gui.scale)), round(y + th + 4 * gui.scale), 1, 1)
            SDL_SetTextInputRect(rect)

        animate_monitor_timer.set()


rename_text_area = TextBox()
gst_output_field = TextBox2()
gst_output_field.text = prefs.gst_output
search_text = TextBox()
rename_files = TextBox2()
sub_lyrics_a = TextBox2()
sub_lyrics_b = TextBox2()
sync_target = TextBox2()
edit_artist = TextBox2()
edit_album = TextBox2()
edit_title = TextBox2()
edit_album_artist = TextBox2()

rename_files.text = prefs.rename_tracks_template
if rename_files_previous:
    rename_files.text = rename_files_previous

text_plex_usr = TextBox2()
text_plex_pas = TextBox2()
text_plex_ser = TextBox2()

text_jelly_usr = TextBox2()
text_jelly_pas = TextBox2()
text_jelly_ser = TextBox2()

text_koel_usr = TextBox2()
text_koel_pas = TextBox2()
text_koel_ser = TextBox2()

text_air_usr = TextBox2()
text_air_pas = TextBox2()
text_air_ser = TextBox2()

text_spot_client = TextBox2()
text_spot_secret = TextBox2()

text_maloja_url = TextBox2()
text_maloja_key = TextBox2()

rename_folder = TextBox2()
rename_folder.text = prefs.rename_folder_template
if rename_folder_previous:
    rename_folder.text = rename_folder_previous

temp_dest = SDL_Rect(0, 0)


class GallClass:
    def __init__(self, size=250, save_out=True):
        self.gall = {}
        self.size = size
        self.queue = []
        self.key_list = []
        self.save_out = save_out
        self.i = 0
        self.lock = threading.Lock()
        self.limit = 60

    def get_file_source(self, track_object):

        global album_art_gen

        sources = album_art_gen.get_sources(track_object)

        if len(sources) == 0:
            return False, 0

        offset = album_art_gen.get_offset(track_object.fullpath, sources)
        return sources[offset], offset

    def worker_render(self):

        self.lock.acquire()
        #time.sleep(0.1)

        if search_over.active:
            while QuickThumbnail.queue:
                img = QuickThumbnail.queue.pop(0)
                response = urllib.request.urlopen(img.url)
                source_image = io.BytesIO(response.read())
                img.read_and_thumbnail(source_image, img.size, img.size)
                gui.update += 1

        while len(self.queue) > 0:

            if gui.halt_image_rendering:
                self.queue.clear()
                break

            self.i += 1

            try:
                #key = self.queue[0]
                key = self.queue.pop(0)
            except:
                print("thumb queue empty")
                break

            if key not in self.gall:
                order = [1, None, None, None]
                self.gall[key] = order
            else:
                order = self.gall[key]

            size = key[1]

            slow_load = False
            cache_load = False

            try:

                if True:
                    offset = 0
                    parent_folder = key[0].parent_folder_path
                    if parent_folder in folder_image_offsets:
                        offset = folder_image_offsets[parent_folder]
                    img_name = str(key[2]) + "-" + str(size) + '-' + str(key[0].index) + "-" + str(offset)
                    if prefs.cache_gallery and os.path.isfile(os.path.join(g_cache_dir, img_name + '.jpg')):
                        source_image = open(os.path.join(g_cache_dir, img_name + '.jpg'), 'rb')
                        #print('load from cache')
                        cache_load = True
                    else:
                        slow_load = True

                if slow_load:

                    source, c_offset = self.get_file_source(key[0])

                    if source is False:
                        order[0] = 0
                        self.gall[key] = order
                        #del self.queue[0]
                        continue

                    img_name = str(key[2]) + "-" + str(size) + '-' + str(key[0].index) + "-" + str(c_offset)

                    # gall_render_last_timer.set()

                    if prefs.cache_gallery and os.path.isfile(os.path.join(g_cache_dir, img_name + '.jpg')):
                        source_image = open(os.path.join(g_cache_dir, img_name + '.jpg'), 'rb')
                        print("slow load image")
                        cache_load = True

                    # elif source[0] == 1:
                    #     # print('tag')
                    #     source_image = io.BytesIO(album_art_gen.get_embed(key[0]))
                    #
                    # elif source[0] == 2:
                    #     try:
                    #         url = get_network_thumbnail_url(key[0])
                    #         response = urllib.request.urlopen(url)
                    #         source_image = response
                    #     except:
                    #         print("IMAGE NETWORK LOAD ERROR")
                    # else:
                    #     source_image = open(source[1], 'rb')
                    source_image = album_art_gen.get_source_raw(0, 0, key[0], subsource=source)

                g = io.BytesIO()
                g.seek(0)

                if cache_load:
                    g.write(source_image.read())

                else:
                    im = Image.open(source_image)

                    if im.mode != "RGB":
                        im = im.convert("RGB")
                    im.thumbnail((size, size), Image.ANTIALIAS)

                    im.save(g, 'BMP')
                    
                    if self.save_out and prefs.cache_gallery and not os.path.isfile(os.path.join(g_cache_dir, img_name + '.jpg')):
                        im.save(os.path.join(g_cache_dir, img_name + '.jpg'), 'JPEG', quality=95)

                g.seek(0)

                #source_image.close()

                order = [2, g, None, None]
                self.gall[key] = order

                gui.update += 1
                #del self.queue[0]

                time.sleep(0.001)

            except:
                # raise
                # print('ERROR: Image load failed on track: ' + key[0].fullpath)
                console.print('ERROR: Image load failed on track: ', level=3)
                console.print("- " + key[0].fullpath, level=5)
                order = [0, None, None, None]
                self.gall[key] = order
                gui.update += 1
                #del self.queue[0]

            if size < 150:
                random.shuffle(self.queue)

        if self.i > 0:
            self.i = 0
            return True
        else:
            return False


    def render(self, track, location, size=None):

        if gallery_load_delay.get() < 0.5:
            return

        x = round(location[0])
        y = round(location[1])

        # time.sleep(0.1)
        if size is None:
            size = self.size

        size = round(size)

        #offset = self.get_offset(pctl.master_library[index].fullpath, self.get_sources(index))
        if track.parent_folder_path in folder_image_offsets:
            offset = folder_image_offsets[track.parent_folder_path]
        else:
            offset = 0

        key = (track, size, offset)

        if key in self.gall:
            # print("old")

            order = self.gall[key]

            if order[0] == 0:
                # broken
                return False

            if order[0] == 1:
                # not done yet
                return False

            if order[0] == 2:
                # finish processing

                wop = rw_from_object(order[1])
                s_image = IMG_Load_RW(wop, 0)
                c = SDL_CreateTextureFromSurface(renderer, s_image)
                SDL_FreeSurface(s_image)
                tex_w = pointer(c_int(size))
                tex_h = pointer(c_int(size))
                SDL_QueryTexture(c, None, None, tex_w, tex_h)
                dst = SDL_Rect(x, y)
                dst.w = int(tex_w.contents.value)
                dst.h = int(tex_h.contents.value)

                order[0] = 3
                order[1] = None
                order[2] = c
                order[3] = dst
                self.gall[(track, size, offset)] = order

            if order[0] == 3:
                # ready

                order[3].x = x
                order[3].y = y
                order[3].x = int((size - order[3].w) / 2) + order[3].x
                order[3].y = int((size - order[3].h) / 2) + order[3].y
                SDL_RenderCopy(renderer, order[2], None, order[3])

                if (track, size, offset) in self.key_list:
                    self.key_list.remove((track, size, offset))
                self.key_list.append((track, size, offset))

                # Remove old images to conserve RAM usage
                if len(self.key_list) > self.limit:
                    gui.update += 1
                    key = self.key_list[0]
                    # while key in self.queue:
                    #     self.queue.remove(key)
                    if self.gall[key][2] is not None:
                        SDL_DestroyTexture(self.gall[key][2])
                    del self.gall[key]
                    del self.key_list[0]

                return True

        else:

            if key not in self.queue:
                self.queue.append(key)
                try:
                    self.lock.release()
                except:
                    pass


        return False


gall_ren = GallClass(album_mode_art_size)
tauon.gall_ren = gall_ren

pl_thumbnail = GallClass(save_out=False)


class ThumbTracks:
    def __init__(self):
        pass

    def path(self, track):

        source, offset = gall_ren.get_file_source(track)

        if source is False:  # No art
            return None

        image_name = track.album + track.parent_folder_path + str(offset)
        image_name = hashlib.md5(image_name.encode()).hexdigest()

        t_path = os.path.join(e_cache_dir, image_name + '.jpg')

        if os.path.isfile(t_path):
            return t_path

        source_image = album_art_gen.get_source_raw(0, 0, track, subsource=source)

        im = Image.open(source_image)
        if im.mode != "RGB":
            im = im.convert("RGB")
        im.thumbnail((1000, 1000), Image.ANTIALIAS)

        im.save(t_path, 'JPEG')

        return t_path


thumb_tracks = ThumbTracks()
tauon.thumb_tracks = thumb_tracks


def img_slide_update_gall(value, pause=True):

    global album_mode_art_size
    gui.halt_image_rendering = True

    album_mode_art_size = value

    clear_img_cache(False)
    if pause:
        gallery_load_delay.set()
        gui.frame_callback_list.append(TestTimer(0.6))
    gui.halt_image_rendering = False

    # Update sizes
    gall_ren.size = album_mode_art_size

    if album_mode_art_size > 150:
        prefs.thin_gallery_borders = False


def clear_img_cache(delete_disk=True):
    global album_art_gen
    album_art_gen.clear_cache()
    prefs.failed_artists.clear()
    prefs.failed_background_artists.clear()
    gall_ren.key_list = []

    while len(gall_ren.queue) > 0:
        time.sleep(0.01)

    for key, value in gall_ren.gall.items():
        SDL_DestroyTexture(value[2])
    gall_ren.gall = {}

    if delete_disk:
        dirs = [g_cache_dir, n_cache_dir, e_cache_dir]
        for direc in dirs:
            if os.path.isdir(direc):
                for item in os.listdir(direc):
                    path = os.path.join(direc, item)
                    os.remove(path)

    prefs.failed_artists.clear()
    for key, value in artist_list_box.thumb_cache.items():
        if value:
            SDL_DestroyTexture(value[0])
    artist_list_box.thumb_cache.clear()
    gui.update += 1


def clear_track_image_cache(track):

    gui.halt_image_rendering = True
    if gall_ren.queue:
        time.sleep(0.05)
    if gall_ren.queue:
        time.sleep(0.2)
    if gall_ren.queue:
        time.sleep(0.5)

    direc = os.path.join(g_cache_dir)
    if os.path.isdir(direc):
        for item in os.listdir(direc):
            n = item.split("-")
            if len(n) > 2 and n[2] == str(track.index):
                os.remove(os.path.join(direc, item))
                print("Cleared cache thumbnail: " + os.path.join(direc, item))

    keys = set()
    for key, value in gall_ren.gall.items():
        if key[0] == track:
            SDL_DestroyTexture(value[2])
            if key not in keys:
                keys.add(key)
    for key in keys:
        del gall_ren.gall[key]
        if key in gall_ren.key_list:
            gall_ren.key_list.remove(key)

    gui.halt_image_rendering = False
    album_art_gen.clear_cache()

class ImageObject():
    def __init__(self):
        self.index = 0
        self.texture = None
        self.rect = None
        self.request_size = (0, 0)
        self.original_size = (0, 0)
        self.actual_size = (0, 0)
        self.source = ""
        self.offset = 0
        self.stats = True
        self.format = ""




class AlbumArt():
    def __init__(self):
        self.image_types = {'jpg', 'JPG', 'jpeg', 'JPEG', 'PNG', 'png', 'BMP', 'bmp', 'GIF', 'gif'}
        self.art_folder_names = {'art', 'scans', 'scan', 'booklet', 'images', 'image', 'cover',
                                 'covers', 'coverart', 'albumart', 'gallery', 'jacket', 'artwork',
                                 'bonus', 'bk', 'cover artwork', 'cover art'}
        self.source_cache = {}
        self.image_cache = []
        self.current_wu = None

        self.blur_texture = None
        self.blur_rect = None
        self.loaded_bg_type = 0

        self.download_in_progress = False
        self.downloaded_image = None
        self.downloaded_track = None

        self.base64cache = (0, 0, "")
        self.processing64on = None

        self.bin_cached = (None, None, None)  # track, subsource, bin

        self.embed_cached = (None, None)

    def async_download_image(self, track, subsource):

        self.downloaded_image = album_art_gen.get_source_raw(0, 0, track, subsource=subsource)
        self.downloaded_track = track
        self.download_in_progress = False
        gui.update += 1


    def get_info(self, track_object):

        sources = self.get_sources(track_object)
        if len(sources) == 0:
            return None

        offset = self.get_offset(track_object.fullpath, sources)

        o_size = (0, 0)
        format = "ERROR"

        for item in self.image_cache:
            if item.index == track_object.index and item.offset == offset:
                o_size = item.original_size
                format = item.format
                break

        else:
            # Hacky fix
            # A quirk is the index stays of the cached image.
            # This workaround can be done since (currently) cache has max size of 1
            if self.image_cache:
                o_size = self.image_cache[0].original_size
                format = self.image_cache[0].format

        return [sources[offset][0], len(sources), offset, o_size, format]

    def get_sources(self, tr):

        filepath = tr.fullpath
        ext = tr.file_ext

        # Check if source list already exists, if not, make it
        if tr.index in self.source_cache:
            return self.source_cache[tr.index]
        else:
            pass

        source_list = []  # istag,

        # Source type the is first element in list
        # 0 = File
        # 1 = Embedded in tag
        # 2 = Network location

        if tr.is_network:
            # Add url if network target
            if tr.art_url_key:
                source_list.append([2, tr.art_url_key])
        else:
            # Check for local image files
            try:
                direc = os.path.dirname(filepath)
                items_in_dir = os.listdir(direc)
            except:
                print("Error loading directroy")
                return []

        # Check for embedded image
        try:
            pic = self.get_embed(tr)
            if pic:
                source_list.append([1, filepath])
        except:
            pass

        if not tr.is_network:

            dirs_in_dir = [subdirec for subdirec in items_in_dir if
                           os.path.isdir(os.path.join(direc, subdirec)) and subdirec.lower() in self.art_folder_names]

            ins = len(source_list)
            for i in range(len(items_in_dir)):
                if os.path.splitext(items_in_dir[i])[1][1:] in self.image_types:
                    dir_path = os.path.join(direc, items_in_dir[i]).replace('\\', "/")
                    # The image name "Folder" is likely desired to be prioritised over other names
                    if os.path.splitext(os.path.basename(dir_path))[0] in ("Folder", "folder"):
                        source_list.insert(ins, [0, dir_path])
                    else:
                        source_list.append([0, dir_path])

            for i in range(len(dirs_in_dir)):
                subdirec = os.path.join(direc, dirs_in_dir[i])
                items_in_dir2 = os.listdir(subdirec)

                for y in range(len(items_in_dir2)):
                    if os.path.splitext(items_in_dir2[y])[1][1:] in self.image_types:
                        dir_path = os.path.join(subdirec, items_in_dir2[y]).replace('\\', "/")
                        source_list.append([0, dir_path])

        self.source_cache[tr.index] = source_list

        return source_list


    def fast_display(self, index, location, box, source, offset):

        # Renders cached image only by given size for faster performance

        found_unit = None
        max_h = 0

        for unit in self.image_cache:
            if unit.source == source[offset][1]:
                if unit.actual_size[1] > max_h:
                    max_h = unit.actual_size[1]
                    found_unit = unit

        if found_unit == None:
            return 1

        unit = found_unit

        temp_dest.x = round(location[0])
        temp_dest.y = round(location[1])

        temp_dest.w = unit.original_size[0]#round(box[0])
        temp_dest.h = unit.original_size[1]#round(box[1])

        bh = round(box[1])
        bw = round(box[0])

        if prefs.zoom_art:
            temp_dest.w, temp_dest.h = fit_box((unit.original_size[0], unit.original_size[1]), box)
        else:

            # Constrain image to given box
            if temp_dest.w > bw:
                temp_dest.w = bw
                temp_dest.h = int(bw * (unit.original_size[1] / unit.original_size[0]))

            if temp_dest.h > bh:
                temp_dest.h = bh
                temp_dest.w = int(temp_dest.h * (unit.original_size[0] / unit.original_size[1]))

            # prevent scaling larger than original image size
            if temp_dest.w > unit.original_size[0] or temp_dest.h > unit.original_size[1]:
                temp_dest.w = unit.original_size[0]
                temp_dest.h = unit.original_size[1]

        # center the image
        temp_dest.x = int((box[0] - temp_dest.w) / 2) + temp_dest.x
        temp_dest.y = int((box[1] - temp_dest.h) / 2) + temp_dest.y

        # render the image
        SDL_RenderCopy(renderer, unit.texture, None, temp_dest)
        style_overlay.hole_punches.append(temp_dest)

        gui.art_drawn_rect = (temp_dest.x, temp_dest.y, temp_dest.w, temp_dest.h)

        return 0

    def open_external(self, track_object):

        index = track_object.index

        source = self.get_sources(track_object)
        if len(source) == 0:
            return 0

        offset = self.get_offset(track_object.fullpath, source)

        if track_object.is_network:
            show_message("Saving network images not implemented")
            return 0
        if source[offset][0] > 0:
            pic = album_art_gen.get_embed(track_object)
            if not pic:
                show_message("Image save error.", "No embedded album art found file.", mode='warning')
                return 0

            source_image = io.BytesIO(pic)
            im = Image.open(source_image)
            source_image.close()

            ext = "." + im.format.lower()
            if im.format == "JPEG":
                ext = ".jpg"
            target = os.path.join(cache_directory, "open-image")
            if not os.path.exists(target):
                os.makedirs(target)
            target = os.path.join(target, "embed-" + str(im.height) + "px-" + str(track_object.index) + ext)

            if len(pic) > 30:
                with open(target, 'wb') as w:
                    w.write(pic)

        else:
            target = source[offset][1]

        if system == "windows" or msys:
            os.startfile(target)
        elif macos:
            subprocess.call(["open", target])
        else:
            subprocess.call(["xdg-open", target])

        return 0

    def cycle_offset(self, track_object, reverse=False):

        filepath = track_object.fullpath
        sources = self.get_sources(track_object)
        if len(sources) == 0:
            return 0
        parent_folder = os.path.dirname(filepath)
        # Find cached offset
        if parent_folder in folder_image_offsets:

            if reverse:
                folder_image_offsets[parent_folder] -= 1
            else:
                folder_image_offsets[parent_folder] += 1

            folder_image_offsets[parent_folder] %= len(sources)
        return 0

    def cycle_offset_reverse(self, track_object):
        self.cycle_offset(track_object, True)

    def get_offset(self, filepath, source):

        # Check if folder offset already exsts, if not, make it
        parent_folder = os.path.dirname(filepath)

        if parent_folder in folder_image_offsets:

            # Reset the offset if greater than number of images available
            if folder_image_offsets[parent_folder] > len(source) - 1:
                folder_image_offsets[parent_folder] = 0
        else:
            folder_image_offsets[parent_folder] = 0

        return folder_image_offsets[parent_folder]

    def get_embed(self, track):

        #cached = self.embed_cached
        #if cached[0] == track:
        #    # print("used cached")
        #    return cached[1]

        filepath = track.fullpath

        pic = None

        if track.file_ext == 'MP3':
            try:
                tag = mutagen.id3.ID3(filepath)
                frame = tag.getall("APIC")
                pic = frame[0].data
            except Exception as e:
                pass

            if len(pic) < 30:
                pic = None

        elif track.file_ext == 'FLAC':
            tag = Flac(filepath)
            tag.read(True)
            if tag.has_picture and len(tag.picture) > 30:
                pic = tag.picture

        elif track.file_ext == 'APE':
            tag = Ape(filepath)
            tag.read()
            if tag.has_picture and len(tag.picture) > 30:
                pic = tag.picture

        elif track.file_ext == 'M4A':
            tag = M4a(filepath)
            tag.read(True)
            if tag.has_picture and len(tag.picture) > 30:
                pic = tag.picture

        elif track.file_ext == 'OPUS' or track.file_ext == 'OGG':
            tag = Opus(filepath)
            tag.read()
            if tag.has_picture and len(tag.picture) > 30:
                a = io.BytesIO(base64.b64decode(tag.picture))
                a.seek(0)
                image = parse_picture_block(a)
                a.close()
                pic = image

        # self.embed_cached = (track, pic)
        return pic


    def get_source_raw(self, offset, sources, track, subsource=None):

        source_image = None

        if subsource is None:
            subsource = sources[offset]

        if subsource[0] == 1:
            # Target is a embedded image\\\
            pic = self.get_embed(track)
            assert pic
            source_image = io.BytesIO(pic)

        elif subsource[0] == 2:
            try:
                if track.file_ext == "RADIO" or track.file_ext == "Spotify":
                    if pctl.radio_image_bin:
                        return pctl.radio_image_bin

                cached_path = os.path.join(n_cache_dir, hashlib.md5(track.art_url_key.encode()).hexdigest()[:12])
                if os.path.isfile(cached_path):
                    source_image = open(cached_path, 'rb')
                else:
                    if track.file_ext == "SUB":
                        source_image = subsonic.get_cover(track)
                    elif track.file_ext == "JELY":
                        source_image = jellyfin.get_cover(track)
                    else:
                        response = urllib.request.urlopen(get_network_thumbnail_url(track))
                        source_image = io.BytesIO(response.read())
                    if source_image:
                        f = open(cached_path, 'wb')
                        f.write(source_image.read())
                        f.close()
                        source_image.seek(0)

            except:
                # raise
                pass

        else:
            source_image = open(subsource[1], 'rb')

        return source_image

    def get_base64(self, track, size):

        # Wait if an identical track is already being processed
        if self.processing64on == track:
            t = 0
            while True:
                if self.processing64on is None:
                    break
                time.sleep(0.05)
                t += 1
                if t > 20:
                    break

        cahced = self.base64cache
        if track == cahced[0] and size == cahced[1]:
            return cahced[2]

        self.processing64on = track

        filepath = track.fullpath
        sources = self.get_sources(track)

        if len(sources) == 0:
            self.processing64on = None
            return False

        offset = self.get_offset(filepath, sources)

        # Get source IO
        source_image = self.get_source_raw(offset, sources, track)

        if source_image is None:
            self.processing64on = None
            return ""

        im = Image.open(source_image)
        if im.mode != "RGB":
            im = im.convert("RGB")
        im.thumbnail(size, Image.ANTIALIAS)
        buff = io.BytesIO()
        im.save(buff, format="JPEG")
        sss = base64.b64encode(buff.getvalue())

        self.base64cache = (track, size, sss)
        self.processing64on = None
        return sss

    def get_background(self, track):
        print("Find background...")
        # Determine artist name to use
        artist = get_artist_safe(track)
        if not artist:
            return None

        # Check cache for existing image
        path = os.path.join(b_cache_dir, artist)
        if os.path.isfile(path):
            print("Load cached background")
            return open(path, "rb")

        # Check we've not already attempted a search for this artist
        if artist in prefs.failed_background_artists:
            return None

        # Get artist MBID
        try:
            s = musicbrainzngs.search_artists(artist, limit=1)
            artist_id = s['artist-list'][0]['id']
        except:
            print("Failed to find artist MBID for: %s" % artist)
            prefs.failed_background_artists.append(artist)
            return None

        # Search fanart.tv for background
        try:

            r = requests.get("http://webservice.fanart.tv/v3/music/" \
                             + artist_id + "?api_key=" + prefs.fatvap, timeout=(4, 10))

            artlink = r.json()['artistbackground'][0]['url']

            response = urllib.request.urlopen(artlink)
            info = response.info()

            assert info.get_content_maintype() == 'image'

            t = io.BytesIO()
            t.seek(0)
            t.write(response.read())
            t.seek(0, 2)
            l = t.tell()
            t.seek(0)

            assert l > 1000

            # Cache image for future use
            f = open(path, "wb")
            f.write(t.read())
            f.close()

            t.seek(0)
            return t

        except:
            print("Failed to find fanart background for: %s" % artist)
            prefs.failed_background_artists.append(artist)
            return None


    def get_blur_im(self, track):

        source_image = None
        self.loaded_bg_type = 0
        if prefs.enable_fanart_bg:
            source_image = self.get_background(track)
            if source_image:
                self.loaded_bg_type = 1

        if source_image is None:
            filepath = track.fullpath
            sources = self.get_sources(track)

            if len(sources) == 0:
                return False

            offset = self.get_offset(filepath, sources)

            source_image = self.get_source_raw(offset, sources, track)

        if source_image is None:
            return None

        im = Image.open(source_image)

        ox_size = im.size[0]
        oy_size = im.size[1]

        format = im.format
        if im.format == "JPEG":
            format = "JPG"

        # print(im.size)
        if im.mode != "RGB":
            im = im.convert("RGB")


        ratio = window_size[0] / ox_size
        ratio += 0.2

        if (oy_size * ratio) - ((oy_size * ratio) // 4) < window_size[1]:
            print("Adjust bg vertical")
            ratio = window_size[1] / (oy_size - (oy_size // 4))
            ratio += 0.2

        new_x = round(ox_size * ratio)
        new_y = round(oy_size * ratio)

        im = im.resize((new_x, new_y))

        if self.loaded_bg_type == 1:
            artist = get_artist_safe(track)
            if artist and artist in prefs.bg_flips:
                im = im.transpose(Image.FLIP_LEFT_RIGHT)

        if ox_size < 500 or prefs.art_bg_always_blur:
            im = im.filter(ImageFilter.GaussianBlur(prefs.art_bg_blur))

        g = io.BytesIO()
        g.seek(0)

        a_channel = Image.new('L', im.size, 255)  # 'L' 8-bit pixels, black and white
        im.putalpha(a_channel)

        im.save(g, 'PNG')
        g.seek(0)

        # source_image.close()

        return g


    def save_thumb(self, track_object, size, save_path, png=False, zoom=False):

        filepath = track_object.fullpath
        sources = self.get_sources(track_object)

        if len(sources) == 0:
            print("Error thumbnailing; no source images found")
            return False

        offset = self.get_offset(filepath, sources)
        source_image = self.get_source_raw(offset, sources, track_object)

        im = Image.open(source_image)
        if im.mode != "RGB":
            im = im.convert("RGB")

        if not zoom:
            im.thumbnail(size, Image.ANTIALIAS)
        else:
            w, h = im.size
            if w != h:
                m = min(w, h)
                im = im.crop((
                    (w - m) / 2,
                    (h - m) / 2,
                    (w + m) / 2,
                    (h + m) / 2,
                ))

            im = im.resize(size, Image.ANTIALIAS)

        if not save_path:
            g = io.BytesIO()
            g.seek(0)
            if png:
                im.save(g, 'PNG')
            else:
                im.save(g, 'JPEG')
            g.seek(0)
            return g

        if png:
            im.save(save_path + '.png', 'PNG')
        else:
            im.save(save_path + '.jpg', 'JPEG')


    def display(self, track, location, box, fast=False, theme_only=False):

        index = track.index
        filepath = track.fullpath

        if prefs.colour_from_image and track.album != gui.theme_temp_current and box[0] != 115:
            if track.album in gui.temp_themes:
                global colours
                colours = gui.temp_themes[track.album]
                gui.theme_temp_current = track.album

        source = self.get_sources(track)

        if len(source) == 0:
            return 1

        offset = self.get_offset(filepath, source)

        if not theme_only:
            # Check if request matches previous
            if self.current_wu is not None and self.current_wu.source == source[offset][1] and \
                            self.current_wu.request_size == box:
                self.render(self.current_wu, location)
                return 0

            if fast:
                return self.fast_display(track, location, box, source, offset)

            # Check if cached
            for unit in self.image_cache:
                if unit.index == index and unit.request_size == box and unit.offset == offset:
                    self.render(unit, location)
                    return 0

        close = True
        # Render new...
        try:

            # Get source IO
            if source[offset][0] == 1:
                # Target is a embedded image
                #source_image = io.BytesIO(self.get_embed(track))
                source_image = self.get_source_raw(0, 0, track, source[offset])

            elif source[offset][0] == 2:
                try:
                    close = False
                    # We want to download the image asynchronously as to not block the UI
                    if self.downloaded_image and self.downloaded_track == track:
                        source_image = self.downloaded_image

                    elif self.download_in_progress:
                        return 0

                    else:
                        self.download_in_progress = True
                        shoot_dl = threading.Thread(target=self.async_download_image, args=([track, source[offset]]))
                        shoot_dl.daemon = True
                        shoot_dl.start()

                        # We'll block with a small timeout to avoid unwanted flashing between frames
                        s = 0
                        while self.download_in_progress:
                            s += 1
                            time.sleep(0.01)
                            if s > 20:  # 200 ms
                                break

                        if self.downloaded_track != track:
                            return

                        assert self.downloaded_image
                        source_image = self.downloaded_image


                except:
                    print("IMAGE NETWORK LOAD ERROR")
                    raise

            else:
                #source_image = open(source[offset][1], 'rb')
                source_image = self.get_source_raw(0, 0, track, source[offset])

            # Generate
            g = io.BytesIO()
            g.seek(0)
            im = Image.open(source_image)
            o_size = im.size

            format = im.format
            if im.format == "JPEG":
                format = "JPG"

            if im.mode != "RGB":
                im = im.convert("RGB")

            if not theme_only:

                if prefs.zoom_art:
                    new_size = fit_box(o_size, box)
                    im = im.resize(new_size, Image.ANTIALIAS)
                else:
                    im.thumbnail((box[0], box[1]), Image.ANTIALIAS)
                im.save(g, 'BMP')
                g.seek(0)

            # Processing for "Carbon" theme
            if track == pctl.playing_object() and gui.theme_name == "Carbon" and track.parent_folder_path != colours.last_album:

                # Find main image colours
                im.thumbnail((50, 50), Image.ANTIALIAS)
                pixels = im.getcolors(maxcolors=2500)
                pixels = sorted(pixels, key=lambda x: x[0], reverse=True)[:]
                colour = pixels[0][1]

                # Try and find a colour that is not grayscale
                for c in pixels:
                    cc = c[1]
                    av = sum(cc) / 3
                    if abs(cc[0] - av) > 10 or abs(cc[1] - av) > 10 or abs(cc[2] - av) > 10:
                        colour = cc
                        break

                h_colour = rgb_to_hls(colour[0], colour[1], colour[2])

                l = .51
                s = .44

                hh = h_colour[0]
                if 0.14 < hh < 0.3:  # Yellow and green are hard to read text on, so lower the luminance for those
                    l = .45
                if check_equal(colour):  # Default to theme purple if source colour was grayscale
                    hh = 0.72

                colours.bottom_panel_colour = hls_to_rgb(hh, l, s)
                colours.last_album = track.parent_folder_path


            # Processing for "Auto-theme" setting
            if prefs.colour_from_image and box[0] != 115 and track.album != gui.theme_temp_current \
                    and track.album not in gui.temp_themes: # and pctl.master_library[index].parent_folder_path != colours.last_album: #mark2233
                colours.last_album = track.parent_folder_path

                colours = copy.deepcopy(colours)

                im.thumbnail((50, 50), Image.ANTIALIAS)
                pixels = im.getcolors(maxcolors=2500)
                # print(pixels)
                pixels = sorted(pixels, key=lambda x: x[0], reverse=True)[:]
                # print(pixels)

                min_colour_varience = 75

                x_colours = []
                for item in pixels:
                    colour = item[1]
                    for cc in x_colours:
                        if abs(colour[0] - cc[0]) < min_colour_varience and abs(
                                        colour[1] - cc[1]) < min_colour_varience and abs(
                                        colour[2] - cc[2]) < min_colour_varience:
                            break
                    else:
                        x_colours.append(colour)

                # print(x_colours)
                colours.playlist_panel_bg = colours.side_panel_background
                colours.playlist_box_background = colours.side_panel_background


                colours.playlist_panel_background = x_colours[0] + (255,)
                if len(x_colours) > 1:
                    colours.side_panel_background = x_colours[1] + (255,)
                    colours.playlist_box_background = colours.side_panel_background
                    if len(x_colours) > 2:
                        colours.title_text = x_colours[2] + (255,)
                        colours.title_playing = x_colours[2] + (255,)
                        if len(x_colours) > 3:
                            colours.artist_text = x_colours[3] + (255,)
                            colours.artist_playing = x_colours[3] + (255,)
                            if len(x_colours) > 4:
                                colours.playlist_box_background =  x_colours[4] + (255,)

                colours.queue_background = colours.side_panel_background
                # Check artist text colour
                if contrast_ratio(colours.artist_text, colours.playlist_panel_background) < 1.9:

                    black = [25, 25, 25, 255]
                    white = [220, 220, 220, 255]

                    con_b = contrast_ratio(black, colours.playlist_panel_background)
                    con_w = contrast_ratio(white, colours.playlist_panel_background)

                    choice = black
                    if con_w > con_b:
                        choice = white

                    colours.artist_text = choice
                    colours.artist_playing = choice

                # Check title text colour
                if contrast_ratio(colours.title_text, colours.playlist_panel_background) < 1.9:

                    black = [60, 60, 60, 255]
                    white = [180, 180, 180, 255]

                    con_b = contrast_ratio(black, colours.playlist_panel_background)
                    con_w = contrast_ratio(white, colours.playlist_panel_background)

                    choice = black
                    if con_w > con_b:
                        choice = white

                    colours.title_text = choice
                    colours.title_playing = choice


                if test_lumi(colours.side_panel_background) < 0.50:
                    colours.side_bar_line1 = [25, 25, 25, 255]
                    colours.side_bar_line2 = [35, 35, 35, 255]
                else:
                    colours.side_bar_line1 = [250, 250, 250, 255]
                    colours.side_bar_line2 = [235, 235, 235, 255]

                colours.album_text = colours.title_text
                colours.album_playing = colours.title_playing

                gui.pl_update = 1

                prcl = 100 - int(test_lumi(colours.playlist_panel_background) * 100)

                if prcl > 45:
                    ce = alpha_blend([0, 0, 0, 180], colours.playlist_panel_background) #[40, 40, 40, 255]
                    colours.index_text = ce
                    colours.index_playing = ce
                    colours.time_text = ce
                    colours.bar_time = ce
                    colours.folder_title = ce
                    colours.star_line = [60, 60, 60, 255]
                    colours.row_select_highlight = [0, 0, 0, 30]
                    colours.row_playing_highlight = [0, 0, 0, 20]
                    colours.gallery_background = rgb_add_hls(colours.playlist_panel_background, 0, -0.03, -0.03)
                else:
                    ce = alpha_blend([255, 255, 255, 160], colours.playlist_panel_background) #[165, 165, 165, 255]
                    colours.index_text = ce
                    colours.index_playing = ce
                    colours.time_text = ce
                    colours.bar_time = ce
                    colours.folder_title = ce
                    colours.star_line = ce #[150, 150, 150, 255]
                    colours.row_select_highlight = [255, 255, 255, 12]
                    colours.row_playing_highlight = [255, 255, 255, 8]
                    colours.gallery_background = rgb_add_hls(colours.playlist_panel_background, 0, 0.03, 0.03)

                gui.temp_themes[track.album] = copy.deepcopy(colours)
                colours = gui.temp_themes[track.album]
                gui.theme_temp_current = track.album


            if theme_only:
                return

            wop = rw_from_object(g)
            s_image = IMG_Load_RW(wop, 0)
            # print(IMG_GetError())

            c = SDL_CreateTextureFromSurface(renderer, s_image)

            tex_w = pointer(c_int(0))
            tex_h = pointer(c_int(0))

            SDL_QueryTexture(c, None, None, tex_w, tex_h)

            dst = SDL_Rect(round(location[0]), round(location[1]))
            dst.w = int(tex_w.contents.value)
            dst.h = int(tex_h.contents.value)

            # Clean uo
            SDL_FreeSurface(s_image)
            g.close()
            # if close:
            #     source_image.close()

            unit = ImageObject()
            unit.index = index
            unit.texture = c
            unit.rect = dst
            unit.request_size = box
            unit.original_size = o_size
            unit.actual_size = (dst.w, dst.h)
            unit.source = source[offset][1]
            unit.offset = offset
            unit.format = format

            self.current_wu = unit
            self.image_cache.append(unit)

            self.render(unit, location)

            if len(self.image_cache) > 5 or (prefs.colour_from_image and len(self.image_cache) > 1):
                SDL_DestroyTexture(self.image_cache[0].texture)
                del self.image_cache[0]

            # temp fix
            global move_on_title
            global playlist_hold
            global quick_drag
            quick_drag = False
            move_on_title = False
            playlist_hold = False

        except Exception as error:

            # print("Image processing error: " + str(error))
            console.print("Image load error")
            console.print("-- Associated track: " + track.fullpath)
            console.print("-- Exception: " + str(error))

            self.current_wu = None
            try:
                del self.source_cache[index][offset]
            except:
                print(" -- Error, no source cache?")

            return 1

        return 0

    def render(self, unit, location):

        rect = unit.rect

        gui.art_aspect_ratio = unit.actual_size[0] / unit.actual_size[1]

        rect.x = round(int((unit.request_size[0] - unit.actual_size[0]) / 2) + location[0])
        rect.y = round(int((unit.request_size[1] - unit.actual_size[1]) / 2) + location[1])

        style_overlay.hole_punches.append(rect)

        SDL_RenderCopy(renderer, unit.texture, None, rect)

        gui.art_drawn_rect = (rect.x, rect.y, rect.w, rect.h)

    def clear_cache(self):

        for unit in self.image_cache:
            SDL_DestroyTexture(unit.texture)

        self.image_cache.clear()
        self.source_cache.clear()
        self.current_wu = None
        self.downloaded_track = None

        self.base64cahce = (0, 0, "")
        self.processing64on = None
        self.bin_cached = (None, None, None)
        self.loading_bin = (None, None)
        self.embed_cached = (None, None)

        gui.temp_themes.clear()
        gui.theme_temp_current = -1
        colours.last_album = ""

#from t_modules.t_art_render import AlbumArt

album_art_gen = AlbumArt()


# 0 - blank
# 1 - preparing first
# 2 - render first
# 3 - preparing 2nd

class StyleOverlay:

    def __init__(self):

        self.min_on_timer = Timer()
        self.fade_on_timer = Timer(0)
        self.fade_off_timer = Timer()

        self.stage = 0

        self.im = None

        self.a_texture = None
        self.a_rect = None

        self.b_texture = None
        self.b_rect = None

        self.a_type = 0
        self.b_type = 0

        self.window_size = None
        self.parent_path = None

        self.hole_punches = []
        self.hole_refills = []

        self.go_to_sleep = False

        self.current_track_album = "none"
        self.current_track_id = -1


    def worker(self):

        if self.stage == 0:

            if prefs.bg_showcase_only and not gui.combo_mode:
                return

            if pctl.playing_ready() and self.min_on_timer.get() > 0:

                track = pctl.playing_object()

                self.window_size = copy.copy(window_size)
                self.parent_path = track.parent_folder_path
                self.current_track_id = track.index
                self.current_track_album = track.album

                try:
                    self.im = album_art_gen.get_blur_im(track)
                except Exception as e:
                    print("Blur blackground error")
                    print(str(e))
                    print(track.fullpath)

                if self.im is None or self.im is False:
                    if self.a_texture:
                        self.stage = 2
                        self.fade_off_timer.set()
                        self.go_to_sleep = True
                        return
                    else:
                        self.flush()
                        self.min_on_timer.force_set(-4)
                        return

                self.stage = 1
                gui.update += 1
                return

    def flush(self):

        if self.a_texture is not None:
            SDL_DestroyTexture(self.a_texture)
            self.a_texture = None
        if self.b_texture is not None:
            SDL_DestroyTexture(self.b_texture)
            self.b_texture = None
        self.min_on_timer.force_set(-0.2)
        self.parent_path = "None"
        self.stage = 0
        tm.ready("worker")
        gui.style_worker_timer.set()
        gui.delay_frame(0.25)
        gui.update += 1

    def display(self):

        if self.min_on_timer.get() < 0:
            return

        if self.stage == 1:

            wop = rw_from_object(self.im)
            s_image = IMG_Load_RW(wop, 0)

            c = SDL_CreateTextureFromSurface(renderer, s_image)

            tex_w = pointer(c_int(0))
            tex_h = pointer(c_int(0))

            SDL_QueryTexture(c, None, None, tex_w, tex_h)

            dst = SDL_Rect(round(-40, 0))
            dst.w = int(tex_w.contents.value)
            dst.h = int(tex_h.contents.value)

            # Clean uo
            SDL_FreeSurface(s_image)
            self.im.close()

            #SDL_SetTextureAlphaMod(c, 10)
            self.fade_on_timer.set()

            if self.a_texture is not None:
                self.b_texture = self.a_texture
                self.b_rect = self.a_rect
                self.b_type = self.a_type

            self.a_texture = c
            self.a_rect = dst
            self.a_type = album_art_gen.loaded_bg_type

            self.stage = 2

            gui.update += 1


        if self.stage == 2:
            track = pctl.playing_object()
            if not self.go_to_sleep and self.b_texture is None and self.current_track_id != track.index:

                if not track.album:
                    self.stage = 0
                else:
                    self.current_track_id = track.index
                    if (self.parent_path != pctl.playing_object().parent_folder_path or self.current_track_album != pctl.playing_object().album):
                        self.stage = 0

        if prefs.bg_showcase_only:
            if not gui.combo_mode:
                return

        t = self.fade_on_timer.get()
        SDL_SetRenderTarget(renderer, gui.main_texture_overlay_temp)
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255)
        SDL_RenderClear(renderer)

        if self.a_texture is not None:
            if self.window_size != window_size:
                self.flush()

        if self.b_texture is not None:

            self.b_rect.y = 0 - self.b_rect.h // 4
            if self.b_type == 1:
                self.b_rect.y = 0

            if t < 0.4:

                SDL_RenderCopy(renderer, self.b_texture, None, self.b_rect)

            else:
                SDL_DestroyTexture(self.b_texture)
                self.b_texture = None
                self.b_rect = None


        if self.a_texture is not None:

            self.a_rect.y = 0 - self.a_rect.h // 4
            if self.a_type == 1:
                self.a_rect.y = 0

            if t < 0.4:
                fade = round(t / 0.4 * 255)
                gui.update += 1

            else:
                fade = 255

            if self.go_to_sleep:
                t = self.fade_off_timer.get()
                gui.update += 1

                if t < 1:
                    fade = 255
                elif t < 1.4:
                    fade = 255 - round((t - 1) / 0.4 * 255)
                else:
                    self.go_to_sleep = False
                    self.flush()
                    return

            if prefs.bg_showcase_only:
                tb = SDL_Rect(0, 0, window_size[0], gui.panelY)
                bb = SDL_Rect(0, window_size[1] - gui.panelBY, window_size[0], gui.panelBY)
                self.hole_punches.append(tb)
                self.hole_punches.append(bb)

            # Center image
            if window_size[0] < 900 * gui.scale:
                self.a_rect.x = (window_size[0] // 2) - self.a_rect.w // 2
            else:
                self.a_rect.x = -40

            SDL_SetRenderTarget(renderer, gui.main_texture_overlay_temp)

            SDL_SetTextureAlphaMod(self.a_texture, fade)
            SDL_RenderCopy(renderer, self.a_texture, None, self.a_rect)


            SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE)

            SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0)
            for rect in self.hole_punches:
                SDL_RenderFillRect(renderer, rect)

            SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND)

            SDL_SetRenderTarget(renderer, gui.main_texture)
            SDL_SetTextureAlphaMod(gui.main_texture_overlay_temp, prefs.art_bg_opacity)
            SDL_RenderCopy(renderer, gui.main_texture_overlay_temp, None, None)

            SDL_SetRenderTarget(renderer, gui.main_texture)

        else:
            SDL_SetRenderTarget(renderer, gui.main_texture)


style_overlay = StyleOverlay()


def trunc_line(line, font, px, dots=True):  # This old function is slow and should be avoided

    if ddt.get_text_w(line, font) < px + 10:
        return line

    if dots:
        while ddt.get_text_w(line.rstrip(" ") + gui.trunk_end, font) > px:
            if len(line) == 0:
                return gui.trunk_end
            line = line[:-1]
        return line.rstrip(" ") + gui.trunk_end

    else:
        while ddt.get_text_w(line, font) > px:

            line = line[:-1]
            if len(line) < 2:
                break

        return line

def right_trunc(line, font, px, dots=True):

    if ddt.get_text_w(line, font) < px + 10:
        return line

    if dots:
        while ddt.get_text_w(line.rstrip(" ") + gui.trunk_end, font) > px:
            if len(line) == 0:
                return gui.trunk_end
            line = line[1:]
        return gui.trunk_end + line.rstrip(" ")

    else:
        while ddt.get_text_w(line, font) > px:
            # trunk = True
            line = line[1:]
            if len(line) < 2:
                break
        # if trunk and dots:
        #     line = line.rstrip(" ") + gui.trunk_end
        return line

# def trunc_line2(line, font, px):
#     trunk = False
#     p = ddt.get_text_w(line, font)
#     if p == 0 or p < px + 15:
#         return line
#
#     tl = line[0:(int(px / p * len(line)) + 3)]
#
#     if ddt.get_text_w(line.rstrip(" ") + gui.trunk_end, font) > px:
#         line = tl
#
#     while ddt.get_text_w(line.rstrip(" ") + gui.trunk_end, font) > px + 10:
#         trunk = True
#         line = line[:-1]
#         if len(line) < 1:
#             break
#
#     return line.rstrip(" ") + gui.trunk_end



click_time = time.time()
scroll_hold = False
scroll_point = 0
scroll_bpoint = 0
sbl = 50
sbp = 100

asbp = 50
album_scroll_hold = False


def fix_encoding(index, mode, enc):
    global default_playlist
    global enc_field

    todo = []

    if mode == 1:
        todo = [index]
    elif mode == 0:
        for b in range(len(default_playlist)):
            if pctl.master_library[default_playlist[b]].parent_folder_name == pctl.master_library[
                index].parent_folder_name:
                todo.append(default_playlist[b])

    for q in range(len(todo)):

        # key = pctl.master_library[todo[q]].title + pctl.master_library[todo[q]].filename
        old_star = star_store.full_get(todo[q])
        if old_star != None:
            star_store.remove(todo[q])

        if enc_field == 'All' or enc_field == 'Artist':
            line = pctl.master_library[todo[q]].artist
            line = line.encode("Latin-1", 'ignore')
            line = line.decode(enc, 'ignore')
            pctl.master_library[todo[q]].artist = line

        if enc_field == 'All' or enc_field == 'Album':
            line = pctl.master_library[todo[q]].album
            line = line.encode("Latin-1", 'ignore')
            line = line.decode(enc, 'ignore')
            pctl.master_library[todo[q]].album = line

        if enc_field == 'All' or enc_field == 'Title':
            line = pctl.master_library[todo[q]].title
            line = line.encode("Latin-1", 'ignore')
            line = line.decode(enc, 'ignore')
            pctl.master_library[todo[q]].title = line

        if old_star != None:
            star_store.insert(todo[q], old_star)

        # if key in pctl.star_library:
        #     newkey = pctl.master_library[todo[q]].title + pctl.master_library[todo[q]].filename
        #     if newkey not in pctl.star_library:
        #         pctl.star_library[newkey] = copy.deepcopy(pctl.star_library[key])
        #         # del pctl.star_library[key]


def transfer_tracks(index, mode, to):
    todo = []

    if mode == 0:
        todo = [index]
    elif mode == 1:
        for b in range(len(default_playlist)):
            if pctl.master_library[default_playlist[b]].parent_folder_name == pctl.master_library[
                index].parent_folder_name:
                todo.append(default_playlist[b])
    elif mode == 2:
        todo = default_playlist

    pctl.multi_playlist[to][2] += todo


def prep_gal():
    global albums
    albums = []

    folder = ""

    for index in default_playlist:

        if folder != pctl.master_library[index].parent_folder_name:
            albums.append([index, 0])
            folder = pctl.master_library[index].parent_folder_name

def add_stations(stations, name):
    if len(stations) == 1:
        for i, s in enumerate(pctl.radio_playlists):
            if s["name"] == "Default":
                s["items"].insert(0, stations[0])
                s["scroll"] = 0
                pctl.radio_playlist_viewing = i
                break
        else:
            r = {}
            r["uid"] = uid_gen()
            r["name"] = 'Default'
            r["items"] = stations
            r["scroll"] = 0
            pctl.radio_playlists.append(r)
            pctl.radio_playlist_viewing = len(pctl.radio_playlists) - 1
    else:
        r = {}
        r["uid"] = uid_gen()
        r["name"] = name
        r["items"] = stations
        r["scroll"] = 0
        pctl.radio_playlists.append(r)
        pctl.radio_playlist_viewing = len(pctl.radio_playlists) - 1
    if not gui.radio_view:
        enter_radio_view()

def load_m3u(path):

    name = os.path.basename(path)[:-4]
    playlist = []
    stations = []

    location_dict = {}
    titles = {}

    if not os.path.isfile(path):
        return

    f = open(path)
    lines = f.readlines()
    f.close()

    for i, line in enumerate(lines):
        line = line.strip("\r\n").strip()
        if not line.startswith("#"): #line.startswith("http"):

            # Get title if present
            line_title = ""
            if i > 0:
                bline = lines[i - 1]
                if "," in bline and bline.startswith("#EXTINF:"):
                    line_title = bline.split(",", 1)[1].strip("\r\n").strip()

            if line.startswith("http"):
                radio = {}
                radio["stream_url"] = line

                if line_title:
                    radio["title"] = line_title
                else:
                    radio["title"] = os.path.splitext(os.path.basename(path))[0].strip()

                stations.append(radio)

                if gui.auto_play_import:
                    gui.auto_play_import = False
                    radiobox.start(radio)
            else:
                line = uri_parse(line)
                # Join file path if possibly relative
                if not line.startswith("/"):
                    line = os.path.join(os.path.dirname(path), line)

                # Cache datbase file paths for quick lookup
                if not location_dict:
                    for key, value in pctl.master_library.items():
                        if value.fullpath:
                            location_dict[value.fullpath] = value
                        if value.title:
                            titles[value.artist + " - " + value.title] = value

                # Is file path already imported?
                print(line)
                if line in location_dict:
                    playlist.append(location_dict[line].index)
                    print("found imported")
                # Or... does the file exist? Then import it
                elif os.path.isfile(line):
                    nt = TrackClass()
                    nt.index = pctl.master_count
                    nt.fullpath = line
                    nt = tag_scan(nt)
                    pctl.master_library[pctl.master_count] = nt
                    playlist.append(pctl.master_count)
                    pctl.master_count += 1
                    print("found file")
                # Last resort, guess based on title
                elif line_title in titles:
                        playlist.append(titles[line_title].index)
                        print("found title")
                else:
                    print("not found")

    if playlist:
        pctl.multi_playlist.append(pl_gen(title=name,
                                          playlist=playlist))
    if stations:
        add_sations(stations, name)

    gui.update = 1

def read_pls(lines, path, followed=False):

    ids = []
    urls = {}
    titles = {}

    for line in lines:
        line = line.strip("\r\n")
        if "=" in line and line.startswith("File") and "http" in line:
            # Get number
            n = line.split("=")[0][4:]
            if n.isdigit():
                if n not in ids:
                    ids.append(n)
                urls[n] = line.split("=", 1)[1].strip()

        if "=" in line and line.startswith("Title"):
            # Get number
            n = line.split("=")[0][5:]
            if n.isdigit():
                if n not in ids:
                    ids.append(n)
                titles[n] = line.split("=", 1)[1].strip()

    stations = []
    for id in ids:
        if id in urls:
            radio = {}
            radio["stream_url"] = urls[id]
            radio["title"] = os.path.splitext(os.path.basename(path))[0]
            radio["scroll"] = 0
            if id in titles:
                radio["title"] = titles[id]

            if ".pls" in radio["stream_url"]:
                if not followed:
                    try:
                        print("Download .pls")
                        response = requests.get(radio["stream_url"], stream=True)
                        if int(response.headers["Content-length"]) < 2000:
                            read_pls(response.content.decode().splitlines(), path, followed=True)
                    except:
                        print("Failed to retrieve .pls")
            else:
                stations.append(radio)
                if gui.auto_play_import:
                    gui.auto_play_import = False
                    radiobox.start(radio)
    if stations:
        add_stations(stations, os.path.basename(path))


def load_pls(path):
    if os.path.isfile(path):
        f = open(path)
        lines = f.readlines()
        read_pls(lines, path)
        f.close()


def load_xspf(path):
    global to_got

    name = os.path.basename(path)[:-5]
    # tauon.log("Importing XSPF playlist: " + path, title=True)
    console.print("Importing XSPF playlist: " + path)

    try:
        parser = ET.XMLParser(encoding="utf-8")
        e = ET.parse(path, parser).getroot()

        a = []
        b = {}
        info = ""

        for top in e:

            if top.tag.endswith("info"):
                info = top.text
            if top.tag.endswith("title"):
                name = top.text
            if top.tag.endswith("trackList"):
                for track in top:
                    if track.tag.endswith("track"):
                        for field in track:
                            print(field.tag)
                            print(field.text)
                            if 'title' in field.tag and field.text:
                                b['title'] = field.text
                            if 'location' in field.tag and field.text:
                                l = field.text
                                if l[:5] == "file:":
                                    l = l.replace('file:', "")
                                    l = l.lstrip("/")
                                    l = "/" + l
                                    l = str(urllib.parse.unquote(l))

                                b['location'] = l
                            if 'creator' in field.tag and field.text:
                                b['artist'] = field.text
                            if 'album' in field.tag and field.text:
                                b['album'] = field.text
                            if 'duration' in field.tag and field.text:
                                b['duration'] = field.text

                        b["info"] = info
                        b["name"] = name
                        a.append(copy.deepcopy(b))
                        b = {}

    except:
        show_message("Error importing XSPF playlist.", "Sorry about that.", mode='warning')
        #tauon.log("-- Error parsing XSPF file")
        console.print("-- Error parsing XSPF file")
        return

    # Extract internet streams first
    stations = []
    for i in reversed(range(len(a))):
        item = a[i]
        if item["location"].startswith("http"):
            radio = {}
            radio["stream_url"] = item["location"]
            radio["title"] = item["name"]
            radio["scroll"] = 0
            if item["info"].startswith("http"):
                radio["website_url"] = item["info"]

            stations.append(radio)

            if gui.auto_play_import:
                gui.auto_play_import = False
                radiobox.start(radio)

            del a[i]
    if stations:
        add_stations(stations, os.path.basename(path))
    playlist = []
    missing = 0

    if len(a) > 5000:
        to_got = 'xspfl'

    # Generate location dict
    location_dict = {}
    base_names = {}
    r_base_names = {}
    titles = {}
    for key, value in pctl.master_library.items():
        if value.fullpath != "":
            location_dict[value.fullpath] = key
        if value.filename != "":
            base_names[value.filename] = 0
            r_base_names[key] = value.filename
        if value.title != "":
            titles[value.title] = 0

    for track in a:
        found = False

        # Check if we already have a track with full file path in database
        if not found and 'location' in track:

            location = track['location']
            if location in location_dict:
                playlist.append(location_dict[location])
                if not os.path.isfile(location):
                    missing += 1
                found = True

            if found is True:
                continue

        # Then check for title, artist and filename match
        if not found and 'location' in track and 'duration' in track and 'title' in track and 'artist' in track:
            base = os.path.basename(track['location'])
            if base in base_names:
                for index, bn in r_base_names.items():
                    va = pctl.master_library[index]
                    if va.artist == track['artist'] and va.title == track['title'] and \
                                    os.path.isfile(va.fullpath) and \
                                    va.filename == base:
                        playlist.append(index)
                        if not os.path.isfile(va.fullpath):
                            missing += 1
                        found = True
                        break
                if found is True:
                    continue

        # Then check for just title and artist match
        if not found and 'title' in track and 'artist' in track and track['title'] in titles:
            for key, value in pctl.master_library.items():
                if value.artist == track['artist'] and value.title == track['title'] and os.path.isfile(value.fullpath):
                    playlist.append(key)
                    if not os.path.isfile(value.fullpath):
                        missing += 1
                    found = True
                    break
            if found is True:
                continue

        if not found and 'location' in track or 'title' in track:
            nt = TrackClass()
            nt.index = pctl.master_count
            nt.found = False

            if 'location' in track:
                location = track['location']
                nt.fullpath = location.replace('\\', '/')
                nt.filename = os.path.basename(location)
                nt.parent_folder_path = os.path.dirname(location.replace('\\', '/'))
                nt.parent_folder_name = os.path.splitext(os.path.basename(nt.parent_folder_path))[0]
                nt.file_ext = os.path.splitext(os.path.basename(location))[1][1:].upper()
                if os.path.isfile(location):
                    nt.found = True
            elif 'album' in track:
                nt.parent_folder_name = track['album']
            if 'artist' in track:
                nt.artist = track['artist']
            if 'title' in track:
                nt.title = track['title']
            if 'duration' in track:
                nt.length = int(float((track['duration'])) / 1000)
            if 'album' in track:
                nt.album = track['album']
            nt.is_cue = False
            if nt.found:
                nt = tag_scan(nt)

            pctl.master_library[pctl.master_count] = nt
            playlist.append(pctl.master_count)
            pctl.master_count += 1
            if nt.found:
                continue

        missing += 1
        console.print("-- Failed to locate track", level=2)
        if 'location' in track:
            console.print("-- -- Expected path: " + track['location'], level=2)
        if 'title' in track:
            console.print("-- -- Title: " + track['title'], level=2)
        if 'artist' in track:
            console.print("-- -- Artist: " + track['artist'], level=2)
        if 'album' in track:
            console.print("-- -- Album: " + track['album'], level=2)

    if missing > 0:
        show_message('Failed to locate ' + str(missing) + ' out of ' + str(len(a)) + ' tracks.')


    #print(playlist)
    if playlist:
        pctl.multi_playlist.append(pl_gen(title=name,
                                          playlist=playlist))
    gui.update = 1

    # tauon.log("Finished importing XSPF")


bb_type = 0

# gui.scroll_hide_box = (0, gui.panelY, 28, window_size[1] - gui.panelBY - gui.panelY)

encoding_menu = False
enc_index = 0
enc_setting = 0
enc_field = 'All'

gen_menu = False

transfer_setting = 0

b_panel_size = 300
b_info_bar = False

message_info_icon = asset_loader("notice.png")
message_warning_icon = asset_loader("warning.png")
message_tick_icon = asset_loader("done.png")
message_arrow_icon = asset_loader("ext.png")
message_error_icon = asset_loader("error.png")
message_bubble_icon = asset_loader("bubble.png")
message_download_icon = asset_loader("ddl.png")


class ToolTip:

    def __init__(self):
        self.text = ""
        self.h = 24 * gui.scale
        self.w = 62 * gui.scale
        self.x = 0
        self.y = 0
        self.timer = Timer()
        self.trigger = 1.1
        self.font = 13
        self.called = False
        self.a = False

    def test(self, x, y, text):

        if self.text != text or x != self.x or y != self.y:
            self.text = text
            #self.timer.set()
            self.a = False

            self.x = x
            self.y = y
            self.w = ddt.get_text_w(text, self.font) + 20 * gui.scale

        self.called = True

        if self.a is False:
            self.timer.set()
            gui.frame_callback_list.append(TestTimer(self.trigger))
        self.a = True

    def render(self):

        if self.called is True:

            if self.timer.get() > self.trigger:

                ddt.rect((self.x, self.y, self.w, self.h), colours.box_button_background)
                #ddt.rect((self.x, self.y, self.w, self.h), colours.grey(45))
                ddt.text((self.x + int(self.w / 2), self.y + 4 * gui.scale, 2), self.text, colours.menu_text, self.font, bg=colours.box_button_background)
            else:
                #gui.update += 1
                pass
        else:
            self.timer.set()
            self.a = False

        self.called = False

tool_tip = ToolTip()
tool_tip2 = ToolTip()
tool_tip2.trigger = 1.8
track_box_path_tool_timer = Timer()

def ex_tool_tip(x, y, text1_width, text, font):

    text2_width = ddt.get_text_w(text, font)
    if text2_width == text1_width:
        return

    y -= 10 * gui.scale

    w = ddt.get_text_w(text, 312) + 24 * gui.scale
    h = 24 * gui.scale

    x = x - int(w / 2)

    border = 1 * gui.scale
    ddt.rect((x - border, y - border, w + border * 2, h + border * 2), colours.grey(60))
    ddt.rect((x, y, w, h), colours.menu_background)
    p = ddt.text((x + int(w / 2), y + 3 * gui.scale, 2), text, colours.menu_text, 312, bg=colours.menu_background)


class ToolTip3:

    def __init__(self):
        self.x = 0
        self.y = 0
        self.text = ""
        self.font = None
        self.show = False
        self.width = 0
        self.height = 24 * gui.scale
        self.timer = Timer()
        self.pl_position = 0
        self.click_exclude_point = (0, 0)

    def set(self, x, y, text, font, rect):

        y = y - 11 * gui.scale
        if self.show == False or self.y != y or x != self.x or self.pl_position != pctl.playlist_view_position:
            self.timer.set()

        if point_proximity_test(self.click_exclude_point, mouse_position, 20 * gui.scale):
            self.timer.set()
            return

        if inp.mouse_click:
            self.click_exclude_point = copy.copy(mouse_position)
            self.timer.set()
            return

        self.x = x
        self.y = y
        self.text = text
        self.font = font
        self.show = True
        self.rect = rect
        self.pl_position = pctl.playlist_view_position


    def render(self):

        if not self.show:
            return

        if not point_proximity_test(self.click_exclude_point, mouse_position, 20 * gui.scale):
            self.click_exclude_point = (0, 0)

        if not coll(self.rect) or inp.mouse_click or gui.level_2_click or self.pl_position != pctl.playlist_view_position:
            self.show = False

        gui.frame_callback_list.append(TestTimer(0.02))

        if self.timer.get() < 0.6:
            return

        w = ddt.get_text_w(self.text, 312) + self.height
        x = self.x #- int(self.width / 2)
        y = self.y
        h = self.height

        border = 1 * gui.scale

        ddt.rect((x - border, y - border, w + border * 2, h + border * 2), colours.grey(60))
        ddt.rect((x, y, w, h), colours.menu_background)
        p = ddt.text((x + int(w / 2), y + 3 * gui.scale, 2), self.text, colours.menu_text, 312,
                     bg=colours.menu_background)

        if not coll(self.rect):
            self.show = False

columns_tool_tip = ToolTip3()

tool_tip_instant = ToolTip3()
# Right click context menu generator

class MenuIcon:

    def __init__(self, asset):

        self.asset = asset
        self.colour = [170, 170, 170, 255]
        self.base_asset = None
        self.base_asset_mod = None
        self.colour_callback = None
        self.mode_callback = None
        self.xoff = 0
        self.yoff = 0


class Menu:
    switch = 0
    count = switch + 1
    instances = []
    active = False

    def rescale(self):
        self.vertical_size = round(22 * gui.scale)
        if gui.scale == 1.25:
            self.vertical_size = 28
        self.h = self.vertical_size
        self.w = self.request_width * gui.scale
        if gui.scale == 2:
            self.w += 15

    def __init__(self, width, show_icons=False):

        self.active = False
        self.request_width = width
        self.close_next_frame = False
        self.clicked = False
        self.pos = [0, 0]
        self.rescale()

        self.reference = 0
        self.items = []
        self.subs = []
        self.selected = -1
        self.up = False
        self.down = False
        self.font = 412
        self.show_icons = show_icons
        self.sub_arrow = MenuIcon(asset_loader("sub.png", True))

        self.id = Menu.count
        self.break_height = round(4 * gui.scale)

        Menu.count += 1

        self.sub_number = 0
        self.sub_active = -1
        self.sub_y_postion = 0
        Menu.instances.append(self)

    @staticmethod
    def deco(_=_):
        return [colours.menu_text, colours.menu_background, None]

    def click(self):
        self.clicked = True
        # cheap hack to prevent scroll bar from being activated when closing menu
        global click_location
        click_location = [0, 0]

    def add(self, title, func, render_func=None, no_exit=False, pass_ref=False, hint=None, icon=None, show_test=None, pass_ref_deco=False, disable_test=None, set_ref=None):
        if render_func is None:
            render_func = self.deco
        self.items.append([title, False, func, render_func, no_exit, pass_ref, hint, icon, show_test, pass_ref_deco, disable_test, set_ref])

    def br(self):
        self.items.append(None)

    def add_sub(self, title, width):
        self.items.append([title, True, self.sub_number, self.deco, width * gui.scale])
        self.sub_number += 1
        self.subs.append([])

    def add_to_sub(self, title, sub, func, render_func=None, no_exit=False, pass_ref=False, args=None, icon=None, pass_ref_deco=False, show_test=None):
        if render_func is None:
            render_func = self.deco
        item = [title, False, func, render_func, no_exit, pass_ref, args, icon, pass_ref_deco, show_test]
        self.subs[sub].append(item)

    def test_item_active(self, item):

        if item[1] is False and item[8] is not None:
            if item[8](1) is False:
                return False
        return True

    def render_icon(self, x, y, icon, selected, fx):

        if colours.lm:
            selected = True

        if icon is not None:

            x += icon.xoff * gui.scale
            y += icon.yoff * gui.scale

            colour = None

            if icon.base_asset is None:
                # Colourise mode

                if icon.colour_callback is not None: #and icon.colour_callback() is not None:
                    colour = icon.colour_callback()

                elif selected and not fx[0] == colours.menu_text_disabled:
                    colour = icon.colour

                if colour is None and icon.base_asset_mod:
                    colour = colours.menu_icons
                    # if colours.lm:
                    #     colour = [160, 160, 160, 255]
                    icon.base_asset_mod.render(x, y, colour)
                    return

                if colour is None:
                    #colour = [145, 145, 145, 70]
                    colour = colours.menu_icons# [255, 255, 255, 35]
                    #colour = [50, 50, 50, 255]

                icon.asset.render(x, y, colour)

            else:
                if not is_grey(colours.menu_background):
                    return  # Since these are currently pre-rendered greyscale, they are
                            # Incompatible with coloured backgrounds. Fix todo.

                # Pre-rendered mode
                if icon.mode_callback is not None:
                    if icon.mode_callback():
                        icon.asset.render(x, y)
                    else:
                        icon.base_asset.render(x, y)
                else:
                    if selected:
                        icon.asset.render(x, y)
                    else:
                        icon.base_asset.render(x, y)

    def render(self):
        if self.active:

            if Menu.switch != self.id:
                self.active = False

                for menu in Menu.instances:
                    if menu.active:
                        break
                else:
                    Menu.active = False

                return

            #ytoff = 3
            y_run = round(self.pos[1])
            to_call = None

            # if window_size[1] < 250 * gui.scale:
            #     self.h = round(14 * gui.scale)
            #     ytoff = -1 * gui.scale
            # else:
            self.h = self.vertical_size
            ytoff = round(self.h * 0.71 - 13 * gui.scale)

            x_run = self.pos[0]

            for i in range(len(self.items)):
                # print(self.items[i])

                # Draw menu break
                if self.items[i] is None:

                    if is_light(colours.menu_background):
                        break_colour = rgb_add_hls(colours.menu_background, 0, -0.1, -0.1)
                    else:
                        break_colour = rgb_add_hls(colours.menu_background, 0, 0.06, 0)

                    rect = (x_run, y_run, self.w, self.break_height - 1)
                    if coll(rect):
                        self.clicked = False

                    ddt.rect_a((x_run, y_run), (self.w, self.break_height), colours.menu_background)

                    ddt.rect_a((x_run, y_run + 2 * gui.scale), (self.w, 2 * gui.scale), break_colour)


                    # Draw tab
                    ddt.rect_a((x_run, y_run), (4 * gui.scale, self.break_height), colours.menu_tab)
                    y_run += self.break_height

                    continue

                if self.test_item_active(self.items[i]) is False:
                    continue
                # if self.items[i][1] is False and self.items[i][8] is not None:
                #     if self.items[i][8](1) == False:
                #         continue

                # Get properties for menu item
                if len(self.items[i]) > 7 and self.items[i][9]:
                    fx = self.items[i][3](self.reference)
                else:
                    fx = self.items[i][3]()

                if fx[2] is not None:
                    label = fx[2]
                else:
                    label = self.items[i][0]

                # Show text as disabled is pass disable test
                if len(self.items[i]) > 7 and self.items[i][10] is not None:
                    if self.items[i][9]:
                        if self.items[i][10](self.reference):
                            fx[0] = colours.menu_text_disabled
                    else:
                        if self.items[i][10]():
                            fx[0] = colours.menu_text_disabled

                # Draw item background, black by default
                ddt.rect_a((x_run, y_run), (self.w, self.h), fx[1])
                bg = fx[1]

                # Detect if mouse is over this item
                selected = False
                rect = (x_run, y_run, self.w, self.h - 1)
                fields.add(rect)

                if coll_point(mouse_position,
                              (x_run, y_run, self.w, self.h - 1)):
                    ddt.rect_a((x_run, y_run), (self.w, self.h), colours.menu_highlight_background)  # [15, 15, 15, 255]
                    selected = True
                    bg = alpha_blend(colours.menu_highlight_background, bg)

                    # Call menu items callback if clicked
                    if self.clicked:

                        if self.items[i][1] is False:
                            to_call = i
                            if self.items[i][11] is not None:
                                self.reference = self.items[i][11]
                            global mouse_down
                            mouse_down = False

                        else:
                            self.clicked = False
                            self.sub_active = self.items[i][2]
                            self.sub_y_postion = y_run

                # Draw tab
                ddt.rect_a((x_run, y_run), (4 * gui.scale, self.h), colours.menu_tab)

                # Draw Icon
                x = 12 * gui.scale
                if self.items[i][1] is False and self.show_icons:

                    icon = self.items[i][7]
                    self.render_icon(x_run + x , y_run + 5 * gui.scale, icon, selected, fx)

                if self.show_icons:
                    x += 25 * gui.scale

                # Draw arrow icon for sub menu
                if self.items[i][1] is True:

                    if is_light(bg) or colours.lm:
                        colour = rgb_add_hls(bg, 0, -0.6, -0.1)
                    else:
                        colour = rgb_add_hls(bg, 0, 0.1, 0)

                    if self.sub_active == self.items[i][2]:
                        if is_light(bg) or colours.lm:
                            colour = rgb_add_hls(bg, 0, -0.8, -0.1)
                        else:
                            colour = rgb_add_hls(bg, 0, 0.40, 0)

                    # colour = [50, 50, 50, 255]
                    # if selected:
                    #     colour = [150, 150, 150, 255]
                    # if self.sub_active == self.items[i][2]:
                    #     colour = [150, 150, 150, 255]
                    self.sub_arrow.asset.render(x_run + self.w - 13 * gui.scale, y_run + 7 * gui.scale, colour)

                # Render the items label
                ddt.text((x_run + x, y_run + ytoff), label, fx[0], self.font, max_w=self.w - (x + 9 * gui.scale), bg=bg)

                # Render the items hint
                if len(self.items[i]) > 6 and self.items[i][6] != None:

                    if is_light(bg) or colours.lm:
                        hint_colour = rgb_add_hls(bg, 0, -0.30, -0.3)
                    else:
                        hint_colour = rgb_add_hls(bg, 0, 0.15, 0)

                    #colo = alpha_blend([255, 255, 255, 50], bg)
                    ddt.text((x_run + self.w - 5, y_run + ytoff, 1), self.items[i][6],
                             hint_colour, self.font, bg=bg)

                y_run += self.h

                if y_run > window_size[1] - self.h:
                    direc = 1
                    if self.pos[0] > window_size[0] // 2:
                        direc = -1
                    x_run += self.w * direc
                    y_run = self.pos[1]

                # Render sub menu if active
                if self.sub_active > -1 and self.items[i][1] and self.sub_active == self.items[i][2]:

                    # sub_pos = [x_run + self.w, self.pos[1] + i * self.h]
                    sub_pos = [x_run + self.w, self.sub_y_postion]
                    sub_w = self.items[i][4]

                    if sub_pos[0] + sub_w > window_size[0]:
                        sub_pos[0] = x_run - sub_w
                        if view_box.active:
                            sub_pos[0] -= view_box.w

                    fx = self.deco()

                    minY = window_size[1] - self.h * len(self.subs[self.sub_active]) - 15 * gui.scale
                    sub_pos[1] = min(sub_pos[1], minY)

                    xoff = 0
                    for i in self.subs[self.sub_active]:
                        if i[7] is not None:
                            xoff = 24 * gui.scale
                            break

                    for w in range(len(self.subs[self.sub_active])):

                        if len(self.subs[self.sub_active][w]) > 5 and self.subs[self.sub_active][w][9] is not None:
                            if not self.subs[self.sub_active][w][9](self.reference):
                                continue

                        # Get item colours
                        if len(self.subs[self.sub_active][w]) > 5 and self.subs[self.sub_active][w][8]:
                            fx = self.subs[self.sub_active][w][3](self.reference)
                        else:
                            fx = self.subs[self.sub_active][w][3]()

                        # Item background
                        ddt.rect_a((sub_pos[0], sub_pos[1] + w * self.h), (sub_w, self.h), fx[1])

                        # Detect if mouse is over this item
                        rect = (sub_pos[0], sub_pos[1] + w * self.h, sub_w, self.h - 1)
                        fields.add(rect)
                        this_select = False
                        bg = colours.menu_background
                        if coll_point(mouse_position,
                                      (sub_pos[0], sub_pos[1] + w * self.h, sub_w, self.h - 1)):
                            ddt.rect_a((sub_pos[0], sub_pos[1] + w * self.h), (sub_w, self.h),
                                       colours.menu_highlight_background)
                            bg = alpha_blend(colours.menu_highlight_background, bg)
                            this_select = True

                            # Call Callback
                            if self.clicked:

                                # If callback needs args
                                if self.subs[self.sub_active][w][6] is not None:
                                    self.subs[self.sub_active][w][2](self.reference, self.subs[self.sub_active][w][6])

                                # If callback just need ref
                                elif self.subs[self.sub_active][w][5]:
                                    self.subs[self.sub_active][w][2](self.reference)

                                else:
                                    self.subs[self.sub_active][w][2]()


                        if fx[2] is not None:
                            label = fx[2]
                        else:
                            label = self.subs[self.sub_active][w][0]

                        # Render sub items icon
                        icon = self.subs[self.sub_active][w][7]
                        self.render_icon(sub_pos[0] + 11 * gui.scale, sub_pos[1] + w * self.h + 5 * gui.scale, icon, this_select, fx)

                        # Render the items label
                        ddt.text((sub_pos[0] + 10 * gui.scale + xoff, sub_pos[1] + ytoff + w * self.h), label, fx[0],
                                 self.font, bg=bg)

                        # Draw tab
                        ddt.rect_a((sub_pos[0], sub_pos[1] + w * self.h), (4 * gui.scale, self.h), colours.menu_tab)

                        # Render the menu outline
                        # ddt.rect_a(sub_pos, (sub_w, self.h * len(self.subs[self.sub_active])), colours.grey(40))


            # Process Click Actions
            if to_call is not None:

                if self.items[to_call][5]:
                    self.items[to_call][2](self.reference)
                else:
                    self.items[to_call][2]()

            if self.clicked or key_esc_press or self.close_next_frame:
                self.close_next_frame = False
                self.active = False
                self.clicked = False

                last_click_location[0] = 0
                last_click_location[1] = 0

                for menu in Menu.instances:
                    if menu.active:
                        break
                else:
                    Menu.active = False

                # Render the menu outline
                # ddt.rect_a(self.pos, (self.w, self.h * len(self.items)), colours.grey(40))

    def activate(self, in_reference=0, position=None):

        Menu.active = True

        if position != None:
            self.pos = [position[0], position[1]]
        else:
            self.pos = [copy.deepcopy(mouse_position[0]), copy.deepcopy(mouse_position[1])]

        self.reference = in_reference
        Menu.switch = self.id
        self.sub_active = -1

        # Reposition the menu if it would otherwise intersect with far edge of window
        if not position:
            if self.pos[0] + self.w > window_size[0]:
                self.pos[0] = self.pos[0] - (self.w + 3 * gui.scale)

        # Get height size of menu
        full_h = 0
        shown_h = 0
        for item in self.items:
            if item is None:
                full_h += self.break_height
                shown_h += self.break_height
            else:
                full_h += self.h
                if self.test_item_active(item) is True:
                    shown_h += self.h

        # Flip menu up if would intersect with bottom of window
        if self.pos[1] + full_h > window_size[1]:
            self.pos[1] = self.pos[1] - shown_h

            # Prevent moving outside top of window
            if self.pos[1] < gui.panelY:
                self.pos[1] = gui.panelY
                self.pos[0] += 5 * gui.scale

        self.active = True


def close_all_menus():
    for menu in Menu.instances:
        menu.active = False
    Menu.active = False


def menu_standard_or_grey(bool):
    if bool:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]

# Create empty area menu
playlist_menu = Menu(130)
radio_entry_menu = Menu(125)
showcase_menu = Menu(125)
center_info_menu = Menu(125)
cancel_menu = Menu(100)
gallery_menu = Menu(175, show_icons=True)
artist_info_menu = Menu(135)
queue_menu = Menu(150)
repeat_menu = Menu(120)
shuffle_menu = Menu(120)
artist_list_menu = Menu(165, show_icons=True)
lightning_menu = Menu(165)
lsp_menu = Menu(145)
folder_tree_menu = Menu(175, show_icons=True)
folder_tree_stem_menu = Menu(190, show_icons=True)
overflow_menu = Menu(175)
spotify_playlist_menu = Menu(175)
radio_context_menu = Menu(175)
chrome_menu = Menu(175)

tauon.chrome_menu = chrome_menu

def enable_artist_list():
    if prefs.left_panel_mode != "artist list":
        gui.last_left_panel_mode = prefs.left_panel_mode
    prefs.left_panel_mode = "artist list"
    gui.lsp = True
    gui.update_layout()

def enable_playlist_list():
    if prefs.left_panel_mode != "playlist":
        gui.last_left_panel_mode = prefs.left_panel_mode
    prefs.left_panel_mode = "playlist"
    gui.lsp = True
    gui.update_layout()

def enable_queue_panel():
    if prefs.left_panel_mode != "queue":
        gui.last_left_panel_mode = prefs.left_panel_mode
    prefs.left_panel_mode = "queue"
    gui.lsp = True
    gui.update_layout()

def enable_folder_list():
    if prefs.left_panel_mode != "folder view":
        gui.last_left_panel_mode = prefs.left_panel_mode
    prefs.left_panel_mode = "folder view"
    gui.lsp = True
    gui.update_layout()

def lsp_menu_test_queue():
    if not gui.lsp:
        return False
    return prefs.left_panel_mode == "queue"

def lsp_menu_test_playlist():
    if not gui.lsp:
        return False
    return prefs.left_panel_mode == "playlist"

def lsp_menu_test_tree():
    if not gui.lsp:
        return False
    return prefs.left_panel_mode == "folder view"

def lsp_menu_test_artist():
    if not gui.lsp:
        return False
    return prefs.left_panel_mode == "artist list"

def toggle_left_last():
    gui.lsp = True
    t = prefs.left_panel_mode
    if t != gui.last_left_panel_mode:
        prefs.left_panel_mode = gui.last_left_panel_mode
        gui.last_left_panel_mode = t

#. Menu entry: A side panel view layout.

lsp_menu.add(_("Playlists + Queue"), enable_playlist_list, disable_test=lsp_menu_test_playlist)
lsp_menu.add(_("Queue"), enable_queue_panel, disable_test=lsp_menu_test_queue)
#. Menu entry: Side panel view layout showing a list of artists with thumbnails.
lsp_menu.add(_("Artist List"), enable_artist_list, disable_test=lsp_menu_test_artist)
#. Menu entry: A side panel view layout. Alternative name: Folder Tree.
lsp_menu.add(_("Folder Navigator"), enable_folder_list, disable_test=lsp_menu_test_tree)

class RenameTrackBox:

    def __init__(self):

        self.active = False
        self.target_track_id = None
        self.single_only = False

    def activate(self, track_id):

        self.active = True
        self.target_track_id = track_id
        if key_shift_down or key_shiftr_down:
            self.single_only = True
        else:
            self.single_only = False

    def render(self):

        if not self.active:
            return

        if gui.level_2_click:
            inp.mouse_click = True
        gui.level_2_click = False

        w = 420 * gui.scale
        h = 155 * gui.scale
        x = int(window_size[0] / 2) - int(w / 2)
        y = int(window_size[1] / 2) - int(h / 2)

        ddt.rect_a((x - 2 * gui.scale, y - 2 * gui.scale), (w + 4 * gui.scale, h + 4 * gui.scale), colours.box_border)
        ddt.rect_a((x, y), (w, h), colours.box_background)
        ddt.text_background_colour = colours.box_background

        if key_esc_press or ((inp.mouse_click or right_click or level_2_right_click) and not coll((x, y, w, h))):
            rename_track_box.active = False

        r_todo = []

        # Find matching folder tracks in playlist
        if not self.single_only:
            for item in default_playlist:
                if pctl.master_library[item].parent_folder_path == pctl.master_library[
                    self.target_track_id].parent_folder_path:

                    # Close and display error if any tracks are not single local files
                    if pctl.master_library[item].is_network is True:
                        rename_track_box.active = False
                        show_message("Cannot rename", "One or more tracks is from a network location!", mode='info')
                    if pctl.master_library[item].is_cue is True:
                        rename_track_box.active = False
                        show_message("This function does not support renaming CUE Sheet tracks.")
                    else:
                        r_todo.append(item)
        else:
            r_todo = [self.target_track_id]

        ddt.text((x + 10 * gui.scale, y + 8 * gui.scale,), _("Track Renaming"), colours.grey(230), 213)

        # if draw.button("Default", x + 230 * gui.scale, y + 8 * gui.scale,
        if rename_files.text != prefs.rename_tracks_template and draw.button(_("Default"), x + w - 85 * gui.scale,
                                                                             y + h - 35 * gui.scale, 70 * gui.scale):
            rename_files.text = prefs.rename_tracks_template

        # ddt.draw_text((x + 14, y + 40,), NRN + cursor, colours.grey(150), 12)
        rename_files.draw(x + 14 * gui.scale, y + 39 * gui.scale, colours.box_input_text, width=300)
        NRN = rename_files.text

        ddt.rect_s((x + 8 * gui.scale, y + 36 * gui.scale, 300 * gui.scale, 22 * gui.scale), colours.box_text_border, 1 * gui.scale)

        afterline = ""
        warn = False
        underscore = False

        for item in r_todo:

            if pctl.master_library[item].track_number == "" or pctl.master_library[item].artist == "" or \
                    pctl.master_library[item].title == "" or pctl.master_library[item].album == "":
                warn = True

            if item == self.target_track_id:
                afterline = parse_template2(NRN, pctl.master_library[item])

        ddt.text((x + 10 * gui.scale, y + 68 * gui.scale), _("BEFORE"), colours.box_text_label, 212)
        line = trunc_line(pctl.master_library[self.target_track_id].filename, 12, 335)
        ddt.text((x + 70 * gui.scale, y + 68 * gui.scale), line, colours.grey(210), 211, max_w=340)

        ddt.text((x + 10 * gui.scale, y + 83 * gui.scale), _("AFTER"), colours.box_text_label, 212)
        ddt.text((x + 70 * gui.scale, y + 83 * gui.scale), afterline, colours.grey(210), 211, max_w=340)

        if (len(NRN) > 3 and len(pctl.master_library[self.target_track_id].filename) > 3 and afterline[-3:].lower() !=
            pctl.master_library[self.target_track_id].filename[-3:].lower()) or len(NRN) < 4 or "." not in afterline[-5:]:
            ddt.text((x + 10 * gui.scale, y + 108 * gui.scale,), "Warning: This may change the file extension",
                     [245, 90, 90, 255],
                     13)

        colour_warn = [143, 186, 65, 255]
        if not unique_template(NRN):
            ddt.text((x + 10 * gui.scale, y + 123 * gui.scale,), "Warning: The filename might not be unique",
                     [245, 90, 90, 255],
                     13)
        if warn:
            ddt.text((x + 10 * gui.scale, y + 135 * gui.scale,), "Warning: A track has incomplete metadata",
                     [245, 90, 90, 255], 13)
            colour_warn = [180, 60, 60, 255]

        label = "Write (" + str(len(r_todo)) + ")"

        if draw.button(label, x + (8 + 300 + 10) * gui.scale, y + 36 * gui.scale, 80 * gui.scale,
                       text_highlight_colour=colours.grey(255), background_highlight_colour=colour_warn,
                       tooltip="Physically renames all the tracks in the folder") or inp.level_2_enter:
            inp.mouse_click = False
            total_todo = len(r_todo)
            pre_state = 0

            for item in r_todo:

                if pctl.playing_state > 0 and item == pctl.track_queue[pctl.queue_step]:
                    pre_state = pctl.stop(True)

                try:

                    afterline = parse_template2(NRN, pctl.master_library[item], strict=True)

                    oldname = pctl.master_library[item].filename
                    oldpath = pctl.master_library[item].fullpath

                    print('Renaming...')

                    star = star_store.full_get(item)
                    star_store.remove(item)

                    oldpath = pctl.master_library[item].fullpath

                    oldsplit = os.path.split(oldpath)

                    if os.path.exists(os.path.join(oldsplit[0], afterline)):
                        print("A file with that name already exists")
                        total_todo -= 1
                        continue

                    if not afterline:
                        print("Rename Error")
                        total_todo -= 1
                        continue

                    if "." in afterline and not afterline.split(".")[0]:
                        print("A file does not have a target filename")
                        total_todo -= 1
                        continue

                    os.rename(pctl.master_library[item].fullpath, os.path.join(oldsplit[0], afterline))

                    pctl.master_library[item].fullpath = os.path.join(oldsplit[0], afterline)
                    pctl.master_library[item].filename = afterline

                    search_string_cache.pop(item, None)
                    search_dia_string_cache.pop(item, None)

                    if star is not None:
                        star_store.insert(item, star)

                except:
                    total_todo -= 1

            rename_track_box.active = False
            print('Done')
            if pre_state == 1:
                pctl.revert()

            if total_todo != len(r_todo):
                show_message("Rename complete." + "  " + str(total_todo) + "/" + str(
                    len(r_todo)) + " filenames written.", mode='warning')

            else:
                show_message(_("Rename complete."),
                             str(total_todo) + "/" + str(len(r_todo)) + _(" filenames were written."), mode='done')
            pctl.notify_change()


rename_track_box = RenameTrackBox()

class TransEditBox:

    def __init__(self):
        self.active = False
        self.active_field = 1
        self.selected = []
        self.playlist = -1

    def render(self):

        if not self.active:
            return

        if gui.level_2_click:
            inp.mouse_click = True
        gui.level_2_click = False

        w = 500 * gui.scale
        h = 255 * gui.scale
        x = int(window_size[0] / 2) - int(w / 2)
        y = int(window_size[1] / 2) - int(h / 2)

        ddt.rect_a((x - 2 * gui.scale, y - 2 * gui.scale), (w + 4 * gui.scale, h + 4 * gui.scale), colours.box_border)
        ddt.rect_a((x, y), (w, h), colours.box_background)
        ddt.text_background_colour = colours.box_background

        if key_esc_press or ((inp.mouse_click or right_click or level_2_right_click) and not coll((x, y, w, h))):
            self.active = False

        select = list(set(shift_selection))
        if not select and pctl.selected_ready():
            select = [playlist_selected]

        titles = [pctl.g(default_playlist[s]).title for s in select]
        artists = [pctl.g(default_playlist[s]).artist for s in select]
        albums = [pctl.g(default_playlist[s]).album for s in select]
        album_artists = [pctl.g(default_playlist[s]).album_artist for s in select]

        #print(select)
        if select != self.selected or pctl.active_playlist_viewing != self.playlist:
            #print("reset")
            self.selected = select
            self.playlist = pctl.active_playlist_viewing
            edit_album.clear()
            edit_artist.clear()
            edit_title.clear()
            edit_album_artist.clear()

            if len(select) == 0:
                return

            tr = pctl.g(default_playlist[select[0]])
            edit_title.text = tr.title

            if check_equal(artists):
                edit_artist.text = artists[0]

            if check_equal(albums):
                edit_album.text = albums[0]

            if check_equal(album_artists):
                edit_album_artist.text = album_artists[0]

        x += round(20 * gui.scale)
        y += round(18 * gui.scale)

        ddt.text((x, y), _("Simple tag editor"), colours.box_title_text, 215)

        if draw.button(_("?"), x + 440 * gui.scale, y ):
            show_message(_("Press Enter in each field to apply its changes to local database."),
                         _("When done, press WRITE TAGS to save to tags in actual files. (Optional but recommended)"), mode="info")

        y += round(24 * gui.scale)
        ddt.text((x, y), _("Number of tracks selected: %d") % len(select), colours.box_title_text, 313)

        y += round(24 * gui.scale)

        if inp.key_tab_press:
            if key_shift_down or key_shiftr_down:
                self.active_field -= 1
            else:
                self.active_field += 1

        if self.active_field < 0:
            self.active_field = 3
        if self.active_field == 4:
            self.active_field = 0
            if len(select) > 1:
                self.active_field = 1

        def field_edit(x, y, label, field_number, names, text_box):
            changed = 0
            ddt.text((x, y), label, colours.box_text_label, 11)
            y += round(16 * gui.scale)
            rect1 = (x, y, round(370 * gui.scale), round(17 * gui.scale))
            fields.add(rect1)
            if (coll(rect1) and inp.mouse_click) or (inp.key_tab_press and self.active_field == field_number):
                self.active_field = field_number
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            tc = colours.box_input_text
            if names and check_equal(names) and text_box.text == names[0]:
                h, l, s = rgb_to_hls(tc[0], tc[1], tc[2])
                l *= 0.7
                tc = hls_to_rgb(h, l, s)
            else:
                changed = 1
            if not (names and check_equal(names)) and not text_box.text:
                changed = 0
                ddt.text((x + round(2 * gui.scale), y), _("<Multiple selected>"), colours.box_text_label, 12)
            text_box.draw(x + round(3 * gui.scale), y, tc, self.active_field == field_number, width=370 * gui.scale)
            if changed:
                ddt.text((x + 377 * gui.scale, y - 1 * gui.scale), "⮨", colours.box_title_text, 214)
            return changed

        changed = 0
        if len(select) == 1:
            changed = field_edit(x, y, _("Track title"), 0, titles, edit_title)
        y += round(40 * gui.scale)
        changed += field_edit(x, y, _("Album name"), 1, albums, edit_album)
        y += round(40 * gui.scale)
        changed += field_edit(x, y, _("Artist name"), 2, artists, edit_artist)
        y += round(40 * gui.scale)
        changed += field_edit(x, y, _("Album-artist name"), 3, album_artists, edit_album_artist)

        y += round(40 * gui.scale)
        for s in select:
            tr = pctl.g(default_playlist[s])
            if tr.is_network:
                ddt.text((x, y), _("Editing network tracks is not recommended!"), [245, 90, 90, 255], 312)

        if inp.key_return_press:

            gui.pl_update += 1
            if self.active_field == 0 and len(select) == 1:
                for s in select:
                    tr = pctl.g(default_playlist[s])
                    star = star_store.full_get(tr.index)
                    star_store.remove(tr.index)
                    tr.title = edit_title.text
                    star_store.merge(tr.index, star)

            if self.active_field == 1:
                for s in select:
                    tr = pctl.g(default_playlist[s])
                    tr.album = edit_album.text
            if self.active_field == 2:
                for s in select:
                    tr = pctl.g(default_playlist[s])
                    star = star_store.full_get(tr.index)
                    star_store.remove(tr.index)
                    tr.artist = edit_artist.text
                    star_store.merge(tr.index, star)
            if self.active_field == 3:
                for s in select:
                    tr = pctl.g(default_playlist[s])
                    tr.album_artist = edit_album_artist.text
            tauon.bg_save()

        ww = ddt.get_text_w(_("WRITE TAGS"), 212) + round(48 * gui.scale)
        if draw.button(_("WRITE TAGS"), (x + w) - ww, y - round(0) * gui.scale):
            if changed:
                show_message(_("Press enter on fields to apply your changes first!"))
                return
            try:
                import mutagen
            except:
                show_message("Please install package python-mutagen for this feature")
                return
            written = 0
            for s in select:
                tr = pctl.g(default_playlist[s])

                if tr.is_network:
                    show_message(_("Writing to a network track is not applicable!"), mode="error")
                    return
                if tr.is_cue:
                    show_message(_("Cannot write CUE sheet types!"), mode="error")
                    return

                muta = mutagen.File(tr.fullpath, easy=True)

                def write_tag(track, muta, field_name_tauon, field_name_muta):
                    item = muta.get(field_name_muta)
                    if item and len(item) > 1:
                        show_message(_("Cannot handle multi-field! Please use external tag editor"), mode="error")
                        return 0
                    if not getattr(tr, field_name_tauon):  # Want delete tag field
                        if item:
                            del muta[field_name_muta]
                    else:
                        muta[field_name_muta] = getattr(tr, field_name_tauon)
                    return 1

                write_tag(tr, muta, "artist", "artist")
                write_tag(tr, muta, "album", "album")
                write_tag(tr, muta, "title", "title")
                write_tag(tr, muta, "album_artist", "albumartist")

                muta.save()
                written += 1
            tauon.bg_save()
            if not gui.message_box:
                show_message(_("%d files rewritten") % written, mode="done")


trans_edit_box = TransEditBox()

class SubLyricsBox:

    def __init__(self):

        self.active = False
        self.target_track = None
        self.active_field = 1

    def activate(self, track):

        self.active = True
        gui.box_over = True
        self.target_track = track


        sub_lyrics_a.text = prefs.lyrics_subs.get(self.target_track.artist, "")
        sub_lyrics_b.text = prefs.lyrics_subs.get(self.target_track.title, "")

        if not sub_lyrics_a.text:
            sub_lyrics_a.text = self.target_track.artist
        if not sub_lyrics_b.text:
            sub_lyrics_b.text = self.target_track.title

    def render(self):

        if not self.active:
            return

        if gui.level_2_click:
            inp.mouse_click = True
        gui.level_2_click = False

        w = 400 * gui.scale
        h = 155 * gui.scale
        x = int(window_size[0] / 2) - int(w / 2)
        y = int(window_size[1] / 2) - int(h / 2)

        ddt.rect_a((x - 2 * gui.scale, y - 2 * gui.scale), (w + 4 * gui.scale, h + 4 * gui.scale), colours.box_border)
        ddt.rect_a((x, y), (w, h), colours.box_background)
        ddt.text_background_colour = colours.box_background

        if key_esc_press or ((inp.mouse_click or right_click or level_2_right_click) and not coll((x, y, w, h))):
            self.active = False
            gui.box_over = False

            if sub_lyrics_a.text and sub_lyrics_a.text != self.target_track.artist:
                prefs.lyrics_subs[self.target_track.artist] = sub_lyrics_a.text
            else:
                if self.target_track.artist in prefs.lyrics_subs:
                    del prefs.lyrics_subs[self.target_track.artist]

            if sub_lyrics_b.text and sub_lyrics_b.text != self.target_track.title:
                prefs.lyrics_subs[self.target_track.title] = sub_lyrics_b.text
            else:
                if self.target_track.title in prefs.lyrics_subs:
                    del prefs.lyrics_subs[self.target_track.title]

        ddt.text((x + 10 * gui.scale, y + 8 * gui.scale,), _("Substitute Lyric Search"), colours.grey(230), 213)

        y += round(35 * gui.scale)
        x += round(23 * gui.scale)

        xx = x
        xx += ddt.text((x + round(0 * gui.scale), y + round(0 * gui.scale)), _("Substitute"), colours.box_text_label, 212)
        xx += round(6 * gui.scale)
        ddt.text((xx, y + round(0 * gui.scale)), self.target_track.artist, colours.box_sub_text, 312)

        y += round(19 * gui.scale)
        xx = x
        xx += ddt.text((xx + round(0 * gui.scale), y + round(0 * gui.scale)), _("with"), colours.box_text_label, 212)
        xx += round(6 * gui.scale)
        rect1 = (xx, y, round(250 * gui.scale), round(17 * gui.scale))
        fields.add(rect1)
        ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
        if (coll(rect1) and inp.mouse_click) or (inp.key_tab_press and self.active_field == 2):
            self.active_field = 1
            inp.key_tab_press = False

        sub_lyrics_a.draw(xx + round(4 * gui.scale), y, colours.box_input_text, self.active_field == 1, width=rect1[2] - 8 * gui.scale)

        y += round(28 * gui.scale)

        xx = x
        xx += ddt.text((x + round(0 * gui.scale), y + round(0 * gui.scale)), _("Substitute"), colours.box_text_label, 212)
        xx += round(6 * gui.scale)
        ddt.text((xx, y + round(0 * gui.scale)), self.target_track.title, colours.box_sub_text, 312)

        y += round(19 * gui.scale)
        xx = x
        xx += ddt.text((xx + round(0 * gui.scale), y + round(0 * gui.scale)), _("with"), colours.box_text_label, 212)
        xx += round(6 * gui.scale)
        rect1 = (xx, y, round(250 * gui.scale), round(16 * gui.scale))
        fields.add(rect1)
        if (coll(rect1) and inp.mouse_click) or (inp.key_tab_press and self.active_field == 1):
            self.active_field = 2
        #ddt.rect(rect1, [40, 40, 40, 255], True)
        ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
        sub_lyrics_b.draw(xx + round(4 * gui.scale), y, colours.box_input_text, self.active_field == 2, width=rect1[2] - 8 * gui.scale)


sub_lyrics_box = SubLyricsBox()


class ExportPlaylistBox:

    def __init__(self):

        self.active = False
        self.id = None
        self.directory_text_box = TextBox2()
        self.default = {
            "path": music_directory if music_directory else os.path.join(user_directory, 'playlists'),
            "type": "xspf",
            "relative": False,
            "auto": False,
        }


    def activate(self, playlist):

        self.active = True
        gui.box_over = True
        self.id = pl_to_id(playlist)

        # Prune old enteries
        ids = []
        for playlist in pctl.multi_playlist:
            ids.append(playlist[6])
        for key in list(prefs.playlist_exports.keys()):
            if key not in ids:
                del prefs.playlist_exports[key]

    def render(self):

        if not self.active:
            return

        w = 500 * gui.scale
        h = 220 * gui.scale
        x = int(window_size[0] / 2) - int(w / 2)
        y = int(window_size[1] / 2) - int(h / 2)

        ddt.rect_a((x - 2 * gui.scale, y - 2 * gui.scale), (w + 4 * gui.scale, h + 4 * gui.scale), colours.box_border)
        ddt.rect_a((x, y), (w, h), colours.box_background)
        ddt.text_background_colour = colours.box_background

        if key_esc_press or ((inp.mouse_click or gui.level_2_click or right_click or level_2_right_click) and not coll((x, y, w, h))):
            self.active = False
            gui.box_over = False

        current = prefs.playlist_exports.get(self.id)
        if not current:
            current = copy.copy(self.default)

        ddt.text((x + 10 * gui.scale, y + 8 * gui.scale,), _("Export Playlist"), colours.grey(230), 213)

        x += round(15 * gui.scale)
        y += round(25 * gui.scale)

        ddt.text((x, y + 8 * gui.scale,), _("Save directory"), colours.grey(230), 11)
        y += round(30 * gui.scale)

        rect1 = (x, y, round(450 * gui.scale), round(16 * gui.scale))
        fields.add(rect1)
        #ddt.rect(rect1, [40, 40, 40, 255], True)
        ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
        self.directory_text_box.text = current["path"]
        self.directory_text_box.draw(x + round(4 * gui.scale), y, colours.box_input_text, True, width=rect1[2] - 8 * gui.scale, click=gui.level_2_click)
        current["path"] = self.directory_text_box.text

        y += round(30 * gui.scale)
        if pref_box.toggle_square(x, y, current["type"] == "xspf", "XSPF", gui.level_2_click):
            current["type"] = "xspf"
        if pref_box.toggle_square(x + round(80 * gui.scale), y, current["type"] == "m3u", "M3U", gui.level_2_click):
            current["type"] = "m3u"
        #pref_box.toggle_square(x + round(160 * gui.scale), y, False, "PLS", gui.level_2_click)
        y += round(35 * gui.scale)
        current["relative"] = pref_box.toggle_square(x, y, current["relative"], _("Use relative paths"), gui.level_2_click)
        y += round(60 * gui.scale)
        current["auto"] = pref_box.toggle_square(x, y, current["auto"], _("Auto-export"), gui.level_2_click)

        y += round(0 * gui.scale)
        ww = ddt.get_text_w(_("Export"), 211)
        x = ((int(window_size[0] / 2) - int(w / 2)) + w) - (ww + round(40 * gui.scale))

        prefs.playlist_exports[self.id] = current

        if draw.button(_("Export"), x, y, press=gui.level_2_click):
            self.run_export(current, self.id, warnings=True)


    def run_export(self, current, id, warnings=True):
        print("Export playlist")
        path = current["path"]
        if not os.path.isdir(path):
            if warnings:
                show_message(_("Directory does not exist"), mode="warning")
            return
        target = ""
        if current["type"] == "xspf":
            target = export_xspf(id_to_pl(id), direc=path, relative=current["relative"], show=False)
        if current["type"] == "m3u":
            target = export_m3u(id_to_pl(id), direc=path, relative=current["relative"], show=False)

        if warnings and target != 1:
            show_message("Playlist exported", target, mode="done")

export_playlist_box = ExportPlaylistBox()

def toggle_repeat():
    gui.update += 1
    pctl.repeat_mode ^= True
    if pctl.mpris is not None:
        pctl.mpris.update_loop()

tauon.toggle_repeat = toggle_repeat

def menu_repeat_off():
    pctl.repeat_mode = False
    pctl.album_repeat_mode = False
    if pctl.mpris is not None:
        pctl.mpris.update_loop()

def menu_set_repeat():
    pctl.repeat_mode = True
    pctl.album_repeat_mode = False
    if pctl.mpris is not None:
        pctl.mpris.update_loop()

def menu_album_repeat():
    pctl.repeat_mode = True
    pctl.album_repeat_mode = True
    if pctl.mpris is not None:
        pctl.mpris.update_loop()

tauon.menu_album_repeat = menu_album_repeat
tauon.menu_repeat_off = menu_repeat_off
tauon.menu_set_repeat = menu_set_repeat

repeat_menu.add(_("Repeat OFF"), menu_repeat_off)
repeat_menu.add(_("Repeat Track"), menu_set_repeat)
repeat_menu.add(_("Repeat Album"), menu_album_repeat)

def toggle_random():
    gui.update += 1
    pctl.random_mode ^= True
    if pctl.mpris is not None:
        pctl.mpris.update_shuffle()

tauon.toggle_random = toggle_random

def toggle_random_on():
    pctl.random_mode = True
    if pctl.mpris is not None:
        pctl.mpris.update_shuffle()
def toggle_random_off():
    pctl.random_mode = False
    if pctl.mpris is not None:
        pctl.mpris.update_shuffle()


def menu_shuffle_off():
    pctl.random_mode = False
    pctl.album_shuffle_mode = False
    if pctl.mpris is not None:
        pctl.mpris.update_shuffle()

def menu_set_random():
    pctl.random_mode = True
    pctl.album_shuffle_mode = False
    if pctl.mpris is not None:
        pctl.mpris.update_shuffle()

def menu_album_random():
    pctl.random_mode = True
    pctl.album_shuffle_mode = True
    if pctl.mpris is not None:
        pctl.mpris.update_shuffle()


def toggle_shuffle_layout(albums=False):
    prefs.shuffle_lock ^= True
    if prefs.shuffle_lock:

        gui.shuffle_was_showcase = gui.showcase_mode
        gui.shuffle_was_random = pctl.random_mode
        gui.shuffle_was_repeat = pctl.repeat_mode

        if not gui.combo_mode:
            view_box.lyrics(hit=True)
        pctl.random_mode = True
        pctl.repeat_mode = False
        if albums:
            pctl.album_shuffle_lock_mode = True
        if pctl.playing_state == 0:
            pctl.advance()
    else:
        pctl.random_mode = gui.shuffle_was_random
        pctl.repeat_mode = gui.shuffle_was_repeat
        pctl.album_shuffle_lock_mode = False
        if not gui.shuffle_was_showcase:
            exit_combo()

def toggle_shuffle_layout_albums():
    toggle_shuffle_layout(albums=True)


def exit_shuffle_layout(_):
    return prefs.shuffle_lock

shuffle_menu.add(_("Shuffle Lockdown"), toggle_shuffle_layout)
shuffle_menu.add(_("Shuffle Lockdown Albums"), toggle_shuffle_layout_albums)
shuffle_menu.br()
shuffle_menu.add(_("Shuffle OFF"), menu_shuffle_off)
shuffle_menu.add(_("Shuffle Tracks"), menu_set_random)
shuffle_menu.add(_("Random Albums"), menu_album_random)

def bio_set_large():
    #if window_size[0] >= round(1000 * gui.scale):
    #gui.artist_panel_height = 320 * gui.scale
    prefs.bio_large = True
    if gui.artist_info_panel:
        artist_info_box.get_data(artist_info_box.artist_on)


def bio_set_small():
    #gui.artist_panel_height = 200 * gui.scale
    prefs.bio_large = False
    update_layout_do()
    if gui.artist_info_panel:
        artist_info_box.get_data(artist_info_box.artist_on)


def artist_info_panel_close():

    gui.artist_info_panel ^= True
    gui.update_layout()


def toggle_bio_size_deco():
    line = _("Make Large Size")
    if prefs.bio_large:
        line = _("Make Compact Size")

    return [colours.menu_text, colours.menu_background, line]


def toggle_bio_size():

    if prefs.bio_large:
        prefs.bio_large = False
        update_layout_do()
        #bio_set_small()

    else:
        prefs.bio_large = True
        update_layout_do()
        #bio_set_large()
    #gui.update_layout()


def flush_artist_bio(artist):

    if os.path.isfile(os.path.join(a_cache_dir, artist + '-lfm.txt')):
        os.remove(os.path.join(a_cache_dir, artist + '-lfm.txt'))
    artist_info_box.text = ""
    artist_info_box.artist_on = None


def test_shift(_):
    return key_shift_down or key_shiftr_down

def test_artist_dl(_):
    return not prefs.auto_dl_artist_data

artist_info_menu.add(_("Close Panel"), artist_info_panel_close)
artist_info_menu.add(_("Make Large"), toggle_bio_size, toggle_bio_size_deco)



def show_in_playlist():

    if album_mode and window_size[0] < 750 * gui.scale:
        toggle_album_mode()

    pctl.playlist_view_position = playlist_selected
    console.print("DEBUG: Position changed by show in playlist")
    shift_selection.clear()
    shift_selection.append(playlist_selected)
    pctl.render_playlist()


filter_icon = MenuIcon(asset_loader('filter.png', True))
filter_icon.colour = [43, 213, 255, 255]
filter_icon.xoff = 1

folder_icon = MenuIcon(asset_loader('folder.png', True))
info_icon = MenuIcon(asset_loader('info.png', True))

folder_icon.colour = [244, 220, 66, 255]
info_icon.colour = [61, 247, 163, 255]

power_bar_icon = asset_loader('power.png', True)

def open_folder_stem(path):

    if system == 'windows' or msys:
        line = r'explorer /select,"%s"' % (
            path.replace("/", "\\"))
        subprocess.Popen(line)
    else:
        line = path
        line += "/"
        if macos:
            subprocess.Popen(['open', line])
        else:
            subprocess.Popen(['xdg-open', line])

def open_folder(index):

    track = pctl.master_library[index]
    if track.is_network:
        show_message("Can't open folder of a network track.")
        return

    if system == 'windows' or msys:
        line = r'explorer /select,"%s"' % (
            track.fullpath.replace("/", "\\"))
        subprocess.Popen(line)
    else:
        line = track.parent_folder_path
        line += "/"
        if macos:
            line = track.fullpath
            subprocess.Popen(['open', '-R', line])
        else:
            subprocess.Popen(['xdg-open', line])





def tag_to_new_playlist(tag_item):
    path_stem_to_playlist(tag_item.path, tag_item.name)

def folder_to_new_playlist_by_track_id(track_id):
    track = pctl.g(track_id)
    path_stem_to_playlist(track.parent_folder_path, track.parent_folder_name)

def stem_to_new_playlist(path):
    path_stem_to_playlist(path, os.path.basename(path))



move_jobs = []
move_in_progress = False

def move_playing_folder_to_tree_stem(path):
    move_playing_folder_to_stem(path, pl_id=tree_view_box.get_pl_id())

def move_playing_folder_to_stem(path, pl_id=None):

    if not pl_id:
        pl_id = pctl.multi_playlist[pctl.active_playlist_viewing][6]

    track = pctl.playing_object()

    if not track or pctl.playing_state == 0:
        show_message("No item is currently playing")
        return

    move_folder = track.parent_folder_path

    # Stop playing track if its in the current folder
    if pctl.playing_state > 0:
        if move_folder in pctl.playing_object().parent_folder_path:
            pctl.stop(True)

    target_base = path

    # Determine name for artist folder
    artist = track.artist
    if track.album_artist:
        artist = track.album_artist

    # Make filename friendly
    artist = filename_safe(artist)

    # Sanity checks
    if track.is_network:
        show_message("This track is a networked track.", mode="error")
        return

    if not os.path.isdir(move_folder):
        show_message("The source folder does not exist.", mode="error")
        return

    if not os.path.isdir(target_base):
        show_message("The destination folder does not exist.", mode="error")
        return

    if os.path.normpath(target_base) == os.path.normpath(move_folder):
        show_message("The destination and source folders are the same.", mode="error")
        return

    if len(target_base) < 4:
        show_message("Safety interupt! The source path seems oddly short.", target_base, mode='error')
        return

    protect = ("", "Documents", "Music", "Desktop", "Downloads")
    for fo in protect:
        if move_folder.strip('\\/') == os.path.join(os.path.expanduser('~'), fo).strip("\\/"):
            show_message("Better not do anything to that folder!", os.path.join(os.path.expanduser('~'), fo), mode='warning')
            return

    if directory_size(move_folder) > 3000000000:
        show_message("Folder size safety limit reached! (3GB)", move_folder, mode='warning')
        return


    # Use target folder if it already is an artist folder
    if os.path.basename(target_base).lower() == artist.lower():
        artist_folder = target_base

    # Make artist folder if it does not exist
    else:
        artist_folder = os.path.join(target_base, artist)
        if not os.path.exists(artist_folder):
            os.makedirs(artist_folder)

    # Remove all tracks with the old paths
    for pl in pctl.multi_playlist:
        for i in reversed(range(len(pl[2]))):
            if pctl.g(pl[2][i]).parent_folder_path == track.parent_folder_path:
                del pl[2][i]

    # Find insert location
    pl = pctl.multi_playlist[id_to_pl(pl_id)][2]

    matches = []
    insert = 0

    for i, item in enumerate(pl):
        if pctl.g(item).fullpath.startswith(target_base):
            insert = i

    for i, item in enumerate(pl):
        if pctl.g(item).fullpath.startswith(artist_folder):
            insert = i

    print("The folder to be moved is: " + move_folder)
    load_order = LoadClass()
    load_order.target = os.path.join(artist_folder, track.parent_folder_name)
    load_order.playlist = pl_id
    load_order.playlist_position = insert

    print(artist_folder)
    print(os.path.join(artist_folder, track.parent_folder_name))
    move_jobs.append((move_folder, os.path.join(artist_folder, track.parent_folder_name), True,
                      track.parent_folder_name, load_order))
    tm.ready("worker")


def move_playing_folder_to_tag(tag_item):
    move_playing_folder_to_stem(tag_item.path)


def re_import4(id):

    p = None
    for i, idd in enumerate(default_playlist):
        if idd == id:
            p = i
            break

    load_order = LoadClass()

    if p is not None:
        load_order.playlist_position = p

    load_order.replace_stem = True
    load_order.target = pctl.g(id).parent_folder_path
    load_order.notify = True
    load_order.playlist = pctl.multi_playlist[pctl.active_playlist_viewing][6]
    load_orders.append(copy.deepcopy(load_order))
    show_message("Rescanning folder...", pctl.g(id).parent_folder_path, mode='info')


def re_import3(stem):

    p = None
    for i, id in enumerate(default_playlist):
        if pctl.g(id).fullpath.startswith(stem + "/"):
            p = i
            break

    load_order = LoadClass()

    if p is not None:
        load_order.playlist_position = p

    load_order.replace_stem = True
    load_order.target = stem
    load_order.notify = True
    load_order.playlist = pctl.multi_playlist[pctl.active_playlist_viewing][6]
    load_orders.append(copy.deepcopy(load_order))
    show_message("Rescanning folder...", stem, mode='info')


def collapse_tree_deco():

    pl_id = tree_view_box.get_pl_id()

    if tree_view_box.opens.get(pl_id):
        return [colours.menu_text, colours.menu_background, None]
    else:
        return [colours.menu_text_disabled, colours.menu_background, None]

def collapse_tree():
    tree_view_box.collapse_all()

def lock_folder_tree():

    if tree_view_box.lock_pl:
        tree_view_box.lock_pl = None
    else:
        tree_view_box.lock_pl = pctl.multi_playlist[pctl.active_playlist_viewing][6]

def lock_folder_tree_deco():

    if tree_view_box.lock_pl:
        return [colours.menu_text, colours.menu_background, _("Unlock Panel")]
    else:
        return [colours.menu_text, colours.menu_background, _("Lock Panel")]

folder_tree_stem_menu.add(_('Open Folder'), open_folder_stem, pass_ref=True, icon=folder_icon)
folder_tree_menu.add(_('Open Folder'), open_folder, pass_ref=True, icon=folder_icon)

lightning_menu.add(_("Filter to New Playlist"), tag_to_new_playlist, pass_ref=True, icon=filter_icon)
folder_tree_menu.add(_("Filter to New Playlist"), folder_to_new_playlist_by_track_id, pass_ref=True, icon=filter_icon)
folder_tree_stem_menu.add(_("Filter to New Playlist"), stem_to_new_playlist, pass_ref=True, icon=filter_icon)
folder_tree_stem_menu.add(_("Rescan Folder"), re_import3, pass_ref=True)
folder_tree_menu.add(_("Rescan Folder"), re_import4, pass_ref=True)
lightning_menu.add(_("Move Playing Folder Here"), move_playing_folder_to_tag, pass_ref=True)

folder_tree_stem_menu.add(_("Move Playing Folder Here"), move_playing_folder_to_tree_stem, pass_ref=True)



folder_tree_stem_menu.br()

folder_tree_stem_menu.add(_('Collapse All'), collapse_tree, collapse_tree_deco)



folder_tree_stem_menu.add("lock", lock_folder_tree, lock_folder_tree_deco)
#folder_tree_menu.add("lock", lock_folder_tree, lock_folder_tree_deco)

gallery_menu.add(_('Open Folder'), open_folder, pass_ref=True, icon=folder_icon)

gallery_menu.add(_("Show in Playlist"), show_in_playlist)


def finish_current():

    playing_object = pctl.playing_object()
    if playing_object is None:
        show_message("")

    if not pctl.force_queue:

        pctl.force_queue.insert(0, queue_item_gen(playing_object.index,
                                    pctl.playlist_playing_position, pl_to_id(pctl.active_playlist_playing), 1, 1))



def add_album_to_queue(ref, position=None, playlist_id=None):
    if position is None:
        position = r_menu_position
    if playlist_id is None:
        playlist_id = pl_to_id(pctl.active_playlist_viewing)

    partway = 0
    playing_object = pctl.playing_object()
    if not pctl.force_queue and playing_object is not None:
        if pctl.g(ref).parent_folder_path == playing_object.parent_folder_path:
            partway = 1

    queue_object = queue_item_gen(ref, position, playlist_id, 1, partway)
    pctl.force_queue.append(queue_object)
    queue_timer_set(queue_object=queue_object)
    if prefs.stop_end_queue:
        pctl.auto_stop = False


def add_album_to_queue_fc(ref):

    playing_object = pctl.playing_object()
    if playing_object is None:
        show_message("")

    queue_item = None

    if not pctl.force_queue:
        queue_item = queue_item_gen(playing_object.index,
                                    pctl.playlist_playing_position, pl_to_id(pctl.active_playlist_playing), 1, 1)
        pctl.force_queue.insert(0, queue_item)
        add_album_to_queue(ref)
        return

    if pctl.force_queue[0][4] == 1:
        queue_item = queue_item_gen(ref,
                                    pctl.playlist_playing_position, pl_to_id(pctl.active_playlist_playing), 1, 0)
        pctl.force_queue.insert(1, queue_item)
    else:

        p = pctl.g(ref).parent_folder_path
        p = ""
        if pctl.playing_ready():
            p = pctl.playing_object().parent_folder_path

        # fixme for network tracks

        for i, item in enumerate(pctl.force_queue):

            if p != pctl.g(item[0]).parent_folder_path:
                queue_item = queue_item_gen(ref,
                                                          pctl.playlist_playing_position,
                                                          pl_to_id(pctl.active_playlist_playing), 1, 0, )
                pctl.force_queue.insert(i, queue_item)
                break

        else:
            queue_item = queue_item_gen(ref,
                                        pctl.playlist_playing_position, pl_to_id(pctl.active_playlist_playing), 1, 0,)
            pctl.force_queue.insert(len(pctl.force_queue), queue_item)
    if queue_item:
        queue_timer_set(queue_object=queue_item)
    if prefs.stop_end_queue:
        pctl.auto_stop = False

gallery_menu.add(_("Add Album to Queue"), add_album_to_queue, pass_ref=True)
gallery_menu.add(_("Enqueue Album Next"), add_album_to_queue_fc, pass_ref=True)


def cancel_import():

    if transcode_list:
        del transcode_list[1:]
        gui.tc_cancel = True
    if loading_in_progress:
        gui.im_cancel = True
    if gui.sync_progress:
        gui.stop_sync = True
        gui.sync_progress = _("Aborting Sync")


cancel_menu.add(_("Cancel"), cancel_import)


def toggle_lyrics_show(a):
    return not gui.combo_mode


def toggle_side_art_deco():
    colour = colours.menu_text
    if prefs.show_side_lyrics_art_panel:
        line = _("Hide Metadata Panel")
    else:
        line = _("Show Metadata Panel")

    if gui.combo_mode:
        colour = colours.menu_text_disabled

    return [colour, colours.menu_background, line]

def toggle_lyrics_panel_position_deco():
    colour = colours.menu_text
    if prefs.lyric_metadata_panel_top:
        line = _("Panel Below Lyrics")
    else:
        line = _("Panel Above Lyrics")

    if gui.combo_mode or not prefs.show_side_lyrics_art_panel:
        colour = colours.menu_text_disabled

    return [colour, colours.menu_background, line]

def toggle_lyrics_panel_position():
    prefs.lyric_metadata_panel_top ^= True

def lyrics_in_side_show(track_object):

    if gui.combo_mode or not prefs.show_lyrics_side:
        return False
    return True



def toggle_side_art():
    prefs.show_side_lyrics_art_panel ^= True


def toggle_lyrics_deco(track_object):

    colour = colours.menu_text

    if gui.combo_mode:
        if prefs.show_lyrics_showcase:
            line = _("Hide Lyrics")
        else:
            line = _("Show Lyrics")
        if not track_object or track_object.lyrics == "":
            colour = colours.menu_text_disabled
        return [colour, colours.menu_background, line]

    if prefs.side_panel_layout == 1: #and prefs.show_side_art:

        if prefs.show_lyrics_side:
            line = _("Hide Lyrics")
        else:
            line = _("Show Lyrics")
        if track_object.lyrics == "":
            colour = colours.menu_text_disabled
        return [colour, colours.menu_background, line]

    if prefs.show_lyrics_side:
        line = _("Hide Lyrics")
    else:
        line = _("Show Lyrics")
    if track_object.lyrics == "":
        colour = colours.menu_text_disabled
    return [colour, colours.menu_background, line]


def toggle_lyrics(track_object):

    if not track_object:
        return

    if gui.combo_mode:
        prefs.show_lyrics_showcase ^= True
        # if prefs.show_lyrics_showcase and track_object.lyrics == "":
        #     show_message(_("No lyrics for this track"))
    else:

        # Handling for alt panel layout
        # if prefs.side_panel_layout == 1 and prefs.show_side_art:
        #     #prefs.show_side_art = False
        #     prefs.show_lyrics_side = True
        #     return

        prefs.show_lyrics_side ^= True
        # if prefs.show_lyrics_side and track_object.lyrics == "":
        #     show_message(_("No lyrics for this track"))


def get_lyric_fire(track_object, silent=False):

    lyrics_ren.lyrics_position = 0

    if not prefs.lyrics_enables:
        if not silent:
            show_message(_("There are no lyric sources enabled."),
                         "See 'lyrics settings' under 'functions' tab in settings.", mode='info')
        return

    t = lyrics_fetch_timer.get()
    print("Lyric rate limit timer is: " + str(t) + " / -60")
    if t < -40:
        print("Lets try again later")
        if not silent:
            show_message(_("Let's be polite and try later."))

            if t < -65:
                show_message("Stop requesting lyrics AAAAAA.", mode='error')

        # If the user keeps pressing, lets mess with them haha
        lyrics_fetch_timer.force_set(t - 5)

        return 'later'

    if t > 0:
        lyrics_fetch_timer.set()
        t = 0

    lyrics_fetch_timer.force_set(t - 10)

    if not silent:
        show_message(_("Searching..."))

    s_artist = track_object.artist
    s_title = track_object.title

    if s_artist in prefs.lyrics_subs:
        s_artist = prefs.lyrics_subs[s_artist]
    if s_title in prefs.lyrics_subs:
        s_title = prefs.lyrics_subs[s_title]

    console.print(f"Searching for lyrics: {s_artist} - {s_title}", level=1)

    found = False
    for name in prefs.lyrics_enables:

        if name in lyric_sources.keys():
            func = lyric_sources[name]

            try:
                lyrics = func(s_artist, s_title)
                if lyrics:
                    console.print(f"Found lyrics from {name}", level=1)
                    track_object.lyrics = lyrics
                    found = True
                    break
            except Exception as e:
                console.print(str(e))

            if not found:
                console.print(f"Could not find lyrics from source {name}", level=1)

    if not found:
        if not silent:
            show_message(_("No lyrics for this track were found"))
    else:
        gui.message_box = False
        if not gui.showcase_mode:
            prefs.show_lyrics_side = True
        gui.update += 1
        lyrics_ren.lyrics_position = 0
        pctl.notify_change()


def get_lyric_wiki(track_object):

    if track_object.artist == "" or track_object.title == "":
        show_message("Insufficient metadata to get lyrics", mode='warning')
        return

    shoot_dl = threading.Thread(target=get_lyric_fire, args=([track_object]))
    shoot_dl.daemon = True
    shoot_dl.start()

    print("..Done")

def get_lyric_wiki_silent(track_object):

    print("Searching for lyrics...")

    if track_object.artist == "" or track_object.title == "":
        return

    shoot_dl = threading.Thread(target=get_lyric_fire, args=([track_object, True]))
    shoot_dl.daemon = True
    shoot_dl.start()

    print("..Done")

def test_auto_lyrics(track_object):

    if not track_object:
        return

    if prefs.auto_lyrics and not track_object.lyrics and track_object.index not in prefs.auto_lyrics_checked:
        if lyrics_check_timer.get() > 5 and pctl.playing_time > 1:
            result = get_lyric_wiki_silent(track_object)
            if result == "later":
                pass
            else:
                lyrics_check_timer.set()
                prefs.auto_lyrics_checked.append(track_object.index)


def get_bio(track_object):

    if track_object.artist != "":
        lastfm.get_bio(track_object.artist)

def search_lyrics_deco(track_object):

    if not track_object.lyrics:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]

showcase_menu.add(_('Search for Lyrics'), get_lyric_wiki, search_lyrics_deco, pass_ref=True, pass_ref_deco=True)



def search_guitarparty(track_object):
    if not track_object.title:
        show_message("Insufitent metadata to search")
    gc.fetch(track_object)

def search_guitarparty_showtest(_):
    return gui.combo_mode and prefs.guitar_chords

showcase_menu.add(_('Search GuitarParty'), search_guitarparty, pass_ref=True, show_test=search_guitarparty_showtest)


def paste_lyrics_deco():

    if SDL_HasClipboardText():
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]

def paste_lyrics(track_object):

    if SDL_HasClipboardText():
        clip = SDL_GetClipboardText()
        #print(clip)
        track_object.lyrics = clip.decode('utf-8')

    else:
        print('NO TEXT TO PASTE')


def paste_chord_lyrics(track_object):
    if track_object.title:
        gc.save_format_b(track_object)

def chord_lyrics_paste_show_test(_):

    return gui.combo_mode and prefs.guitar_chords

def clear_chord_lyrics(track_object):
    if track_object.title:
        gc.clear(track_object)

showcase_menu.add(_('Paste Chord Lyrics'), paste_chord_lyrics, pass_ref=True, show_test=chord_lyrics_paste_show_test)
showcase_menu.add(_('Clear Chord Lyrics'), clear_chord_lyrics, pass_ref=True, show_test=chord_lyrics_paste_show_test)


def copy_lyrics_deco(track_object):

    if track_object.lyrics:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]


def copy_lyrics(track_object):
    copy_to_clipboard(track_object.lyrics)


def clear_lyrics(track_object):
    track_object.lyrics = ""

def clear_lyrics_deco(track_object):

    if track_object.lyrics:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]

def split_lyrics(track_object):

    if track_object.lyrics != "":
        track_object.lyrics = track_object.lyrics.replace(". ", ". \n")
    else:
        pass

def show_sub_search(track_object):
    sub_lyrics_box.activate(track_object)

showcase_menu.add(_('Toggle Lyrics'), toggle_lyrics, toggle_lyrics_deco, pass_ref=True, pass_ref_deco=True)
showcase_menu.add_sub("Misc…", 150)
showcase_menu.add_to_sub(_('Substitute Search...'), 0, show_sub_search, pass_ref=True)
showcase_menu.add_to_sub(_('Paste Lyrics'), 0, paste_lyrics, paste_lyrics_deco, pass_ref=True)
showcase_menu.add_to_sub(_('Copy Lyrics'), 0, copy_lyrics, copy_lyrics_deco, pass_ref=True, pass_ref_deco=True)
showcase_menu.add_to_sub(_('Clear Lyrics'), 0, clear_lyrics, clear_lyrics_deco, pass_ref=True, pass_ref_deco=True)
showcase_menu.add_to_sub('Toggle art panel', 0, toggle_side_art, toggle_side_art_deco, show_test=lyrics_in_side_show)
showcase_menu.add_to_sub('Toggle art position', 0, toggle_lyrics_panel_position, toggle_lyrics_panel_position_deco, show_test=lyrics_in_side_show)


center_info_menu.add(_('Search for Lyrics'), get_lyric_wiki, search_lyrics_deco, pass_ref=True, pass_ref_deco=True)
center_info_menu.add(_('Toggle Lyrics'), toggle_lyrics, toggle_lyrics_deco, pass_ref=True, pass_ref_deco=True)
center_info_menu.add_sub("Misc…", 150)
center_info_menu.add_to_sub(_('Substitute Search...'), 0, show_sub_search, pass_ref=True)
center_info_menu.add_to_sub(_('Paste Lyrics'), 0, paste_lyrics, paste_lyrics_deco, pass_ref=True)
center_info_menu.add_to_sub(_('Copy Lyrics'), 0, copy_lyrics, copy_lyrics_deco, pass_ref=True, pass_ref_deco=True)
center_info_menu.add_to_sub(_('Clear Lyrics'), 0, clear_lyrics, clear_lyrics_deco, pass_ref=True, pass_ref_deco=True)
center_info_menu.add_to_sub('Toggle art panel', 0, toggle_side_art, toggle_side_art_deco, show_test=lyrics_in_side_show)
center_info_menu.add_to_sub('Toggle art position', 0, toggle_lyrics_panel_position, toggle_lyrics_panel_position_deco, show_test=lyrics_in_side_show)


def save_embed_img(track_object):

    filepath = track_object.fullpath
    folder = track_object.parent_folder_path
    ext = track_object.file_ext

    if track_object.is_network:
        show_message("Saving network images not implemented")
        return

    try:
        pic = album_art_gen.get_embed(track_object)

        if not pic:
            show_message("Image save error.", "No embedded album art found file.", mode='warning')
            return

        source_image = io.BytesIO(pic)
        im = Image.open(source_image)

        source_image.close()

        ext = "." + im.format.lower()
        if im.format == "JPEG":
            ext = ".jpg"

        target = os.path.join(folder, "embed-" + str(im.height) + "px-" + str(track_object.index) + ext)

        if len(pic) > 30:
            with open(target, 'wb') as w:
                w.write(pic)

        open_folder(track_object.index)

    except:
        show_message("Image save error.", "A mysterious error occurred", mode='error')

picture_menu = Menu(175)

def open_image_deco(track_object):

    info = album_art_gen.get_info(track_object)

    if info is None:
        return [colours.menu_text_disabled, colours.menu_background, None]

    line_colour = colours.menu_text

    return [line_colour, colours.menu_background, None]

def open_image(track_object):

    album_art_gen.open_external(track_object)

def extract_image_deco(track_object):

    info = album_art_gen.get_info(track_object)

    if info is None:
        return [colours.menu_text_disabled, colours.menu_background, None]

    if info[0] == 1:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]


picture_menu.add(_("Open Image"), open_image, open_image_deco, pass_ref=True, pass_ref_deco=True)


def cycle_image_deco(track_object):

    info = album_art_gen.get_info(track_object)

    if pctl.playing_state != 0 and (info is not None and info[1] > 1):
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]


def cycle_offset(track_object):
    album_art_gen.cycle_offset(track_object)

def cycle_offset_back(track_object):
    album_art_gen.cycle_offset_reverse(track_object)


# Next and previous pictures
#picture_menu.add(_("Next"), cycle_offset, cycle_image_deco, pass_ref=True, pass_ref_deco=True)
picture_menu.add(_("Previous"), cycle_offset_back, cycle_image_deco, pass_ref=True, pass_ref_deco=True)

# Extract embedded artwork from file
picture_menu.add(_('Extract Image'), save_embed_img, extract_image_deco, pass_ref=True, pass_ref_deco=True)


def dl_art_deco(tr):

    if not tr.album or not tr.artist:
        return [colours.menu_text_disabled, colours.menu_background, None]
    return [colours.menu_text, colours.menu_background, None]


def download_art1(tr):

    if tr.is_network:
        show_message(_("Cannot download art for network tracks."))
        return

    # Determine noise of folder ----------------
    siblings = []
    parent = tr.parent_folder_path

    for pl in pctl.multi_playlist:
        for ti in pl[2]:
            tr = pctl.g(ti)
            if tr.parent_folder_path == parent:
                siblings.append(tr)

    album_tags = []
    date_tags = []

    for tr in siblings:
        album_tags.append(tr.album)
        date_tags.append(tr.date)

    album_tags = set(album_tags)
    date_tags = set(date_tags)

    if len(album_tags) > 2 or len(date_tags) > 2:
        show_message(_("It doesn't look like this folder belongs to a single album, sorry"))
        return

    # -------------------------------------------

    if not os.path.isdir(tr.parent_folder_path):
        show_message("Directory missing.")
        return


    try:
        show_message(_("Looking up MusicBrainz ID..."))



        if 'musicbrainz_releasegroupid' not in tr.misc or 'musicbrainz_artistids' not in tr.misc or not tr.misc['musicbrainz_artistids']:

            print("MusicBrainz ID lookup...")

            artist = tr.album_artist
            if not tr.album:
                return
            if not artist:
                artist = tr.artist

            s = musicbrainzngs.search_release_groups(tr.album, artist=artist, limit=1)

            album_id = s['release-group-list'][0]['id']
            artist_id = s['release-group-list'][0]['artist-credit'][0]['artist']['id']

            print("Found release group ID: " + album_id)
            print("Found artist ID: " + artist_id)

        else:

            album_id = tr.misc['musicbrainz_releasegroupid']
            artist_id = tr.misc['musicbrainz_artistids'][0]

            print("Using tagged release group ID: " + album_id)
            print("Using tagged artist ID: " + artist_id)

        if prefs.enable_fanart_cover:
            try:
                show_message("Searching fanart.tv for cover art...")

                r = requests.get("http://webservice.fanart.tv/v3/music/albums/" \
                                 + artist_id + "?api_key=" + prefs.fatvap, timeout=(4, 10))

                artlink = r.json()['albums'][album_id]['albumcover'][0]['url']
                id = r.json()['albums'][album_id]['albumcover'][0]['id']

                response = urllib.request.urlopen(artlink)
                info = response.info()

                t = io.BytesIO()
                t.seek(0)
                t.write(response.read())
                t.seek(0, 2)
                l = t.tell()
                t.seek(0)

                if info.get_content_maintype() == 'image' and l > 1000:

                    if info.get_content_subtype() == 'jpeg':
                        filepath = os.path.join(tr.parent_folder_path, "cover-" + id + ".jpg")
                    elif info.get_content_subtype() == 'png':
                        filepath = os.path.join(tr.parent_folder_path, "cover-" + id + ".png")
                    else:
                        show_message("Could not detect downloaded filetype.", mode='error')
                        return

                    f = open(filepath, 'wb')
                    f.write(t.read())
                    f.close()

                    show_message(_("Cover art downloaded from fanart.tv"), mode='done')
                    #clear_img_cache()
                    for track_id in default_playlist:
                        if tr.parent_folder_path == pctl.g(track_id).parent_folder_path:
                            clear_track_image_cache(pctl.g(track_id))
                    return
            except:
                print("Failed to get from fanart.tv")

        show_message("Searching MusicBrainz for cover art...")
        t = io.BytesIO(musicbrainzngs.get_release_group_image_front(album_id, size=None))
        l = 0
        t.seek(0, 2)
        l = t.tell()
        t.seek(0)
        if l > 1000:
            filepath = os.path.join(tr.parent_folder_path, album_id + ".jpg")
            f = open(filepath, 'wb')
            f.write(t.read())
            f.close()

            show_message(_("Cover art downloaded from MusicBrainz"), mode='done')
            #clear_img_cache()
            clear_track_image_cache(tr)

            for track_id in default_playlist:
                if tr.parent_folder_path == pctl.g(track_id).parent_folder_path:
                    clear_track_image_cache(pctl.g(track_id))

            return

    except:
        show_message(_("Matching cover art or ID could not be found."))

def download_art1_fire(track_object):

    shoot_dl = threading.Thread(target=download_art1, args=[track_object])
    shoot_dl.daemon = True
    shoot_dl.start()


def remove_embed_picture(track_object):

    index = track_object.index

    if key_shift_down or key_shiftr_down:
        tracks = [index]
        if track_object.is_cue or track_object.is_network:
            show_message("Error - No handling for this kind of track", mode='warning')
            return
    else:
        tracks = []
        original_parent_folder = track_object.parent_folder_name
        for k in default_playlist:
            tr = pctl.g(k)
            if original_parent_folder == tr.parent_folder_name:
                tracks.append(k)

    removed = 0
    pr = pctl.stop(True)
    try:
        for item in tracks:

            tr = pctl.g(item)

            if tr.is_cue:
                continue

            if tr.is_network:
                continue

            if "MP3" == tr.file_ext:
                try:
                    tag = mutagen.id3.ID3(tr.fullpath)
                    tag.delall("APIC")
                    remove = True
                    tag.save(padding=no_padding)
                except Exception as e:
                    print("No APIC found")
                    removed += 1

            if tr.file_ext == "M4A":
                try:
                    tag = mutagen.mp4.MP4(tr.fullpath)
                    del tag.tags["covr"]
                    tag.save(padding=no_padding)
                    removed += 1
                except:
                    pass

            if tr.file_ext in ("OGA", "OPUS", "OGG"):
                show_message("Removing vorbis image not implemented")
                # try:
                #     tag = mutagen.File(tr.fullpath).tags
                #     print(tag)
                #     removed += 1
                # except:
                #     pass

            if "FLAC" == tr.file_ext:
                try:
                    tag = mutagen.flac.FLAC(tr.fullpath)
                    tag.clear_pictures()
                    tag.save(padding=no_padding)
                    removed += 1
                except:
                    pass

            clear_track_image_cache(tr)

    except Exception as e:
        show_message("Image remove error", mode='error')
        return

    if removed == 0:
        show_message(_("Image removal failed."), mode='error')
        return
    elif removed == 1:
        show_message(_("Deleted embedded picture from file"), mode='done')
    else:
        show_message("Deleted embedded picture from " + str(removed) + " files", mode='done')
    if pr == 1:
        pctl.revert()

del_icon = asset_loader('del.png', True)
delete_icon = MenuIcon(del_icon)


def delete_file_image(track_object):

    try:
        showc = album_art_gen.get_info(track_object)
        if showc is not None and showc[0] == 0:
            source = album_art_gen.get_sources(track_object)[showc[2]][1]
            os.remove(source)
            #clear_img_cache()
            clear_track_image_cache(track_object)
            print("Deleted file: " + source)
    except:
        show_message("Something went wrong", mode='error')



def delete_track_image_deco(track_object):
    info = album_art_gen.get_info(track_object)

    text = _("Delete Image File")
    line_colour = colours.menu_text

    if info is None:
        return [colours.menu_text_disabled, colours.menu_background, None]

    elif info and info[0] == 0:
        text = _("Delete Image File")

    elif info and info[0] == 1:
        if pctl.playing_state > 0 and track_object.file_ext in ("MP3", "FLAC", "M4A"):
            line_colour = colours.menu_text
        else:
            line_colour = colours.menu_text_disabled

        text = _("Delete Embedded | Folder")
        if key_shift_down or key_shiftr_down:
            text = _("Delete Embedded | Track")

    return [line_colour, colours.menu_background, text]


def delete_track_image(track_object):
    info = album_art_gen.get_info(track_object)
    if info and info[0] == 0:
        delete_file_image(track_object)
    elif info and info[0] == 1:
        remove_embed_picture(track_object)

picture_menu.add('Delete Image <combined>', delete_track_image, delete_track_image_deco, pass_ref=True, pass_ref_deco=True, icon=delete_icon)







picture_menu.add(_('Quick-Fetch Cover Art'), download_art1_fire, dl_art_deco, pass_ref=True, pass_ref_deco=True)


def toggle_gimage(mode=0):
    if mode == 1:
        return prefs.show_gimage
    prefs.show_gimage ^= True


def search_image_deco(track_object):

    if track_object.artist and track_object.album:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]

def ser_gimage(track_object):

    if track_object.artist and track_object.album:
        line = "https://www.google.com/search?tbm=isch&q=" + urllib.parse.quote(track_object.artist + " " + track_object.album)
        webbrowser.open(line, new=2, autoraise=True)

# picture_menu.add(_('Search Google for Images'), ser_gimage, search_image_deco, pass_ref=True, pass_ref_deco=True, show_test=toggle_gimage)

#picture_menu.add(_('Toggle art box'), toggle_side_art, toggle_side_art_deco)

picture_menu.add(_('Search for Lyrics'), get_lyric_wiki, search_lyrics_deco, pass_ref=True, pass_ref_deco=True)
picture_menu.add(_('Toggle Lyrics'), toggle_lyrics, toggle_lyrics_deco, pass_ref=True, pass_ref_deco=True)


def append_here():
    global cargo
    global default_playlist
    default_playlist += cargo


def paste_deco():

    active = False
    line = None
    if len(cargo) > 0:
        active = True
    elif SDL_HasClipboardText():
        text = copy_from_clipboard()
        if text.startswith("/") or "file://" in text or text.startswith("spotify"):
            active = True
        elif prefs.spot_mode and text.startswith("https://open.spotify.com/album/"): # or text.startswith("https://open.spotify.com/track/"):
            active = True
            line = _("Paste Spotify Album")

    if active:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, line]


def lightning_move_test(discard):
    return gui.lightning_copy and prefs.show_transfer



# def copy_deco():
#     line = "Copy"
#     if key_shift_down:
#         line = "Copy" #Folder From Library"
#     else:
#         line = "Copy"
#
#
#     return [colours.menu_text, colours.menu_background, line]


#playlist_menu.add('Paste', append_here, paste_deco)

def unique_template(string):

    return "<t>" in string or \
        "<title>" in string or \
        "<n>" in string or \
        "<number>" in string or \
        "<tracknumber>" in string or \
        "<tn>" in string or \
        "<sn>" in string or \
        "<singlenumber>" in string or \
        "<s>" in string or "%t" in string or "%tn" in string

def re_template_word(word, tr):

    if word == "aa" or word == "albumartist":

        if tr.album_artist:
            return tr.album_artist
        else:
            return tr.artist

    if word == "a" or word == "artist":
        return tr.artist

    if word == "t" or word == "title":
        return tr.title

    if word == "n" or word == "number" or word == "tracknumber" or word == "tn":
        if len(str(tr.track_number)) < 2:
            return "0" + str(tr.track_number)
        else:
            return str(tr.track_number)

    if word == "sn" or word == "singlenumber" or word == "singletracknumber" or word == "s":
        return str(tr.track_number)

    if word == "d" or word == "date" or word == "year":
        return str(tr.date)

    if word == "b" or "album" in word:
        return str(tr.album)

    if word == "g" or word == "genre":
        return tr.genre

    if word == "x" or "ext" in word or "file" in word:
        return tr.file_ext.lower()

    if word == "ux" or "upper" in word:
        return tr.file_ext.upper()

    if word == "c" or "composer" in word:
        return tr.composer

    if "comment" in word:
        return tr.comment.replace("\n", "").replace("\r", "")

    return ""


def parse_template2(string, track_object, strict=False):

    temp = ""
    out = ""

    mode = 0

    for c in string:

        if mode == 0:

            if c == "<":
                mode = 1
            else:
                out += c

        else:

            if c == ">":

                test = re_template_word(temp, track_object)
                if strict:
                    assert test
                out += test

                mode = 0
                temp = ""

            else:

                temp += c

    if "<und" in string:
        out = out.replace(" ", "_")

    return parse_template(out, track_object, strict=strict)

def parse_template(string, track_object, up_ext=False, strict=False):
    set = 0
    underscore = False
    output = ""

    while set < len(string):
        if string[set] == "%" and set < len(string) - 1:
            set += 1
            if string[set] == 'n':
                if len(str(track_object.track_number)) < 2:
                    output += "0"
                if strict:
                    assert str(track_object.track_number)
                output += str(track_object.track_number)
            elif string[set] == 'a':
                if up_ext and track_object.album_artist != "": # Context of renaming a folder
                    output += track_object.album_artist
                else:
                    if strict:
                        assert track_object.artist
                    output += track_object.artist
            elif string[set] == 't':
                if strict:
                    assert track_object.title
                output += track_object.title
            elif string[set] == 'c':
                if strict:
                    assert track_object.composer
                output += track_object.composer
            elif string[set] == 'd':
                if strict:
                    assert track_object.date
                output += track_object.date
            elif string[set] == 'b':
                if strict:
                    assert track_object.album
                output += track_object.album
            elif string[set] == 'x':
                if up_ext:
                    output += track_object.file_ext.upper()
                else:
                    output += "." + track_object.file_ext.lower()
            elif string[set] == 'u':
                underscore = True
        else:
            output += string[set]
        set += 1

    output = output.rstrip(" -").lstrip(" -")

    if underscore:
        output = output.replace(' ', "_")

    # Attempt to ensure the output text is filename safe
    output = filename_safe(output)

    return output

# Create playlist tab menu
tab_menu = Menu(160, show_icons=True)
radio_tab_menu = Menu(160, show_icons=True)

def rename_playlist(index, generator=False):

    gui.rename_playlist_box = True
    rename_playlist_box.edit_generator = False
    rename_playlist_box.playlist_index = index
    rename_playlist_box.x = mouse_position[0]
    rename_playlist_box.y = mouse_position[1]

    if generator:
        rename_playlist_box.y = window_size[1] // 2 - round(200 * gui.scale)
        rename_playlist_box.x = window_size[0] // 2 - round(250 * gui.scale)

    if rename_playlist_box.y > round(350 * gui.scale):
        rename_playlist_box.y = round(350 * gui.scale)

    if rename_playlist_box.y < gui.panelY:
        rename_playlist_box.y = gui.panelY + 10 * gui.scale

    if gui.radio_view:
        rename_text_area.set_text(pctl.radio_playlists[index]["name"])
    else:
        rename_text_area.set_text(pctl.multi_playlist[index][0])
    rename_text_area.highlight_all()
    gui.gen_code_errors = False

    if generator:
        rename_playlist_box.toggle_edit_gen()


def edit_generator_box(index):
    rename_playlist(index, generator=True)


tab_menu.add(_('Rename'), rename_playlist, pass_ref=True, hint="Ctrl+R")
radio_tab_menu.add(_('Rename'), rename_playlist, pass_ref=True, hint="Ctrl+R")


def pin_playlist_toggle(pl):
    pctl.multi_playlist[pl][8] ^= True

def pl_pin_deco(pl):

    #if pctl.multi_playlist[pl][8] == True and tab_menu.pos[1] >

    if pctl.multi_playlist[pl][8] == True:
        return [colours.menu_text, colours.menu_background, _("Pin")]
    else:
        return [colours.menu_text, colours.menu_background, _('Unpin')]

tab_menu.add("Pin", pin_playlist_toggle, pl_pin_deco, pass_ref=True, pass_ref_deco=True)



def pl_lock_deco(pl):

    if pctl.multi_playlist[pl][9] == True:
        return [colours.menu_text, colours.menu_background, _("Unlock")]
    else:
        return [colours.menu_text, colours.menu_background, _('Lock')]


def view_pl_is_locked(_):
    return pctl.multi_playlist[pctl.active_playlist_viewing][9]


def pl_is_locked(pl):
    if not pctl.multi_playlist:
        return False
    return pctl.multi_playlist[pl][9]

def lock_playlist_toggle(pl):
    pctl.multi_playlist[pl][9] ^= True


def lock_colour_callback():
    if pctl.multi_playlist[gui.tab_menu_pl][9]:
        if colours.lm:
            return [230, 180, 60, 255]
        return [240, 190, 10, 255]
    else:
        return None

lock_asset = asset_loader('lock.png', True)
lock_icon = MenuIcon(lock_asset)
lock_icon.base_asset_mod = asset_loader('unlock.png', True)
lock_icon.colour = [240, 190, 10, 255]
lock_icon.colour_callback = lock_colour_callback
lock_icon.xoff = 4
lock_icon.yoff = -1



tab_menu.add(_('Lock'), lock_playlist_toggle, pl_lock_deco, pass_ref=True, pass_ref_deco=True, icon=lock_icon)


def export_m3u(pl, direc=None, relative=False, show=True):
    if len(pctl.multi_playlist[pl][2]) < 1:
        show_message("There are no tracks in this playlist. Nothing to export")
        return 1

    if not direc:
        direc = os.path.join(user_directory, 'playlists')
        if not os.path.exists(direc):
            os.makedirs(direc)
    target = os.path.join(direc, pctl.multi_playlist[pl][0] + '.m3u')

    f = open(target, 'w', encoding='utf-8')
    f.write("#EXTM3U")
    for number in pctl.multi_playlist[pl][2]:
        track = pctl.master_library[number]
        title = track.artist
        if title:
            title += " - "
        title += track.title

        if not track.is_network:
            f.write("\n#EXTINF:")
            f.write(str(round(track.length)))
            if title:
                f.write(f",{title}")
            path = track.fullpath
            if relative:
                path = os.path.relpath(path, start=direc)
            f.write(f"\n{path}")
    f.close()

    if show:
        line = direc
        line += "/"
        if system == "windows" or msys:
            os.startfile(line)
        elif macos:
            subprocess.Popen(['open', line])
        else:
            subprocess.Popen(['xdg-open', line])
    return target

def export_xspf(pl, direc=None, relative=False, show=True):
    if len(pctl.multi_playlist[pl][2]) < 1:
        show_message("There are no tracks in this playlist. Nothing to export")
        return 1

    if not direc:
        direc = os.path.join(user_directory, 'playlists')
        if not os.path.exists(direc):
            os.makedirs(direc)

    target = os.path.join(direc, pctl.multi_playlist[pl][0] + '.xspf')

    xport = open(target, 'w', encoding='utf-8')
    xport.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    xport.write('<playlist version="1" xmlns="http://xspf.org/ns/0/">\n')
    xport.write('  <trackList>\n')

    for number in pctl.multi_playlist[pl][2]:
        track = pctl.master_library[number]
        path = track.fullpath
        if relative:
            path = os.path.relpath(path, start=direc)

        xport.write('    <track>\n')
        if track.title != "":
            xport.write('      <title>' + escape(track.title) + '</title>\n')
        if track.is_cue is False and track.fullpath != "":
            xport.write('      <location>' + escape(path) + '</location>\n')
        if track.artist != "":
            xport.write('      <creator>' + escape(track.artist) + '</creator>\n')
        if track.album != "":
            xport.write('      <album>' + escape(track.album) + '</album>\n')
        xport.write('      <duration>' + str(int(track.length * 1000)) + '</duration>\n')
        xport.write('    </track>\n')
    xport.write('  </trackList>\n')
    xport.write('</playlist>\n\n')
    xport.close()

    if show:
        line = direc
        line += "/"
        if system == "windows" or msys:
            os.startfile(line)
        elif macos:
            subprocess.Popen(['open', line])
        else:
            subprocess.Popen(['xdg-open', line])

    return target

def reload():
    if album_mode:
        reload_albums(quiet=True)

    #tree_view_box.clear_all()
    # elif gui.combo_mode:
    #     reload_albums(quiet=True)
    #     combo_pl_render.prep()


def clear_playlist(index):
    global default_playlist

    if pl_is_locked(index):
        show_message("Playlist is locked to prevent accidental erasure")
        return

    pctl.multi_playlist[index][7].clear()  # clear import folder list

    if not pctl.multi_playlist[index][2]:
        print("Playlist is already empty")
        return

    li = []
    for i, ref in enumerate(pctl.multi_playlist[index][2]):
        li.append((i, ref))

    undo.bk_tracks(index, list(reversed(li)))

    del pctl.multi_playlist[index][2][:]
    if pctl.active_playlist_viewing == index:
        default_playlist = pctl.multi_playlist[index][2]
        reload()

    # pctl.playlist_playing = 0
    pctl.multi_playlist[index][3] = 0
    if index == pctl.active_playlist_viewing:
        pctl.playlist_view_position = 0

    gui.pl_update = 1


def convert_playlist(pl, get_list=False):
    global transcode_list

    if system == 'windows' or msys:
        if not os.path.isfile(user_directory + '/encoder/ffmpeg.exe'):
            show_message("Error: Missing ffmpeg.exe from encoder directory",
                         "Expected location: " + user_directory + '/encoder/ffmpeg.exe', mode='warning')
            return
        # if prefs.transcode_codec == 'mp3' and not os.path.isfile(user_directory + '/encoder/lame.exe'):
        #     show_message("Error: Missing lame.exe from '/encoder' directory")
        #     return
    else:
        if shutil.which(ffmpeg) is None:
            show_message("Error: ffmpeg does not appear to be installed")
            return
        # if prefs.transcode_codec == 'mp3' and shutil.which('lame') is None:
        #     show_message("Error: LAME does not appear to be installed")
        #     return

    paths = []
    folders = []

    for track in pctl.multi_playlist[pl][2]:
        if pctl.master_library[track].parent_folder_path not in paths:
            paths.append(pctl.master_library[track].parent_folder_path)

    for path in paths:
        folder = []
        for track in pctl.multi_playlist[pl][2]:
            if pctl.master_library[track].parent_folder_path == path:
                folder.append(track)
                if prefs.transcode_codec == 'flac' and pctl.master_library[track].file_ext.lower() in ('mp3', 'opus',
                                                                                                       'm4a', 'mp4',
                                                                                                     'ogg', 'aac'):
                    show_message("This includes the conversion of a lossy codec to a lossless one!")

        folders.append(folder)

    if get_list:
        return folders

    transcode_list.extend(folders)



def get_folder_tracks_local(pl_in):
    selection = []
    parent = os.path.normpath(pctl.master_library[default_playlist[pl_in]].parent_folder_path)
    while pl_in < len(default_playlist) and parent == os.path.normpath(pctl.master_library[default_playlist[pl_in]].parent_folder_path):
        selection.append(pl_in)
        pl_in += 1
    return selection


def test_pl_tab_locked(pl):
    if gui.radio_view:
        return False
    return pctl.multi_playlist[pl][9]


# Clear playlist
tab_menu.add(_('Clear'), clear_playlist, pass_ref=True, disable_test=test_pl_tab_locked, pass_ref_deco=True)

def move_radio_playlist(source, dest):
    if dest > source:
        dest += 1
    try:
        temp = pctl.radio_playlists[source]
        pctl.radio_playlists[source] = "old"
        pctl.radio_playlists.insert(dest, temp)
        pctl.radio_playlists.remove("old")
        pctl.radio_playlist_viewing = pctl.radio_playlists.index(temp)
    except:
        print("Warning: Playlist move error")

def move_playlist(source, dest):
    global default_playlist
    if dest > source:
        dest += 1
    try:
        active = pctl.multi_playlist[pctl.active_playlist_playing]
        view = pctl.multi_playlist[pctl.active_playlist_viewing]

        temp = pctl.multi_playlist[source]
        pctl.multi_playlist[source] = "old"
        pctl.multi_playlist.insert(dest, temp)
        pctl.multi_playlist.remove("old")

        pctl.active_playlist_playing = pctl.multi_playlist.index(active)
        pctl.active_playlist_viewing = pctl.multi_playlist.index(view)
        default_playlist = default_playlist = pctl.multi_playlist[pctl.active_playlist_viewing][2]
    except:
        print("Warning: Playlist move error")


def delete_playlist(index, force=False, check_lock=False):

    if gui.radio_view:
        del pctl.radio_playlists[index]
        if not pctl.radio_playlists:
            pctl.radio_playlists = [{"uid": uid_gen(), "name": "Default", "items": []}]
        return

    global default_playlist

    if check_lock and pl_is_locked(index):
        show_message("Playlist is locked to prevent accidental deletion")
        return

    if not force:
        if pl_is_locked(index):
            show_message("Playlist is locked to prevent accidental deletion")
            return

        gen = pctl.gen_codes.get(pl_to_id(index), "")
        if (gen == "" or gen.startswith("self ")) and pctl.multi_playlist[index][2]:
            if not (key_shift_down or key_shiftr_down):
                show_message(_("Are you sure you want to delete this playlist?"), _("Try again while holding shift to confirm"))
                return

    if gui.rename_playlist_box:
        return

    # Set screen to be redrawn
    gui.pl_update = 1
    gui.update += 1

    # Backup the playlist to be deleted
    #pctl.playlist_backup.append(pctl.multi_playlist[index])
    #pctl.playlist_backup.append(pctl.multi_playlist[index])
    undo.bk_playlist(index)

    # If we're deleting the final playlist, delete it and create a blank one in place
    if len(pctl.multi_playlist) == 1:
        pctl.multi_playlist.clear()
        pctl.multi_playlist.append(pl_gen())
        default_playlist = pctl.multi_playlist[0][2]
        pctl.active_playlist_playing = 0
        return

    # Take note of the id of the playing playlist
    old_playing_id = pctl.multi_playlist[pctl.active_playlist_playing][6]

    # Take note of the id of the viewed open playlist
    old_view_id = pctl.multi_playlist[pctl.active_playlist_viewing][6]

    # Delete the requested playlist
    del pctl.multi_playlist[index]

    # Re-set the open viewed playlist number by uid
    for i, pl in enumerate(pctl.multi_playlist):

        if pl[6] == old_view_id:
            pctl.active_playlist_viewing = i
            break
    else:
        # print("Lost the viewed playlist!")
        # Try find the playing playlist and make it the viewed playlist
        for i, pl in enumerate(pctl.multi_playlist):
            if pl[6] == old_playing_id:
                pctl.active_playlist_viewing = i
                break
        else:
            # Playing playlist was deleted, lets just move down one playlist
            if pctl.active_playlist_viewing > 0:
                pctl.active_playlist_viewing -= 1

    # Re-initiate the now viewed playlist
    if old_view_id != pctl.multi_playlist[pctl.active_playlist_viewing][6]:
        default_playlist = pctl.multi_playlist[pctl.active_playlist_viewing][2]
        pctl.playlist_view_position = pctl.multi_playlist[pctl.active_playlist_viewing][3]
        console.print("DEBUG: Position reset by playlist delete")
        playlist_selected = pctl.multi_playlist[pctl.active_playlist_viewing][5]
        shift_selection = [playlist_selected]

        if album_mode:
            reload_albums(True)
            goto_album(pctl.playlist_view_position)

    # Re-set the playing playlist number by uid
    for i, pl in enumerate(pctl.multi_playlist):

        if pl[6] == old_playing_id:
            pctl.active_playlist_playing = i
            break
    else:
        print("Lost the playing playlist!")
        pctl.active_playlist_playing = pctl.active_playlist_viewing
        pctl.playlist_playing_position = -1

    test_show_add_home_music()

    # Cleanup
    ids = []
    for p in pctl.multi_playlist:
        ids.append(p[6])

    for key in list(gui.gallery_positions.keys()):
        if key not in ids:
            del gui.gallery_positions[key]
    for key in list(pctl.gen_codes.keys()):
        if key not in ids:
            del pctl.gen_codes[key]

    pctl.db_inc += 1

to_scan = []

def delete_playlist_force(index):
    delete_playlist(index, force=True, check_lock=True)

def delete_playlist_by_id(id, force=False, check_lock=False):
    delete_playlist(id_to_pl(id), force=force, check_lock=check_lock)

def delete_playlist_ask(index):
    if gui.radio_view:
        delete_playlist_force(index)
        return
    gen = pctl.gen_codes.get(pl_to_id(index), "")
    if (gen and not gen.startswith("self ")) or not pctl.multi_playlist[index][2]:
        delete_playlist(index)
        return

    gui.message_box_confirm_callback = delete_playlist_by_id
    gui.message_box_confirm_reference = (pl_to_id(index), True, True)
    show_message(_("Are you sure you want to delete playlist: %s?") % pctl.multi_playlist[index][0], mode="confirm")

def rescan_tags(pl):

    for track in pctl.multi_playlist[pl][2]:
        if pctl.master_library[track].is_cue is False:
            to_scan.append(track)
    tm.ready("worker")


# def re_import(pl):
#
#     path = pctl.multi_playlist[pl][7]
#     if path == "":
#         return
#     for i in reversed(range(len(pctl.multi_playlist[pl][2]))):
#         if path.replace('\\', '/') in pctl.master_library[pctl.multi_playlist[pl][2][i]].parent_folder_path:
#             del pctl.multi_playlist[pl][2][i]
#
#     load_order = LoadClass()
#     load_order.replace_stem = True
#     load_order.target = path
#     load_order.playlist = pctl.multi_playlist[pl][6]
#     load_orders.append(copy.deepcopy(load_order))


def re_import2(pl):

    paths = pctl.multi_playlist[pl][7]

    reduce_paths(paths)

    for path in paths:
        if os.path.isdir(path):
            load_order = LoadClass()
            load_order.replace_stem = True
            load_order.target = path
            load_order.notify = True
            load_order.playlist = pctl.multi_playlist[pl][6]
            load_orders.append(copy.deepcopy(load_order))

    if paths:
        show_message(_("Rescanning folders..."), mode='info')



def s_append(index):
    paste(playlist_no=index)


def append_playlist(index):

    global cargo
    pctl.multi_playlist[index][2] += cargo

    gui.pl_update = 1
    reload()


def tryint(s):
    try:
        return int(s)
    except:
        return s

def index_key(index):
    s = str(pctl.master_library[index].track_number)
    d = str(pctl.master_library[index].disc_number)

    # Make sure the value for disc number is an interger, make 1 if 0, otherwise ignore
    if d:
        try:
            dd = int(d)
            if dd < 2:
                dd = 1
            d = str(dd)
        except:
            d = ""

    # Add the disc number for sorting by CD, make it '1' if theres isnt one
    if s or d:
        if not d:
            s = "1" + "d" + s
        else:
            s = d + "d" + s

    # Use the filename if we dont have any metadata to sort by,
    # since it could likely have the track number in it
    else:
        s = pctl.master_library[index].filename

    # This splits the line by groups of numbers, causing the sorting algorithum to sort
    # by those numbers. Should work for filenames, even with the disc number in the name.
    try:
        return [tryint(c) for c in re.split('([0-9]+)', s)]
    except:
        return "a"


def sort_tracK_numbers_album_only(pl, custom_list=None):
    current_folder = ""
    albums = []
    if custom_list is None:
        playlist = pctl.multi_playlist[pl][2]
    else:
        playlist = custom_list

    for i in range(len(playlist)):
        if i == 0:
            albums.append(i)
            current_folder = pctl.master_library[playlist[i]].album
        else:
            if pctl.master_library[playlist[i]].album != current_folder:
                current_folder = pctl.master_library[playlist[i]].album
                albums.append(i)

    i = 0
    while i < len(albums) - 1:
        playlist[albums[i]:albums[i + 1]] = sorted(playlist[albums[i]:albums[i + 1]], key=index_key)
        i += 1
    if len(albums) > 0:
        playlist[albums[i]:] = sorted(playlist[albums[i]:], key=index_key)

    gui.pl_update += 1

def sort_track_2(pl, custom_list=None):
    current_folder = ""
    albums = []
    if custom_list is None:
        playlist = pctl.multi_playlist[pl][2]
    else:
        playlist = custom_list

    for i in range(len(playlist)):
        if i == 0:
            albums.append(i)
            current_folder = pctl.master_library[playlist[i]].parent_folder_name
        else:
            if pctl.master_library[playlist[i]].parent_folder_name != current_folder:
                current_folder = pctl.master_library[playlist[i]].parent_folder_name
                albums.append(i)

    i = 0
    while i < len(albums) - 1:
        playlist[albums[i]:albums[i + 1]] = sorted(playlist[albums[i]:albums[i + 1]], key=index_key)
        i += 1
    if len(albums) > 0:
        playlist[albums[i]:] = sorted(playlist[albums[i]:], key=index_key)

    gui.pl_update += 1

tauon.sort_track_2 = sort_track_2


def key_filepath(index):
    track = pctl.master_library[index]
    return track.parent_folder_path.lower(), track.filename

def key_filename(index):
    track = pctl.master_library[index]
    return track.filename

def sort_path_pl(pl, custom_list=None):

    if custom_list is not None:
        target = custom_list
    else:
        target = pctl.multi_playlist[pl][2]

    # if use_natsort and False:
    #     target[:] = natsort.os_sorted(target, key=key_fullpath)
    # else:
    target.sort(key=key_filepath)

def append_current_playing(index):

    if spot_ctl.coasting:
        spot_ctl.append_playing(index)
        gui.pl_update = 1
        return

    if pctl.playing_state > 0 and len(pctl.track_queue) > 0:
        pctl.multi_playlist[index][2].append(pctl.track_queue[pctl.queue_step])
        gui.pl_update = 1


def export_stats(pl):
    playlist_time = 0
    play_time = 0
    total_size = 0
    tracks_in_playlist = len(pctl.multi_playlist[pl][2])

    seen_files = {}
    seen_types = {}

    mp3_bitrates = {}
    ogg_bitrates = {}
    m4a_bitrates = {}

    are_cue = 0

    for index in pctl.multi_playlist[pl][2]:
        track = pctl.g(index)

        playlist_time += int(track.length)
        play_time += star_store.get(index)

        if track.is_cue:
            are_cue += 1

        if track.file_ext == "MP3":
            mp3_bitrates[track.bitrate] = mp3_bitrates.get(track.bitrate, 0) + 1
        if track.file_ext == "OGG" or track.file_ext == "OGA":
            ogg_bitrates[track.bitrate] = ogg_bitrates.get(track.bitrate, 0) + 1
        if track.file_ext == "M4A":
            m4a_bitrates[track.bitrate] = m4a_bitrates.get(track.bitrate, 0) + 1

        type = track.file_ext
        if type == "OGA":
            type = "OGG"
        seen_types[type] = seen_types.get(type, 0) + 1

        if track.fullpath and not track.is_network:
            if track.fullpath not in seen_files:
                size = track.size
                if not size and os.path.isfile(track.fullpath):
                    size = os.path.getsize(track.fullpath)
                seen_files[track.fullpath] = size


    total_size = sum(seen_files.values())

    stats_gen.update(pl)
    line = 'Playlist:\n' + pctl.multi_playlist[pl][0] + "\n\n"
    line += 'Generated:\n' + time.strftime("%c") + "\n\n"
    line += 'Tracks in playlist:\n' + str(tracks_in_playlist)
    line += "\n\n"
    line += "Repeats in playlist:\n"
    unique = len(set(pctl.multi_playlist[pl][2]))
    line += str(tracks_in_playlist - unique)
    line += "\n\n"
    line += 'Total local size:\n' + get_filesize_string(total_size) + "\n\n"
    line += 'Playlist duration:\n' + str(datetime.timedelta(seconds=int(playlist_time))) + "\n\n"
    line += 'Total playtime:\n' + str(datetime.timedelta(seconds=int(play_time))) + "\n\n"

    line += "Track types:\n"
    if tracks_in_playlist:
        types = sorted(seen_types, key=seen_types.get, reverse=True)
        for type in types:
            perc = round((seen_types.get(type) / tracks_in_playlist) * 100, 1)
            if perc < 0.1:
                perc = "<0.1"
            if type == "SPOT":
                type = "SPOTIFY"
            if type == "SUB":
                type = "AIRSONIC"
            line += f"{type} ({perc}%); "
    line = line.rstrip("; ")
    line += "\n\n"

    if tracks_in_playlist:
        line += "Percent of tracks are CUE type:\n"
        perc = are_cue / tracks_in_playlist
        if perc == 0:
            perc = 0
        if 0 < perc < 0.01:
            perc = "<0.01"
        else:
            perc = round(perc, 2)

        line += str(perc) + "%"
        line += "\n\n"

    if tracks_in_playlist and mp3_bitrates:
        line += "MP3 bitrates (kbps):\n"
        rates = sorted(mp3_bitrates, key=mp3_bitrates.get, reverse=True)
        others = 0
        for rate in rates:
            perc = round((mp3_bitrates.get(rate) / sum(mp3_bitrates.values())) * 100, 1)
            if perc < 1:
                others += perc
            else:
                line += f"{rate} ({perc}%); "

        if others:
            others = round(others, 1)
            if others < 0.1:
                others = "<0.1"
            line += f"Others ({others}%);"
        line = line.rstrip("; ")
        line += "\n\n"

    if tracks_in_playlist and ogg_bitrates:
        line += "OGG bitrates (kbps):\n"
        rates = sorted(ogg_bitrates, key=ogg_bitrates.get, reverse=True)
        others = 0
        for rate in rates:
            perc = round((ogg_bitrates.get(rate) / sum(ogg_bitrates.values())) * 100, 1)
            if perc < 1:
                others += perc
            else:
                line += f"{rate} ({perc}%); "

        if others:
            others = round(others, 1)
            if others < 0.1:
                others = "<0.1"
            line += f"Others ({others}%);"
        line = line.rstrip("; ")
        line += "\n\n"

    # if tracks_in_playlist and m4a_bitrates:
    #     line += "M4A bitrates (kbps):\n"
    #     rates = sorted(m4a_bitrates, key=m4a_bitrates.get, reverse=True)
    #     others = 0
    #     for rate in rates:
    #         perc = round((m4a_bitrates.get(rate) / sum(m4a_bitrates.values())) * 100, 1)
    #         if perc < 1:
    #             others += perc
    #         else:
    #             line += f"{rate} ({perc}%); "
    #
    #     if others:
    #         others = round(others, 1)
    #         if others < 0.1:
    #             others = "<0.1"
    #         line += f"Others ({others}%);"
    #
    #     line = line.rstrip("; ")
    #     line += "\n\n"

    line += "\n-------------- Top Artists --------------------\n\n"

    ls = stats_gen.artist_list
    for i, item in enumerate(ls[:50]):
        line += str(i + 1) + ".\t" + stt2(item[1]) + "\t" + item[0] + "\n"

    line += "\n\n-------------- Top Albums --------------------\n\n"
    ls = stats_gen.album_list
    for i, item in enumerate(ls[:50]):
        line += str(i + 1) + ".\t" + stt2(item[1]) + "\t" + item[0] + "\n"
    line += "\n\n-------------- Top Genres --------------------\n\n"
    ls = stats_gen.genre_list
    for i, item in enumerate(ls[:50]):
        line += str(i + 1) + ".\t" + stt2(item[1]) + "\t" + item[0] + "\n"

    line = line.encode('utf-8')
    xport = open(user_directory + '/stats.txt', 'wb')
    xport.write(line)
    xport.close()
    target = os.path.join(user_directory, "stats.txt")
    if system == "windows" or msys:
        os.startfile(target)
    elif macos:
        subprocess.call(['open', target])
    else:
        subprocess.call(["xdg-open", target])


def imported_sort(pl):

    if pl_is_locked(pl):
        show_message(_("Playlist is locked"))
        return

    pctl.multi_playlist[pl][2].sort(key= lambda x: pctl.g(x).index)

    reload_albums()
    tree_view_box.clear_target_pl(pl)


def standard_sort(pl):

    if pl_is_locked(pl):
        show_message(_("Playlist is locked"))
        return

    sort_path_pl(pl)
    sort_track_2(pl)
    reload_albums()
    tree_view_box.clear_target_pl(pl)


def year_s(plt):

    sorted_temp = sorted(plt, key=lambda x: x[1])
    temp = []

    for album in sorted_temp:
        temp += album[0]
    return temp


def year_sort(pl, custom_list=None):
    if custom_list:
        playlist = custom_list
    else:
        playlist = pctl.multi_playlist[pl][2]
    plt = []
    pl2 = []
    artist = ""
    album_artist = ""

    p = 0
    while p < len(playlist):

        track = get_object(playlist[p])

        if track.artist != artist:
           if album_artist and track.album_artist and album_artist == track.album_artist:
               pass
           elif len(artist) > 5 and artist.lower() in track.parent_folder_name.lower():
               pass
           else:
                artist = track.artist
                pl2 += year_s(plt)
                plt = []


        if track.album_artist:
            album_artist = track.album_artist

        if p > len(playlist) - 1:
            break

        album = []
        on = get_object(playlist[p]).parent_folder_path
        album.append(playlist[p])
        t = 1

        while t + p < len(playlist) - 1 and get_object(playlist[p + t]).parent_folder_path == on:
                album.append(playlist[p + t])
                t += 1

        date = get_object(playlist[p]).date

        # If date is xx-xx-yyyy format, just grab the year from the end
        # so that the M and D don't interfere with the sorter
        if len(date) > 4 and date[-4:].isnumeric():
            date = date[-4:]

        # If we don't have a date, see if we can grab one from the folder name
        # following the format: (XXXX)
        if date == "":
            pfn = get_object(playlist[p]).parent_folder_name
            if len(pfn) > 6 and pfn[-1] == ")" and pfn[-6] == "(":
                date = pfn[-5:-1]

        plt.append((album, date, artist + " " + get_object(playlist[p]).album))
        p += len(album)
        # print(album)

    if plt:
        pl2 += year_s(plt)
        plt = []

    if custom_list is not None:
        return pl2

    # We can't just assign the playlist because it may disconnect the 'pointer' default_playlist
    pctl.multi_playlist[pl][2][:] = pl2[:]
    reload_albums()
    tree_view_box.clear_target_pl(pl)


def pl_toggle_playlist_break(ref):
    pctl.multi_playlist[ref][4] ^= 1
    gui.pl_update = 1


delete_icon.xoff = 3
delete_icon.colour = [249, 70, 70, 255]

tab_menu.add(_('Delete'), delete_playlist_force, pass_ref=True, hint="Ctrl+W", icon=delete_icon, disable_test=test_pl_tab_locked, pass_ref_deco=True)
radio_tab_menu.add(_('Delete'), delete_playlist_force, pass_ref=True, hint="Ctrl+W", icon=delete_icon, disable_test=test_pl_tab_locked, pass_ref_deco=True)

def gen_unique_pl_title(base, extra="", start=1):

    ex = start
    title = base
    while ex < 100:
        for playlist in pctl.multi_playlist:
            if playlist[0] == title:
                ex += 1
                if ex == 1:
                    title = base + " (" + extra.rstrip(" ") + ")"
                else:
                    title = base + " (" + extra + str(ex) + ")"
                break
        else:
            break

    return title


def new_playlist(switch=True):

    if gui.radio_view:
        r = {}
        r["uid"] = uid_gen()
        r["name"] = "New Radio List"
        r["items"] = [] #copy.copy(prefs.radio_urls)
        r["scroll"] = 0
        pctl.radio_playlists.append(r)
        return

    title = gen_unique_pl_title("New Playlist")

    top_panel.prime_side = 1
    top_panel.prime_tab = len(pctl.multi_playlist)

    pctl.multi_playlist.append(pl_gen(title=title))  # [title, 0, [], 0, 0, 0])
    if switch:
        switch_playlist(len(pctl.multi_playlist) - 1)
    return len(pctl.multi_playlist) - 1

heartx_icon = MenuIcon(asset_loader('heart-menu.png', True))
spot_heartx_icon = MenuIcon(asset_loader('heart-menu.png', True))
transcode_icon = MenuIcon(asset_loader('transcode.png', True))
mod_folder_icon = MenuIcon(asset_loader('mod_folder.png', True))
settings_icon = MenuIcon(asset_loader('settings2.png', True))
rename_tracks_icon = MenuIcon(asset_loader('pen.png', True))
add_icon = MenuIcon(asset_loader('new.png', True))
spot_asset = asset_loader('spot.png', True)
spot_icon = MenuIcon(spot_asset)
spot_icon.colour = [30, 215, 96, 255]
spot_icon.xoff = 5
spot_icon.yoff = 2

tab_menu.br()

def append_deco():

    if pctl.playing_state > 0:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    text = None
    if spot_ctl.coasting:
        text = _("Add Spotify Album")

    return [line_colour, colours.menu_background, text]


def rescan_deco(pl):

    if pctl.multi_playlist[pl][7]:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    #base = os.path.basename(pctl.multi_playlist[pl][7])

    return [line_colour, colours.menu_background, None]

def regenerate_deco(pl):

    id = pl_to_id(pl)
    value = pctl.gen_codes.get(id)

    if value:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]

column_names = (
    "Artist",
    "Album Artist",
    "Album",
    "Title",
    "Composer",
    "Time",
    "Date",
    "Genre",
    "#",
    "P",
    "Starline",
    "Rating",
    "Comment",
    "Codec",
    "Lyrics",
    "Bitrate",
    "S",
    "Filename"
)


def parse_generator(string):
    cmds = []
    quotes = []
    current = ""
    q_string = ""
    inquote = False
    for cha in string:
        if not inquote and cha == " ":
            if current:
                cmds.append(current)
                quotes.append(q_string)
            q_string = ""
            current = ""
            continue
        elif cha == "\"":
            inquote ^= True

        current += cha

        if inquote and cha != "\"":
            q_string += cha

    if current:
        cmds.append(current)
        quotes.append(q_string)

    return cmds, quotes, inquote


def upload_spotify_playlist(pl):

    p_id = pl_to_id(pl)
    string = pctl.gen_codes.get(p_id)
    id = None
    if string:
        cmds, quotes, inquote = parse_generator(string)
        for i, cm in enumerate(cmds):
            if cm.startswith("spl\""):
                id = quotes[i]
                break

    urls = []
    playlist = pctl.multi_playlist[pl][2]

    warn = False
    for track_id in playlist:
        tr = pctl.g(track_id)
        url = tr.misc.get("spotify-track-url")
        if not url:
            warn = True
            continue
        urls.append(url)

    if warn:
        show_message(_("Playlist contains non-Spotify tracks"), mode="error")
        return

    new = False
    if id is None:
        name = pctl.multi_playlist[pl][0].split(" by ")[0]
        show_message(_("Created new Spotify playlist"), name, mode="done")
        id = spot_ctl.create_playlist(name)
        if id:
            new = True
            pctl.gen_codes[p_id] = "spl\"" + id + "\""
    if id is None:
        show_message(_("Error creating Spotify playlist"))
        return
    if not new:
        show_message(_("Updated Spotify playlist"), mode="done")
    spot_ctl.upload_playlist(id, urls)


def regenerate_playlist(pl=-1, silent=False, id=None):

    if id is None and pl == -1:
        return

    if id is None:
        id = pl_to_id(pl)

    if pl == -1:
        pl = id_to_pl(id)
        if pl is None:
            return

    source_playlist = pctl.multi_playlist[pl][2]

    string = pctl.gen_codes.get(id)
    if not string:
        if not silent:
            show_message("This playlist has no generator")
        return

    cmds, quotes, inquote = parse_generator(string)

    if inquote:
        gui.gen_code_errors = "close"
        return

    playlist = []
    selections = []
    errors = False
    selections_searched = 0
    #
    # print(cmds)
    # print(quotes)

    pctl.regen_in_progress = True

    for i, cm in enumerate(cmds):

        quote = quotes[i]

        if cm.startswith("\"") and (cm.endswith(">") or cm.endswith("<")):
            cm_found = False

            for col in column_names:

                if quote.lower() == col.lower() or _(quote).lower() == col.lower():
                    cm_found = True

                    if cm[-1] == ">":
                        sort_ass(0, invert=False, custom_list=playlist, custom_name=col)
                    elif cm[-1] == "<":
                        sort_ass(0, invert=True, custom_list=playlist, custom_name=col)
                    break
            if cm_found:
                continue

        elif cm == "self":
            selections.append(pctl.multi_playlist[pl][2])

        elif cm == "auto":
            pass

        elif cm.startswith("spl\""):
            playlist.extend(spot_ctl.playlist(quote, return_list=True))

        elif cm == "sal":
            playlist.extend(spot_ctl.get_library_albums(return_list=True))

        elif cm == "slt":
            playlist.extend(spot_ctl.get_library_likes(return_list=True))

        elif cm == "plex":
            if not plex.scanning:
                playlist.extend(plex.get_albums(return_list=True))

        elif cm == "jelly":
            if not jellyfin.scanning:
                playlist.extend(jellyfin.ingest_library(return_list=True))

        elif cm == "koel":
            if not koel.scanning:
                playlist.extend(koel.get_albums(return_list=True))

        elif cm == "air":
            if not subsonic.scanning:
                playlist.extend(subsonic.get_music3(return_list=True))

        elif cm == "a":
            if not selections and not selections_searched:
                for plist in pctl.multi_playlist:
                    code = pctl.gen_codes.get(plist[6])
                    if code is None or code == "" or code.startswith("self"):
                        selections.append(plist[2])

            temp = []
            for selection in selections:
                temp += selection

            playlist += list(OrderedDict.fromkeys(temp))
            selections.clear()

        elif cm == "cue":

            for i in reversed(range(len(playlist))):
                    tr = pctl.g(playlist[i])
                    if not tr.is_cue:
                        del playlist[i]
            playlist = list(OrderedDict.fromkeys(playlist))

        elif cm == "today":
            d = datetime.date.today()
            for i in reversed(range(len(playlist))):
                    tr = pctl.g(playlist[i])
                    if not tr.date[5:7] == f"{d:%m}" or not tr.date[8:10] == f"{d:%d}":
                        del playlist[i]
            playlist = list(OrderedDict.fromkeys(playlist))

        elif cm.startswith("com\""):
            for i in reversed(range(len(playlist))):
                    tr = pctl.g(playlist[i])
                    if quote not in tr.comment:
                        del playlist[i]
            playlist = list(OrderedDict.fromkeys(playlist))

        elif cm.startswith("ext"):
            value = quote.upper()
            if value:
                if not selections:
                    for plist in pctl.multi_playlist:
                        selections.append(plist[2])

                temp = []
                for selection in selections:
                    for track in selection:
                        tr = pctl.g(track)
                        if tr.file_ext == value:
                            temp.append(track)

                playlist += list(OrderedDict.fromkeys(temp))

        elif cm == "ypa":
            playlist = year_sort(0, playlist)

        elif cm == "tn":
            sort_track_2(0, playlist)

        elif cm == "ia>":
            playlist = gen_last_imported_folders(0, playlist)

        elif cm == "ia<":
            playlist = gen_last_imported_folders(0, playlist, reverse=True)

        elif cm == "m>":
            playlist = gen_last_modified(0, playlist)

        elif cm == "m<":
            playlist = gen_last_modified(0, playlist, reverse=False)

        elif cm == "ly" or cm == "lyrics":
            playlist = gen_lyrics(0, playlist)

        elif cm == "l" or cm == "love" or cm == "loved":
            playlist = gen_love(0, playlist)

        elif cm == "clr":
            selections.clear()

        elif cm == "rv" or cm == "reverse":
            playlist = gen_reverse(0, playlist)

        elif cm == "rva":
            playlist = gen_folder_reverse(0, playlist)

        elif cm == "rata>":

            playlist = gen_folder_top_rating(0, custom_list=playlist)

        elif cm == "rat>":

            def rat_key(track_id):
                return star_store.get_rating(track_id)

            playlist = sorted(playlist, key=rat_key, reverse=True)

        elif cm == "rat<":

            def rat_key(track_id):
                return star_store.get_rating(track_id)
                # tr = pctl.g(track_id)
                # if "FMPS_Rating" in tr.misc:
                #     return tr.misc["FMPS_Rating"]
                # else:
                #     return 0

            playlist = sorted(playlist, key=rat_key)

        elif cm[:4] == "rat=":
            value = cm[4:]
            try:
                value = float(value) * 2
                temp = []
                for item in playlist:
                    if value == star_store.get_rating(item):
                        temp.append(item)
                    # tr = pctl.g(item)
                    # if "FMPS_Rating" in tr.misc:
                    #     if value == tr.misc["FMPS_Rating"]:
                    #         temp.append(item)
                playlist = temp
            except:
                errors = True
                pass
                #raise

        elif cm[:4] == "rat<":
            value = cm[4:]
            try:
                value = float(value) * 2
                temp = []
                for item in playlist:
                    if value > star_store.get_rating(item):
                        temp.append(item)
                    # tr = pctl.g(item)
                    # if "FMPS_Rating" in tr.misc:
                    #     if value > tr.misc["FMPS_Rating"]:
                    #         temp.append(item)
                playlist = temp
            except:
                errors = True
                pass

        elif cm[:4] == "rat>":
            value = cm[4:]
            try:
                value = float(value) * 2
                temp = []
                for item in playlist:
                    if value < star_store.get_rating(item):
                        temp.append(item)
                    # tr = pctl.g(item)
                    # if "FMPS_Rating" in tr.misc:
                    #     if value < tr.misc["FMPS_Rating"]:
                    #         temp.append(item)
                playlist = temp
            except:
                errors = True
                pass

        elif cm == "rat":
            temp = []
            for item in playlist:
                #tr = pctl.g(item)
                if star_store.get_rating(item) > 0:
                    temp.append(item)
                # if "FMPS_Rating" in tr.misc:
                #     temp.append(item)
            playlist = temp

        elif cm == "norat":
            temp = []
            for item in playlist:
                if star_store.get_rating(item) == 0:
                    temp.append(item)
                # tr = pctl.g(item)
                # if "FMPS_Rating" not in tr.misc:
                #     temp.append(item)
            playlist = temp

        elif cm == "d>":
            playlist = gen_sort_len(0, custom_list=playlist)

        elif cm == "d<":
            playlist = gen_sort_len(0, custom_list=playlist)
            playlist = list(reversed(playlist))

        elif cm[:2] == "d<":
            value = cm[2:]
            if value and value.isdigit():
                value = int(value)
                for i in reversed(range(len(playlist))):
                    tr = pctl.g(playlist[i])
                    if not value > tr.length:
                        del playlist[i]

        elif cm[:2] == "d>":
            value = cm[2:]
            if value and value.isdigit():
                value = int(value)
                for i in reversed(range(len(playlist))):
                    tr = pctl.g(playlist[i])
                    if not value < tr.length:
                        del playlist[i]

        elif cm == "path":
            sort_path_pl(0, custom_list=playlist)

        elif cm == "pa>":
            playlist = gen_folder_top(0, custom_list=playlist)

        elif cm == "pa<":
            playlist = gen_folder_top(0, custom_list=playlist)
            playlist = gen_folder_reverse(0, playlist)

        elif cm == "pt>" or cm == "pc>":
            playlist = gen_top_100(0, custom_list=playlist)

        elif cm == "pt<" or cm == "pc<":
            playlist = gen_top_100(0, custom_list=playlist)
            playlist = list(reversed(playlist))

        elif cm[:3] == "pt>":
            value = cm[3:]
            if value and value.isdigit():
                value = int(value)
                for i in reversed(range(len(playlist))):
                    t_time = star_store.get(playlist[i])
                    if t_time < value:
                        del playlist[i]

        elif cm[:3] == "pt<":
            value = cm[3:]
            if value and value.isdigit():
                value = int(value)
                for i in reversed(range(len(playlist))):
                    t_time = star_store.get(playlist[i])
                    if t_time > value:
                        del playlist[i]

        elif cm[:3] == "pc>":
            value = cm[3:]
            if value and value.isdigit():
                value = int(value)
                for i in reversed(range(len(playlist))):
                    t_time = star_store.get(playlist[i])
                    tr = pctl.g(playlist[i])
                    if tr.length > 0:
                        if not value < t_time / tr.length:
                            del playlist[i]

        elif cm[:3] == "pc<":
            value = cm[3:]
            if value and value.isdigit():
                value = int(value)
                for i in reversed(range(len(playlist))):
                    t_time = star_store.get(playlist[i])
                    tr = pctl.g(playlist[i])
                    if tr.length > 0:
                        if not value > t_time / tr.length:
                            del playlist[i]

        elif cm == "y<":
            playlist = gen_sort_date(0, False, playlist)

        elif cm == "y>":
            playlist = gen_sort_date(0, True, playlist)

        elif cm[:2] == "y=":
            value = cm[2:]
            if value:
                temp = []
                for item in playlist:
                    if value in pctl.master_library[item].date:
                        temp.append(item)
                playlist = temp

        elif cm[:3] == "y>=":
            value = cm[3:]
            if value and value.isdigit():
                value = int(value)
                temp = []
                for item in playlist:
                    if pctl.master_library[item].date[:4].isdigit() and int(pctl.master_library[item].date[:4]) >= value:
                        temp.append(item)
                playlist = temp

        elif cm[:3] == "y<=":
            value = cm[3:]
            if value and value.isdigit():
                value = int(value)
                temp = []
                for item in playlist:
                    if pctl.master_library[item].date[:4].isdigit() and int(pctl.master_library[item].date[:4]) <= value:
                        temp.append(item)
                playlist = temp

        elif cm[:2] == "y>":
            value = cm[2:]
            if value and value.isdigit():
                value = int(value)
                temp = []
                for item in playlist:
                    if pctl.master_library[item].date[:4].isdigit() and int(pctl.master_library[item].date[:4]) > value:
                        temp.append(item)
                playlist = temp

        elif cm[:2] == "y<":
            value = cm[2:]
            if value and value.isdigit:
                value = int(value)
                temp = []
                for item in playlist:
                    if pctl.master_library[item].date[:4].isdigit() and int(pctl.master_library[item].date[:4]) < value:
                        temp.append(item)
                playlist = temp

        elif cm == "st" or cm == "rt" or cm == "r":
            random.shuffle(playlist)

        elif cm == "sf" or cm == "rf" or cm == "ra" or cm == "sa":
            playlist = gen_folder_shuffle(0, custom_list=playlist)

        elif cm.startswith("n"):
            value = cm[1:]
            if value.isdigit():
                playlist = playlist[:int(value)]

        # SEARCH FOLDER
        elif cm.startswith("p\"") and len(cm) > 3:

            if not selections:
                for plist in pctl.multi_playlist:
                    code = pctl.gen_codes.get(plist[6])
                    if code is None or code == "" or code.startswith("self"):
                        selections.append(plist[2])

            search = quote
            search_over.all_folders = True
            search_over.sip = True
            search_over.search_text.text = search
            try:
                worker2_lock.release()
            except:
                pass
            while search_over.sip:
                time.sleep(0.01)

            found_name = ""

            for result in search_over.results:
                if result[0] == 5:
                    found_name = result[1]
                    break
            else:
                print("No folder search result found")
                continue

            search_over.clear()

            playlist += search_over.click_meta(found_name, get_list=True, search_lists=selections)

        # SEARCH GENRE
        elif (cm.startswith("g\"") or cm.startswith("gm\"") or cm.startswith("g=\"")) and len(cm) > 3:

            if not selections:
                for plist in pctl.multi_playlist:
                    code = pctl.gen_codes.get(plist[6])
                    if code is None or code == "" or code.startswith("self"):
                        selections.append(plist[2])

            g_search = quote.lower().replace("-", "") #.replace(" ", "")

            search = g_search
            search_over.sip = True
            search_over.search_text.text = search
            try:
                worker2_lock.release()
            except:
                pass
            while search_over.sip:
                time.sleep(0.01)

            found_name = ""

            if cm.startswith("g=\""):
                for result in search_over.results:
                    if result[0] == 3 and result[1].lower().replace("-", "").replace(" ", "") == g_search:  #
                        found_name = result[1]
                        break
            elif cm.startswith("g\"") or not prefs.sep_genre_multi:
                for result in search_over.results:
                    if result[0] == 3:
                        found_name = result[1]
                        break
            elif cm.startswith("gm\""):
                for result in search_over.results:
                    if result[0] == 3 and result[1].endswith("+"):  #
                        found_name = result[1]
                        break


            if not found_name:
                print("No genre search result found")
                continue

            search_over.clear()

            playlist += search_over.click_genre(found_name, get_list=True, search_lists=selections)

        # SEARCH ARTIST
        elif cm.startswith("a\"") and len(cm) > 3 and cm != "auto":

            if not selections:
                for plist in pctl.multi_playlist:
                    code = pctl.gen_codes.get(plist[6])
                    if code is None or code == "" or code.startswith("self"):
                        selections.append(plist[2])

            search = quote
            search_over.sip = True
            search_over.search_text.text = "artist " + search
            try:
                worker2_lock.release()
            except:
                pass
            while search_over.sip:
                time.sleep(0.01)

            found_name = ""

            for result in search_over.results:
                if result[0] == 0:
                    found_name = result[1]
                    break
            else:
                print("No artist search result found")
                continue

            search_over.clear()
            # for item in search_over.click_artist(found_name, get_list=True, search_lists=selections):
            #     playlist.append(item)
            playlist += search_over.click_artist(found_name, get_list=True, search_lists=selections)

        elif cm.startswith("ff\""):

            for i in reversed(range(len(playlist))):
                tr = pctl.g(playlist[i])
                line = " ".join([tr.title, tr.artist, tr.album, tr.fullpath, tr.composer, tr.comment])
                if not search_magic(quote.lower(), line.lower()):
                    del playlist[i]
            playlist = list(OrderedDict.fromkeys(playlist))

        elif cm.startswith("fx\""):

            for i in reversed(range(len(playlist))):
                tr = pctl.g(playlist[i])
                line = " ".join([tr.title, tr.artist, tr.album, tr.fullpath, tr.composer, tr.comment, tr.album_artist]).lower()
                if prefs.diacritic_search and all([ord(c) < 128 for c in quote]):
                    line = str(unidecode(line))

                if search_magic(quote.lower(), line):
                    del playlist[i]


        elif cm.startswith("find\"") or cm.startswith("f\"") or cm.startswith("fs\""):

            if not selections:
                for plist in pctl.multi_playlist:
                    code = pctl.gen_codes.get(plist[6])
                    if code is None or code == "" or code.startswith("self"):
                        selections.append(plist[2])

            cooldown = 0
            dones = {}
            for selection in selections:
                for track_id in selection:
                    if track_id not in dones:
                        tr = pctl.g(track_id)

                        if cm.startswith("fs\""):
                            line = "|".join([tr.title, tr.artist, tr.album, tr.fullpath, tr.composer, tr.comment,
                                             tr.album_artist]).lower()
                            if quote.lower() in line:
                                playlist.append(track_id)

                        else:
                            line = " ".join([tr.title, tr.artist, tr.album, tr.fullpath, tr.composer, tr.comment, tr.album_artist]).lower()

                            # if prefs.diacritic_search and all([ord(c) < 128 for c in quote]):
                            #     line = str(unidecode(line))

                            if search_magic(quote.lower(), line):
                                playlist.append(track_id)

                        cooldown += 1
                        if cooldown > 300:
                            time.sleep(0.005)
                            cooldown = 0

                        dones[track_id] = None

            playlist = list(OrderedDict.fromkeys(playlist))

        elif cm.startswith("s\""):
            pl_name = quote
            target = None
            for p in pctl.multi_playlist:
                if p[0].lower() == pl_name.lower():
                    target = p[2]
                    break
            else:
                for p in pctl.multi_playlist:
                    #print(p[0].lower())
                    #print(pl_name.lower())
                    if p[0].lower().startswith(pl_name.lower()):
                        target = p[2]
                        break
            if target is None:
                print(f"not found: {pl_name}")
                print("Target playlist not found")
                selections_searched += 1
                errors = "playlist"
                continue
            selections_searched += 1
            selections.append(target)
            #print(selections)

        else:
            errors = True

    gui.gen_code_errors = errors
    if not playlist and not errors:
        gui.gen_code_errors = "empty"

    if gui.rename_playlist_box and (not playlist or cmds.count("a") > 1):
        pass
    else:
        source_playlist[:] = playlist[:]

    tree_view_box.clear_target_pl(0, id)
    pctl.regen_in_progress = False
    gui.pl_update = 1
    reload()
    pctl.notify_change()

    # print(cmds)




def make_auto_sorting(pl):
    pctl.gen_codes[pl_to_id(pl)] = "self a path tn ypa auto"
    show_message(_("OK. This playlist will automatically sort on import from now on"),
                 _("You remove or edit this behavior by going \"Misc...\" > \"Edit generator...\""), mode="done")

extra_tab_menu = Menu(155, show_icons=True)

extra_tab_menu.add(_("New Playlist"), new_playlist, icon=add_icon)

def spotify_show_test(_):
    return prefs.spot_mode

tab_menu.add(_("Upload"), upload_spotify_playlist, pass_ref=True, pass_ref_deco=True, icon=spot_icon, show_test=spotify_show_test)


def regen_playlist_async(pl):
    if pctl.regen_in_progress:
        show_message("A regen is already in progress...")
        return
    shoot_dl = threading.Thread(target=regenerate_playlist, args=([pl]))
    shoot_dl.daemon = True
    shoot_dl.start()


tab_menu.add(_("Regenerate"), regen_playlist_async, regenerate_deco, pass_ref=True, pass_ref_deco=True, hint="Alt+R")
tab_menu.add_sub(_("Generate…"), 150)
tab_menu.add_sub(_("Sort…"), 170)
extra_tab_menu.add_sub(_("From Current…"), 133)
# tab_menu.add(_("Sort by Filepath"), standard_sort, pass_ref=True, disable_test=test_pl_tab_locked, pass_ref_deco=True)
# tab_menu.add(_("Sort Track Numbers"), sort_track_2, pass_ref=True)
# tab_menu.add(_("Sort Year per Artist"), year_sort, pass_ref=True)

tab_menu.add_to_sub(_("Sort by Imported"), 1, imported_sort, pass_ref=True)
tab_menu.add_to_sub(_("Sort by Filepath"), 1, standard_sort, pass_ref=True)
tab_menu.add_to_sub(_('Sort Track Numbers'), 1, sort_track_2, pass_ref=True)
tab_menu.add_to_sub(_('Sort Year per Artist'), 1, year_sort, pass_ref=True)
tab_menu.add_to_sub(_('Make Playlist Auto-Sorting'), 1, make_auto_sorting, pass_ref=True)

tab_menu.br()

tab_menu.add(_('Rescan Folder'), re_import2, rescan_deco, pass_ref=True, pass_ref_deco=True)

tab_menu.add(_('Paste'), s_append, paste_deco, pass_ref=True)
tab_menu.add(_("Append Playing"), append_current_playing, append_deco, pass_ref=True)
tab_menu.br()

# tab_menu.add("Sort By Filepath", sort_path_pl, pass_ref=True)

tab_menu.add(_("Export…"), export_playlist_box.activate, pass_ref=True)


tab_menu.add_sub(_("Misc…"), 175)


def forget_pl_import_folder(pl):

    pctl.multi_playlist[pl][7] = []

def remove_duplicates(pl):

    playlist = []

    for item in pctl.multi_playlist[pl][2]:
        if item not in playlist:
            playlist.append(item)

    removed = len(pctl.multi_playlist[pl][2]) - len(playlist)
    if not removed:
        show_message(f"No duplicates were found")
    else:
        show_message(f"{removed} duplicates removed", mode="done")

    pctl.multi_playlist[pl][2][:] = playlist[:]

def start_quick_add(pl):
    pctl.quick_add_target = pl_to_id(pl)
    show_message("You can now add/remove albums to this playlist by right clicking in gallery of any playlist",
                 "To exit this mode, click \"Disengage\" from main MENU")

def auto_get_sync_targets():

    search_paths = ["/run/user/*/gvfs/*/*/[Mm]usic",
                    "/run/media/*/*/[Mm]usic",]
    result_paths = []
    for item in search_paths:
        result_paths.extend(glob.glob(item))
    return result_paths

def auto_sync_thread(pl):

    if prefs.transcode_inplace:
        show_message("Cannot sync when in transcode inplace mode")
        return

    # Find target path
    gui.sync_progress = "Starting Sync..."
    gui.update += 1

    path = sync_target.text.strip().rstrip("/").rstrip("\\").replace("\n", "").replace("\r", "")
    if not path:
        show_message(_("No target folder selected"))
        gui.sync_progress = ""
        gui.stop_sync = False
        gui.update += 1
        return
    if not os.path.isdir(path):
        show_message(_("Target folder could not be found"))
        gui.sync_progress = ""
        gui.stop_sync = False
        gui.update += 1
        return

    prefs.sync_target = path

    # Get list of folder names on device
    console.print("Getting folder list from device...")
    d_folder_names = os.listdir(path)
    console.print("Got list")

    # Get list of folders we want
    folders = convert_playlist(pl, get_list=True)
    folder_names = []
    folder_dict = {}

    if gui.stop_sync:
        gui.sync_progress = ""
        gui.stop_sync = False
        gui.update += 1

    # Find the folder names the transcode function would name them
    for folder in folders:
        name = encode_folder_name(pctl.g(folder[0]))
        for item in folder:
            if pctl.g(item).album != pctl.g(folder[0]).album:
                name = os.path.basename(pctl.g(folder[0]).parent_folder_path)
                break
        folder_names.append(name)
        folder_dict[name] = folder

    # ------
    # Find deletes
    if prefs.sync_deletes:
        for d_folder in d_folder_names:
            if gui.stop_sync:
                break
            if d_folder not in folder_names:
                gui.sync_progress = _("Deleting folders...")
                gui.update += 1
                console.print(f"DELETING: {d_folder}")
                shutil.rmtree(os.path.join(path, d_folder))

    # -------
    # Find todos
    todos = []
    for folder in folder_names:
        if folder not in d_folder_names:
            todos.append(folder)
            console.print(f"Want to add: {folder}")
        else:
            console.print(f"Already exists: {folder}")

    gui.update += 1
    # -----
    # Prepare and copy
    for i, item in enumerate(todos):
        gui.sync_progress = _("Copying files to device")
        if gui.stop_sync:
            break

        free_space = shutil.disk_usage(path)[2] / 8 / 100000000  # in GB
        if free_space < 0.6:
            show_message(_("Sync aborted! Low disk space on target device"), mode="warning")
            break

        if prefs.bypass_transcode or (prefs.smart_bypass and 0 < pctl.g(folder_dict[item][0]).bitrate <= 128):
            print("Smart bypass...")

            source_parent = pctl.g(folder_dict[item][0]).parent_folder_path
            if os.path.exists(source_parent):
                if os.path.exists(os.path.join(path, item)):
                    show_message(_("Sync warning"), _("One or more folders to sync has the same name. Skipping."),
                                 mode="warning")
                    continue

                os.mkdir(os.path.join(path, item))
                encode_done = source_parent
            else:
                show_message("One or more folders is missing")
                continue

        else:

            encode_done = os.path.join(prefs.encoder_output, item)
            if not os.path.exists(encode_done):
                console.print("Need to transcode")
                remain = len(todos) - i
                if remain > 1:
                    gui.sync_progress = str(remain) + " " + _("Folders Remaining")
                else:
                    gui.sync_progress = str(remain) + " " + _("Folder Remaining")
                transcode_list.append(folder_dict[item])
                tm.ready("worker")
                while transcode_list:
                    time.sleep(1)
                if gui.stop_sync:
                    break
            else:
                console.print("A transcode is already done")

            if os.path.exists(encode_done):

                if os.path.exists(os.path.join(path, item)):
                    show_message(_("Sync warning"), _("One or more folders to sync has the same name. Skipping."), mode="warning")
                    continue

                os.mkdir(os.path.join(path, item))

        for file in os.listdir(encode_done):

            console.print("Copy file...")
            #gui.sync_progress += "."
            gui.update += 1

            if os.path.isfile(os.path.join(encode_done, file)):
                size = os.path.getsize(os.path.join(encode_done, file))
                sync_file_timer.set()
                shutil.copyfile(os.path.join(encode_done, file), os.path.join(os.path.join(path, item), file))
            if gui.sync_speed == 0 or sync_file_update_timer.get() > 1 and not file.endswith(".jpg"):
                sync_file_update_timer.set()
                gui.sync_speed = size / sync_file_timer.get()
                gui.sync_progress = _("Copying files to device") + " @ " + get_filesize_string_rounded(gui.sync_speed) + "/s"
                if gui.stop_sync:
                    gui.sync_progress = _("Aborting Sync") + " @ " + get_filesize_string_rounded(gui.sync_speed) + "/s"

        console.print("Finished copying folder")

    gui.sync_speed = 0
    gui.sync_progress = ""
    gui.stop_sync = False
    gui.update += 1
    show_message(_("Sync completed"), mode="done")


def auto_sync(pl):
    shoot_dl = threading.Thread(target=auto_sync_thread, args=([pl]))
    shoot_dl.daemon = True
    shoot_dl.start()

def set_sync_playlist(pl):
    id = pl_to_id(pl)
    if prefs.sync_playlist == id:
        prefs.sync_playlist = None
    else:
        prefs.sync_playlist = pl_to_id(pl)

def sync_playlist_deco(pl):
    text = _("Set as Sync Playlist")
    id = pl_to_id(pl)
    if id == prefs.sync_playlist:
        text = _("Un-set as Sync Playlist")
    return [colours.menu_text, colours.menu_background, text]

def set_download_playlist(pl):
    id = pl_to_id(pl)
    if prefs.download_playlist == id:
        prefs.download_playlist = None
    else:
        prefs.download_playlist = pl_to_id(pl)

def set_download_deco(pl):
    text = _("Set as Downloads Playlist")
    id = pl_to_id(pl)
    if id == prefs.download_playlist:
        text = _("Un-set as Downloads Playlist")
    return [colours.menu_text, colours.menu_background, text]



def csv_string(item):
    item = str(item)
    item.replace("\"", "\"\"")
    return f"\"{item}\""

def export_playlist_albums(pl):
    p = pctl.multi_playlist[pl]
    name = p[0]
    playlist = p[2]

    albums = []
    playtimes = {}
    last_folder = None
    for i, id in enumerate(playlist):
        track = pctl.g(id)
        if last_folder != track.parent_folder_path:
            last_folder = track.parent_folder_path
            if id not in albums:
                albums.append(id)

        playtimes[last_folder] = playtimes.get(last_folder, 0) + int(star_store.get(id))

    filename = f"{user_directory}/{name}.csv"
    xport = open(filename, 'w')

    xport.write("Album name;Artist;Release date;Genre;Rating;Playtime;Folder path")

    for id in albums:
        track = pctl.g(id)
        artist = track.album_artist
        if not artist:
            artist = track.artist

        xport.write("\n")
        xport.write(csv_string(track.album) + ",")
        xport.write(csv_string(artist) + ",")
        xport.write(csv_string(track.date) + ",")
        xport.write(csv_string(track.genre) + ",")
        xport.write(str(int(album_star_store.get_rating(track))))
        xport.write(",")
        xport.write(str(round(playtimes[track.parent_folder_path])))
        xport.write(",")
        xport.write(csv_string(track.parent_folder_path))

    xport.close()
    show_message("Export complete.", "Saved as: " + filename, mode='done')


tab_menu.add_to_sub(_("Export Playlist Stats"), 2, export_stats, pass_ref=True)
tab_menu.add_to_sub(_("Export Albums CSV"), 2, export_playlist_albums, pass_ref=True)
tab_menu.add_to_sub(_('Transcode All'), 2, convert_playlist, pass_ref=True)
tab_menu.add_to_sub(_('Rescan Tags'), 2, rescan_tags, pass_ref=True)
# tab_menu.add_to_sub(_('Forget Import Folder'), 2, forget_pl_import_folder, rescan_deco, pass_ref=True, pass_ref_deco=True)
# tab_menu.add_to_sub(_('Re-Import Last Folder'), 1, re_import, pass_ref=True)
#tab_menu.add_to_sub(_('Quick Export XSPF'), 2, export_xspf, pass_ref=True)
#tab_menu.add_to_sub(_('Quick Export M3U'), 2, export_m3u, pass_ref=True)
tab_menu.add_to_sub(_("Toggle Breaks"), 2, pl_toggle_playlist_break, pass_ref=True)
tab_menu.add_to_sub(_("Edit Generator..."), 2, edit_generator_box, pass_ref=True)
tab_menu.add_to_sub(_("Engage Gallery Quick Add"), 2, start_quick_add, pass_ref=True)
tab_menu.add_to_sub(_("Set as Sync Playlist"), 2, set_sync_playlist, sync_playlist_deco, pass_ref_deco=True, pass_ref=True)
tab_menu.add_to_sub(_("Set as Downloads Playlist"), 2, set_download_playlist, set_download_deco, pass_ref_deco=True, pass_ref=True)
tab_menu.add_to_sub(_("Remove Duplicates"), 2, remove_duplicates, pass_ref=True)

#tab_menu.add_to_sub("Empty Playlist", 0, new_playlist)

def best(index):
    #key = pctl.master_library[index].title + pctl.master_library[index].filename
    if pctl.master_library[index].length < 1:
        return 0
    return int(star_store.get(index))

def key_rating(index):
    return star_store.get_rating(index)

def key_scrobbles(index):
    return pctl.g(index).lfm_scrobbles

def key_playcount(index):
    #key = pctl.master_library[index].title + pctl.master_library[index].filename
    if pctl.master_library[index].length < 1:
        return 0
    return star_store.get(index) / pctl.master_library[index].length
    # if key in pctl.star_library:
    #     return pctl.star_library[key] / pctl.master_library[index].length
    # else:
    #     return 0

def add_pl_tag(text):
    return f" <{text}>"

def gen_top_rating(index, custom_list=None):

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[index][2]
    playlist = copy.deepcopy(source)
    playlist = sorted(playlist, key=key_rating, reverse=True)

    if custom_list is not None:
        return playlist

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Top Rated Tracks")),
                               playlist=copy.deepcopy(playlist),
                               hide_title=1))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a rat>"

def gen_top_100(index, custom_list=None):

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[index][2]
    playlist = copy.deepcopy(source)
    playlist = sorted(playlist, key=best, reverse=True)

    if custom_list is not None:
        return playlist

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Top Played Tracks")),
                               playlist=copy.deepcopy(playlist),
                               hide_title=1))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a pt>"


tab_menu.add_to_sub(_("Top Played Tracks"), 0, gen_top_100, pass_ref=True)
extra_tab_menu.add_to_sub(_("Top Played Tracks"), 0, gen_top_100, pass_ref=True)



def gen_folder_top(pl, get_sets=False, custom_list=None):

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[pl][2]

    if len(source) < 3:
        return []

    sets = []
    se = []
    tr = pctl.g(source[0])
    last = tr.parent_folder_path
    last_al = tr.album
    for track in source:
        if last != pctl.master_library[track].parent_folder_path or last_al != pctl.master_library[track].album:
            last = pctl.master_library[track].parent_folder_path
            last_al = pctl.master_library[track].album
            sets.append(copy.deepcopy(se))
            se = []
        se.append(track)
    sets.append(copy.deepcopy(se))

    def best(folder):
        #print(folder)
        total_star = 0
        for item in folder:
            # key = pctl.master_library[item].title + pctl.master_library[item].filename
            # if key in pctl.star_library:
            #     total_star += int(pctl.star_library[key])
            total_star += int(star_store.get(item))
        #print(total_star)
        return total_star

    if get_sets:
        r = []
        for item in sets:
            r.append((item, best(item)))
        return r

    sets = sorted(sets, key=best, reverse=True)


    playlist = []

    for se in sets:
        playlist += se

    # pctl.multi_playlist.append(
    #     [pctl.multi_playlist[pl][0] + " <Most Played Albums>", 0, copy.deepcopy(playlist), 0, 0, 0])
    if custom_list is not None:
        return playlist

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[pl][0] + add_pl_tag(_("Top Played Albums")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=0))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[pl][0] + "\" a pa>"


tab_menu.add_to_sub(_("Top Played Albums"), 0, gen_folder_top, pass_ref=True)
extra_tab_menu.add_to_sub(_("Top Played Albums"), 0, gen_folder_top, pass_ref=True)

tab_menu.add_to_sub(_("Top Rated Tracks"), 0, gen_top_rating, pass_ref=True)
extra_tab_menu.add_to_sub(_("Top Rated Tracks"), 0, gen_top_rating, pass_ref=True)

def gen_folder_top_rating(pl, get_sets=False, custom_list=None):

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[pl][2]

    if len(source) < 3:
        return []

    sets = []
    se = []
    tr = pctl.g(source[0])
    last = tr.parent_folder_path
    last_al = tr.album
    for track in source:
        if last != pctl.master_library[track].parent_folder_path or last_al != pctl.master_library[track].album:
            last = pctl.master_library[track].parent_folder_path
            last_al = pctl.master_library[track].album
            sets.append(copy.deepcopy(se))
            se = []
        se.append(track)
    sets.append(copy.deepcopy(se))

    def best(folder):
        return album_star_store.get_rating((pctl.g(folder[0])))

    if get_sets:
        r = []
        for item in sets:
            r.append((item, best(item)))
        return r

    sets = sorted(sets, key=best, reverse=True)

    playlist = []

    for se in sets:
        playlist += se

    if custom_list is not None:
        return playlist

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[pl][0] + add_pl_tag(_("Top Rated Albums")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=0))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[pl][0] + "\" a rata>"


def gen_lyrics(pl, custom_list=None):
    playlist = []

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[pl][2]

    for item in source:
        if pctl.master_library[item].lyrics != "":
            playlist.append(item)

    if custom_list is not None:
        return playlist

    if len(playlist) > 0:
        pctl.multi_playlist.append(pl_gen(title=_("Tracks with lyrics"),
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))

        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[pl][0] + "\" a ly"

    else:
        show_message(_("No tracks with lyrics were found."))

tab_menu.add_to_sub(_("Top Rated Albums"), 0, gen_folder_top_rating, pass_ref=True)
extra_tab_menu.add_to_sub(_("Top Rated Albums"), 0, gen_folder_top_rating, pass_ref=True)

def gen_incomplete(pl, custom_list=None):
    playlist = []

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[pl][2]

    albums = {}
    nums = {}
    for id in source:
        track = pctl.g(id)
        if track.album and track.track_number:

            if type(track.track_number) is str and not track.track_number.isdigit():
                continue

            if track.album not in albums:
                albums[track.album] = []
                nums[track.album] = []

            if track not in albums[track.album]:
                albums[track.album].append(track)
                nums[track.album].append(int(track.track_number))

    for album, tracks in albums.items():
        numbers = nums[album]
        if len(numbers) > 2:
            mi = min(numbers)
            mx = max(numbers)
            for track in tracks:
                if type(track.track_total) is int or (type(track.track_total) is str and track.track_total.isdigit()):
                    mx = max(mx, int(track.track_total))
            r = list(range(int(mi), int(mx)))
            for track in tracks:
                if int(track.track_number) in r:
                    r.remove(int(track.track_number))
            if r or mi > 1:
                for tr in tracks:
                    playlist.append(tr.index)

    if custom_list is not None:
        return playlist

    if len(playlist) > 0:
        show_message(_("Note this may include albums that simply have tracks missing an album tag"))
        pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[pl][0] + add_pl_tag(_("Incomplete Albums")),
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))

        # pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[pl][0] + "\" a ly"

    else:
        show_message(_("No incomplete albums were found."))



def gen_codec_pl(codec):

    playlist = []

    for pl in pctl.multi_playlist:
        for item in pl[2]:
            if pctl.master_library[item].file_ext == codec and item not in playlist:
                playlist.append(item)

    if len(playlist) > 0:
        pctl.multi_playlist.append(pl_gen(title=_("Codec") + ": " + codec,
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))



def gen_last_imported_folders(index, custom_list=None, reverse=True):
    source = custom_list
    if source is None:
        source = pctl.multi_playlist[index][2]

    a_cache = {}

    def key_import(index):

        track = pctl.master_library[index]
        cached = a_cache.get((track.album, track.parent_folder_name))
        if cached is not None:
            return cached

        if track.album:
            a_cache[(track.album, track.parent_folder_name)] = index
        return index

    playlist = copy.deepcopy(source)
    playlist = sorted(playlist, key=key_import, reverse=reverse)
    sort_track_2(0, playlist)

    if custom_list is not None:
        return playlist


def gen_last_modified(index, custom_list=None, reverse=True):

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[index][2]

    a_cache = {}

    def key_modified(index):

        track = pctl.master_library[index]
        cached = a_cache.get((track.album, track.parent_folder_name))
        if cached is not None:
            return cached

        if track.album:
            a_cache[(track.album, track.parent_folder_name)] =  pctl.master_library[index].modified_time
        return pctl.master_library[index].modified_time

    playlist = copy.deepcopy(source)
    playlist = sorted(playlist, key=key_modified, reverse=reverse)
    sort_track_2(0, playlist)

    if custom_list is not None:
        return playlist

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("File Modified")),
                               playlist=copy.deepcopy(playlist),
                               hide_title=0))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a m>"

tab_menu.add_to_sub(_("File Modified"), 0, gen_last_modified, pass_ref=True)
extra_tab_menu.add_to_sub(_("File Modified"), 0, gen_last_modified, pass_ref=True)

# tab_menu.add_to_sub(_("File Path"), 0, standard_sort, pass_ref=True)
# extra_tab_menu.add_to_sub(_("File Path"), 0, standard_sort, pass_ref=True)


def gen_love(pl, custom_list=None):
    playlist = []

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[pl][2]

    for item in source:
        if get_love_index(item):
            playlist.append(item)

    if custom_list is not None:
        return playlist

    if len(playlist) > 0:
        #pctl.multi_playlist.append(["Interesting Comments", 0, copy.deepcopy(playlist), 0, 0, 0])
        pctl.multi_playlist.append(pl_gen(title=_("Loved"),
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))
        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[pl][0] + "\" a l"
    else:
        show_message("No loved tracks were found.")

def gen_comment(pl):
    playlist = []

    for item in pctl.multi_playlist[pl][2]:
        cm = pctl.master_library[item].comment
        if len(cm) > 20 and \
                        cm[0] != "0" and \
                        'http://' not in cm and \
                        'www.' not in cm and \
                        'Release' not in cm and \
                        'EAC' not in cm and \
                        '@' not in cm and \
                        '.com' not in cm and \
                        'ipped' not in cm and \
                        'ncoded' not in cm and \
                        'ExactA' not in cm and \
                        'WWW.' not in cm and \
                        cm[2] != "+" and \
                        cm[1] != "+":
            playlist.append(item)

    if len(playlist) > 0:
        #pctl.multi_playlist.append(["Interesting Comments", 0, copy.deepcopy(playlist), 0, 0, 0])
        pctl.multi_playlist.append(pl_gen(title=_("Interesting Comments"),
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))
    else:
        show_message("Nothing of interest was found.")


def gen_replay(pl):
    playlist = []

    for item in pctl.multi_playlist[pl][2]:
        if pctl.master_library[item].misc.get("replaygain_track_gain"):
            playlist.append(item)

    if len(playlist) > 0:
        pctl.multi_playlist.append(pl_gen(title="ReplayGain Tracks",
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))
    else:
        show_message("No replay gain tags were found.")


def gen_sort_len(index, custom_list=None):

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[index][2]

    def length(index):

        if pctl.master_library[index].length < 1:
            return 0
        else:
            return int(pctl.master_library[index].length)

    playlist = copy.deepcopy(source)
    playlist = sorted(playlist, key=length, reverse=True)

    if custom_list is not None:
        return playlist

    # pctl.multi_playlist.append(
    #     [pctl.multi_playlist[index][0] + " <Duration Sorted>", 0, copy.deepcopy(playlist), 0, 1, 0])

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Duration Sorted")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=1))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a d>"

tab_menu.add_to_sub(_("Longest Tracks"), 0, gen_sort_len, pass_ref=True)
extra_tab_menu.add_to_sub(_("Longest Tracks"), 0, gen_sort_len, pass_ref=True)


def gen_folder_duration(pl, get_sets=False):
    if len(pctl.multi_playlist[pl][2]) < 3:
        return

    sets = []
    se = []
    last = pctl.master_library[pctl.multi_playlist[pl][2][0]].parent_folder_path
    last_al = pctl.master_library[pctl.multi_playlist[pl][2][0]].album
    for track in pctl.multi_playlist[pl][2]:
        if last != pctl.master_library[track].parent_folder_path or last_al != pctl.master_library[track].album:
            last = pctl.master_library[track].parent_folder_path
            last_al = pctl.master_library[track].album
            sets.append(copy.deepcopy(se))
            se = []
        se.append(track)
    sets.append(copy.deepcopy(se))

    def best(folder):
        total_duration = 0
        for item in folder:
            total_duration += pctl.master_library[item].length
        return total_duration

    if get_sets:
        r = []
        for item in sets:
            r.append((item, best(item)))
        return r

    sets = sorted(sets, key=best, reverse=True)
    playlist = []

    for se in sets:
        playlist += se

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[pl][0] + add_pl_tag(_("Longest Albums")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=0))


tab_menu.add_to_sub(_("Longest Albums"), 0, gen_folder_duration, pass_ref=True)
extra_tab_menu.add_to_sub(_("Longest Albums"), 0, gen_folder_duration, pass_ref=True)


def gen_sort_date(index, rev=False, custom_list=None):

    def g_date(index):

        if pctl.master_library[index].date != "":
            return str(pctl.master_library[index].date)
        else:
            return "z"

    playlist = []
    lowest = 0
    highest = 0
    first = True

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[index][2]

    for item in source:
        date = pctl.master_library[item].date
        if date != "":
            playlist.append(item)
            if len(date) > 4 and date[:4].isdigit():
                date = date[:4]
            if len(date) == 4 and date.isdigit():
                year = int(date)
                if first:
                    lowest = year
                    highest = year
                    first = False
                if year < lowest:
                    lowest = year
                if year > highest:
                    highest = year

    playlist = sorted(playlist, key=g_date, reverse=rev)

    if custom_list is not None:
        return playlist

    line = add_pl_tag(_("Year Sorted"))
    if lowest != highest and lowest != 0 and highest != 0:
        if rev:
            line = " <" + str(highest) + "-" + str(lowest) + ">"
        else:
            line = " <" + str(lowest) + "-" + str(highest) + ">"

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + line,
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=0))

    if rev:
        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a y>"
    else:
        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a y<"

tab_menu.add_to_sub(_("Year by Oldest"), 0, gen_sort_date, pass_ref=True)
extra_tab_menu.add_to_sub(_("Year by Oldest"), 0, gen_sort_date, pass_ref=True)


def gen_sort_date_new(index):
    gen_sort_date(index, True)


tab_menu.add_to_sub(_("Year by Latest"), 0, gen_sort_date_new, pass_ref=True)
extra_tab_menu.add_to_sub(_("Year by Latest"), 0, gen_sort_date_new, pass_ref=True)


# tab_menu.add_to_sub(_("Year by Artist"), 0, year_sort, pass_ref=True)
# extra_tab_menu.add_to_sub(_("Year by Artist"), 0, year_sort, pass_ref=True)

def gen_500_random(index):

    playlist = copy.deepcopy(pctl.multi_playlist[index][2])

    random.shuffle(playlist)

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Shuffled Tracks")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=1))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a st"

tab_menu.add_to_sub(_("Shuffled Tracks"), 0, gen_500_random, pass_ref=True)
extra_tab_menu.add_to_sub(_("Shuffled Tracks"), 0, gen_500_random, pass_ref=True)


def gen_folder_shuffle(index, custom_list=None):
    folders = []
    dick = {}

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[index][2]

    for track in source:
        parent = pctl.master_library[track].parent_folder_path
        if parent not in folders:
            folders.append(parent)
        if parent not in dick:
            dick[parent] = []
        dick[parent].append(track)

    random.shuffle(folders)
    playlist = []

    for folder in folders:
        playlist += dick[folder]

    if custom_list is not None:
        return playlist


    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Shuffled Albums")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=0))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a ra"

tab_menu.add_to_sub(_("Shuffled Albums"), 0, gen_folder_shuffle, pass_ref=True)
extra_tab_menu.add_to_sub(_("Shuffled Albums"), 0, gen_folder_shuffle, pass_ref=True)


def gen_best_random(index):

    playlist = []

    for p in pctl.multi_playlist[index][2]:
        time = star_store.get(p)

        if time > 300:
            playlist.append(p)

    random.shuffle(playlist)

    if len(playlist) > 0:

        pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Lucky Random")),
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=1))

        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a pt>300 rt"


tab_menu.add_to_sub(_("Lucky Random"), 0, gen_best_random, pass_ref=True)
extra_tab_menu.add_to_sub(_("Lucky Random"), 0, gen_best_random, pass_ref=True)


def gen_reverse(index, custom_list=None):

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[index][2]

    playlist = list(reversed(source))

    if custom_list is not None:
        return playlist

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Reversed")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=pctl.multi_playlist[index][4]))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a rv"

tab_menu.add_to_sub(_("Reverse Tracks"), 0, gen_reverse, pass_ref=True)
extra_tab_menu.add_to_sub(_("Reverse Tracks"), 0, gen_reverse, pass_ref=True)

def gen_folder_reverse(index, custom_list=None):

    source = custom_list
    if source is None:
        source = pctl.multi_playlist[index][2]

    folders = []
    dick = {}
    for track in source:
        parent = pctl.master_library[track].parent_folder_path
        if parent not in folders:
            folders.append(parent)
        if parent not in dick:
            dick[parent] = []
        dick[parent].append(track)

    folders = list(reversed(folders))
    playlist = []

    for folder in folders:
        playlist += dick[folder]

    if custom_list is not None:
        return playlist

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Reversed Albums")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=0))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[index][0] + "\" a rva"

tab_menu.add_to_sub(_("Reverse Albums"), 0, gen_folder_reverse, pass_ref=True)
extra_tab_menu.add_to_sub(_("Reverse Albums"), 0, gen_folder_reverse, pass_ref=True)

def gen_dupe(index):
    playlist = pctl.multi_playlist[index][2]

    pctl.multi_playlist.append(pl_gen(title=gen_unique_pl_title(pctl.multi_playlist[index][0], _("Duplicate") + " ", 0),
                                      playing=pctl.multi_playlist[index][1],
                                      playlist=copy.deepcopy(playlist),
                                      position=pctl.multi_playlist[index][3],
                                      hide_title=pctl.multi_playlist[index][4],
                                      selected=pctl.multi_playlist[index][5]))

tab_menu.add_to_sub(_("Duplicate"), 0, gen_dupe, pass_ref=True)
extra_tab_menu.add_to_sub(_("Duplicate"), 0, gen_dupe, pass_ref=True)


def gen_sort_path(index):
    def path(index):
        return pctl.master_library[index].fullpath

    playlist = copy.deepcopy(pctl.multi_playlist[index][2])
    playlist = sorted(playlist, key=path)

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Filepath Sorted")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=0))

# tab_menu.add_to_sub("Filepath", 1, gen_sort_path, pass_ref=True)


def gen_sort_artist(index):

    def artist(index):
        return pctl.master_library[index].artist

    playlist = copy.deepcopy(pctl.multi_playlist[index][2])
    playlist = sorted(playlist, key=artist)

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Artist Sorted")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=0))

# tab_menu.add_to_sub("Artist → gui.abc", 0, gen_sort_artist, pass_ref=True)


def gen_sort_album(index):
    def album(index):
        return pctl.master_library[index].album

    playlist = copy.deepcopy(pctl.multi_playlist[index][2])
    playlist = sorted(playlist, key=album)

    pctl.multi_playlist.append(pl_gen(title=pctl.multi_playlist[index][0] + add_pl_tag(_("Album Sorted")),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=0))

# tab_menu.add_to_sub("Album → gui.abc", 0, gen_sort_album, pass_ref=True)
tab_menu.add_to_sub(_("Loved"), 0, gen_love, pass_ref=True)
extra_tab_menu.add_to_sub(_("Loved"), 0, gen_love, pass_ref=True)
tab_menu.add_to_sub(_("Has Comment"), 0, gen_comment, pass_ref=True)
extra_tab_menu.add_to_sub(_("Has Comment"), 0, gen_comment, pass_ref=True)
tab_menu.add_to_sub(_("Has Lyrics"), 0, gen_lyrics, pass_ref=True)
extra_tab_menu.add_to_sub(_("Has Lyrics"), 0, gen_lyrics, pass_ref=True)


def get_playing_line():
    if 3 > pctl.playing_state > 0:
        title = pctl.master_library[pctl.track_queue[pctl.queue_step]].title
        artist = pctl.master_library[pctl.track_queue[pctl.queue_step]].artist
        return artist + " - " + title
    else:
        return 'Stopped'


def get_broadcast_line():
    if pctl.broadcast_active:
        title = pctl.master_library[pctl.broadcast_index].title
        artist = pctl.master_library[pctl.broadcast_index].artist
        return artist + " - " + title
    else:
        return 'No Title'


def reload_config_file():

    if transcode_list:
        show_message("Cannot reload while a transcode is in progress!", mode='error')
        return

    load_prefs()
    gui.opened_config_file = False

    ddt.force_subpixel_text = prefs.force_subpixel_text
    ddt.clear_text_cache()

    show_message(_("Configuration reloaded"), mode="done")
    gui.update_layout()

def open_config_file():
    save_prefs()
    target = os.path.join(config_directory, "tauon.conf")
    if system == "windows" or msys:
        os.startfile(target)
    elif macos:
        subprocess.call(['open', "-t", target])
    else:
        subprocess.call(["xdg-open", target])
    show_message(_("Config file opened."), _('Click "Reload" if you made any changes'), mode='arrow')
    #reload_config_file()
    #gui.message_box = False
    gui.opened_config_file = True

def open_keymap_file():

    target = os.path.join(config_directory, "input.txt")

    if not os.path.isfile(target):
        show_message("Input file missing")
        return

    if system == "windows" or msys:
        os.startfile(target)
    elif macos:
        subprocess.call(['open', target])
    else:
        subprocess.call(["xdg-open", target])

def open_file(target):

    if not os.path.isfile(target):
        show_message("Input file missing")
        return

    if system == "windows" or msys:
        os.startfile(target)
    elif macos:
        subprocess.call(['open', target])
    else:
        subprocess.call(["xdg-open", target])


def open_data_directory():
    target = user_directory
    if system == "windows" or msys:
        os.startfile(target)
    elif macos:
        subprocess.call(['open', target])
    else:
        subprocess.call(["xdg-open", target])


def remove_folder(index):
    global default_playlist

    for b in range(len(default_playlist) - 1, -1, -1):
        r_folder = pctl.master_library[index].parent_folder_name
        if pctl.master_library[default_playlist[b]].parent_folder_name == r_folder:
            del default_playlist[b]

    reload()


def convert_folder(index):
    global default_playlist
    global transcode_list

    if system == 'windows' or msys:
        if not os.path.isfile(user_directory + '/encoder/ffmpeg.exe'):
            show_message("Error: Missing ffmpeg.exe from encoder directory",
                         "Expected location: " + user_directory + '/encoder/ffmpeg.exe', mode='warning')
            return

    else:
        if shutil.which(ffmpeg) is None:
            show_message("Error: ffmpeg does not appear to be installed")
            return

    folder = []
    if key_shift_down or key_shiftr_down:
        track_object = pctl.g(index)
        if track_object.is_network:
            show_message(_("Transcoding tracks from network locations is not supported"))
            return
        folder = [index]

        if prefs.transcode_codec == 'flac' and track_object.file_ext.lower() in ('mp3', 'opus',
                                                                                 'mp4', 'ogg',
                                                                                 'aac'):
            show_message("NO! Bad user!", "Im not going to let you transcode a lossy codec to a lossless one!",
                         mode='warning')

            return
        folder = [index]

    else:
        r_folder = pctl.master_library[index].parent_folder_path
        for item in default_playlist:
            if r_folder == pctl.master_library[item].parent_folder_path:

                track_object = pctl.g(item)
                if track_object.is_network:
                    show_message(_("Transcoding tracks from network locations is not supported"))
                    return

                if item not in folder:
                    folder.append(item)
                # print(prefs.transcode_codec)
                # print(track_object.file_ext)
                if prefs.transcode_codec == 'flac' and track_object.file_ext.lower() in ('mp3', 'opus',
                                                                                                      'mp4', 'ogg',
                                                                                                      'aac'):
                    show_message("NO! Bad user!", "Im not going to let you transcode a lossy codec to a lossless one!",
                                 mode='warning')

                    return

    # print(folder)
    transcode_list.append(folder)
    tm.ready("worker")


def transfer(index, args):
    global cargo
    global default_playlist
    old_cargo = copy.deepcopy(cargo)

    if args[0] == 1 or args[0] == 0:  # copy
        if args[1] == 1:  # single track
            cargo.append(index)
            if args[0] == 0:  # cut
                del default_playlist[playlist_selected]

        elif args[1] == 2:  # folder
            for b in range(len(default_playlist)):
                if pctl.master_library[default_playlist[b]].parent_folder_name == pctl.master_library[
                    index].parent_folder_name:
                    cargo.append(default_playlist[b])
            if args[0] == 0:  # cut
                for b in reversed(range(len(default_playlist))):
                    if pctl.master_library[default_playlist[b]].parent_folder_name == pctl.master_library[
                        index].parent_folder_name:
                        del default_playlist[b]

        elif args[1] == 3:  # playlist
            cargo += default_playlist
            if args[0] == 0:  # cut
                default_playlist = []

    elif args[0] == 2:  # Drop
        if args[1] == 1:  # Before

            insert = playlist_selected
            while insert > 0 and pctl.master_library[default_playlist[insert]].parent_folder_name == \
                    pctl.master_library[index].parent_folder_name:
                insert -= 1
                if insert == 0:
                    break
            else:
                insert += 1

            while len(cargo) > 0:
                default_playlist.insert(insert, cargo.pop())

        elif args[1] == 2:  # After
            insert = playlist_selected

            while insert < len(default_playlist) and pctl.master_library[default_playlist[insert]].parent_folder_name == \
                    pctl.master_library[index].parent_folder_name:
                insert += 1

            while len(cargo) > 0:
                default_playlist.insert(insert, cargo.pop())
        elif args[1] == 3:  # End
            default_playlist += cargo
            # cargo = []

        cargo = old_cargo

    reload()


def temp_copy_folder(ref):
    global cargo
    cargo = []
    transfer(ref, args=[1, 2])

def activate_track_box(index):
    global track_box
    global r_menu_index
    r_menu_index = index
    track_box = True
    track_box_path_tool_timer.set()

def menu_paste(position):
    paste(None, position)

def s_copy():

    # Copy tracks to internal clipboard
    # gui.lightning_copy = False
    # if key_shift_down:
    gui.lightning_copy = True

    clip = copy_from_clipboard()
    if "file://" in clip:
        copy_to_clipboard("")

    global cargo
    cargo = []
    if default_playlist:
        for item in shift_selection:
            cargo.append(default_playlist[item])

    if not cargo and -1 < playlist_selected < len(default_playlist):
        cargo.append(default_playlist[playlist_selected])

    tauon.cargo_playtime = None

    if len(cargo) == 1:
        tauon.cargo_playtime = star_store.get(cargo[0])

def directory_size(path):
    total = 0
    for dirpath, dirname, filenames in os.walk(path):
        for file in filenames:
            path = os.path.join(dirpath, file)
            total += os.path.getsize(path)
    return total



def lightning_paste():

    move = True
    # if not key_shift_down:
    #     move = False

    move_track = pctl.g(cargo[0])
    move_path = move_track.parent_folder_path

    for item in cargo:
        if move_path != pctl.g(item).parent_folder_path:
            show_message("More than one folder is in the clipboard",
                         'This function can only move one folder at a time.', mode='info')
            return

    match_track = pctl.g(default_playlist[shift_selection[0]])
    match_path = match_track.parent_folder_path

    if pctl.playing_state > 0 and move:
        if pctl.playing_object().parent_folder_path == move_path:
            pctl.stop(True)

    p = Path(match_path)
    s = list(p.parts)
    base = s[0]
    c = base
    del s[0]

    to_move = []
    for pl in pctl.multi_playlist:
        for i in reversed(range(len(pl[2]))):
            if pctl.g(pl[2][i]).parent_folder_path == move_track.parent_folder_path:
                to_move.append(pl[2][i])

    to_move = list(set(to_move))


    for level in s:
        upper = c
        c = os.path.join(c, level)

        t_artist = match_track.artist
        ta_artist = match_track.album_artist

        t_artist = filename_safe(t_artist)
        ta_artist = filename_safe(ta_artist)


        if (len(t_artist) > 0 and t_artist in level) or \
                (len(ta_artist) > 0 and ta_artist in level):

            print("found target artist level")
            print(t_artist)
            print("Upper folder is: " + upper)

            if len(move_path) < 4:
                show_message("Safety interupt! The source path seems oddly short.", move_path, mode='error')
                return

            if not os.path.isdir(upper):
                show_message("The target directory is missing!", upper, mode='warning')
                return

            if not os.path.isdir(move_path):
                show_message("The source directory is missing!", move_path, mode='warning')
                return

            protect = ("", "Documents", "Music", "Desktop", "Downloads")
            for fo in protect:
                if move_path.strip('\\/') == os.path.join(os.path.expanduser('~'), fo).strip("\\/"):
                    show_message("Better not do anything to that folder!", os.path.join(os.path.expanduser('~'), fo),
                                 mode='warning')
                    return

            if directory_size(move_path) > 3000000000:
                show_message("Folder size safety limit reached! (3GB)", move_path, mode='warning')
                return

            if len(next(os.walk(move_path))[2]) > max(20, len(to_move) * 2):
                show_message("Safety interupt! The source folder seems to have many files.", move_path, mode='warning')
                return

            artist = move_track.artist
            if move_track.album_artist != "":
                artist = move_track.album_artist

            artist = filename_safe(artist)

            if artist == "":
                show_message("The track needs to have an artist name.")
                return

            artist_folder = os.path.join(upper, artist)

            print("Target will be: " + artist_folder)

            if os.path.isdir(artist_folder):
                print("The target artist folder already exists")
            else:
                print("Need to make artist folder")
                os.makedirs(artist_folder)

            print("The folder to be moved is: " + move_path)
            load_order = LoadClass()
            load_order.target = os.path.join(artist_folder, move_track.parent_folder_name)
            load_order.playlist = pctl.multi_playlist[pctl.active_playlist_viewing][6]

            insert = shift_selection[0]
            old_insert = insert
            while insert < len(default_playlist) and pctl.master_library[pctl.multi_playlist[pctl.active_playlist_viewing][2][insert]].parent_folder_name == \
                    pctl.master_library[pctl.multi_playlist[pctl.active_playlist_viewing][2][old_insert]].parent_folder_name:
                insert += 1

            load_order.playlist_position = insert

            move_jobs.append((move_path, os.path.join(artist_folder, move_track.parent_folder_name), move, move_track.parent_folder_name, load_order))
            tm.ready("worker")
            # Remove all tracks with the old paths
            for pl in pctl.multi_playlist:
                for i in reversed(range(len(pl[2]))):
                    if pctl.g(pl[2][i]).parent_folder_path == move_track.parent_folder_path:
                        del pl[2][i]

            break
    else:
        show_message("Could not find a folder with the artist's name to match level at.")
        return

    # for file in os.listdir(artist_folder):
    #

    if album_mode:
        prep_gal()
        reload_albums(True)

    cargo.clear()
    gui.lightning_copy = False


def paste(playlist_no=None, track_id=None):

    clip = copy_from_clipboard()
    if "spotify" in clip:
        cargo.clear()
        for link in clip.split("\n"):
            print(link)
            link = link.strip()
            if clip.startswith("https://open.spotify.com/track/") or clip.startswith("spotify:track:"):
               spot_ctl.append_track(link)
            elif clip.startswith("https://open.spotify.com/album/") or clip.startswith("spotify:album:"):
                l = spot_ctl.append_album(link, return_list=True)
                if l:
                    cargo.extend(l)
            elif clip.startswith("https://open.spotify.com/playlist/"):
                spot_ctl.playlist(link)
        if album_mode:
            reload_albums()
        gui.pl_update += 1
        clip = False

    found = False
    if clip:
        clip = clip.split("\n")
        for i, line in enumerate(clip):
            if line.startswith("file://") or line.startswith("/"):
                target = str(urllib.parse.unquote(line)).replace("file://", "").replace("\r", "")
                load_order = LoadClass()
                load_order.target = target
                load_order.playlist = pctl.multi_playlist[pctl.active_playlist_viewing][6]

                if playlist_no is not None:
                    load_order.playlist = pl_to_id(playlist_no)
                if track_id is not None:
                    load_order.playlist_position = r_menu_position

                load_orders.append(copy.deepcopy(load_order))
                found = True

    if not found:

        if playlist_no is None:
            if track_id is None:
                transfer(0, (2, 3))
            else:
                transfer(track_id, (2, 2))
        else:
            append_playlist(playlist_no)

    gui.pl_update += 1
    return


def s_cut():
    s_copy()
    del_selected()


playlist_menu.add('Paste', paste, paste_deco)

def paste_playlist_coast_fire():
    url = None
    if spot_ctl.coasting and pctl.playing_state == 3:
        url = spot_ctl.get_album_url_from_local(pctl.playing_object())
    elif pctl.playing_ready() and "spotify-album-url" in pctl.playing_object().misc:
        url = pctl.playing_object().misc["spotify-album-url"]
    if url:
        default_playlist.extend(spot_ctl.append_album(url, return_list=True))
    gui.pl_update += 1

def paste_playlist_coast_album():
    shoot_dl = threading.Thread(target=paste_playlist_coast_fire)
    shoot_dl.daemon = True
    shoot_dl.start()

def paste_playlist_coast_album_deco():
    if spot_ctl.coasting or spot_ctl.playing:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]

playlist_menu.add(_('Add Playing Spotify Album'), paste_playlist_coast_album, paste_playlist_coast_album_deco, show_test=spotify_show_test)

def refind_playing():
    # Refind playing index
    if pctl.playing_ready():
        for i, n in enumerate(default_playlist):
            if pctl.track_queue[pctl.queue_step] == n:
                pctl.playlist_playing_position = i
                break


def del_selected(force_delete=False):
    global shift_selection
    global playlist_selected

    gui.update += 1
    gui.pl_update = 1

    if not shift_selection:
        shift_selection = [playlist_selected]

    if not default_playlist:
        return

    li = []

    for item in reversed(shift_selection):
        if item > len(default_playlist) - 1:
            return

        li.append((item, default_playlist[item]))

        # # Remove from playlist folder import list
        # tr = pctl.g(default_playlist[item])
        # if tr.parent_folder_path in pctl.multi_playlist[pctl.active_playlist_viewing][7]:
        #     pctl.multi_playlist[pctl.active_playlist_viewing][7].remove(tr.parent_folder_path)

        del default_playlist[item]

    if force_delete:
        for item in li:

            tr = pctl.g(item[1])
            if not tr.is_network:
                try:
                    send2trash(tr.fullpath)
                    show_message("Tracks sent to trash")
                except:
                    show_message("One or more tracks could not be sent to trash")

                    if force_delete:
                        try:
                            os.remove(tr.fullpath)
                            show_message("Files deleted", mode='info')
                        except:
                            show_message("Error deleting one or more files", mode='error')

    else:
        undo.bk_tracks(pctl.active_playlist_viewing, li)

    reload()
    tree_view_box.clear_target_pl(pctl.active_playlist_viewing)

    if playlist_selected > len(default_playlist) - 1:
        playlist_selected = len(default_playlist) - 1

    shift_selection = [playlist_selected]
    gui.pl_update += 1
    refind_playing()
    pctl.notify_change()


def force_del_selected():
    del_selected(force_delete=True)

def test_show(dummy):
    return album_mode

def show_in_gal(track, silent=False):
    # goto_album(playlist_selected)
    gui.gallery_animate_highlight_on = goto_album(playlist_selected)
    if not silent:
        gallery_select_animate_timer.set()


# Create track context menu
track_menu = Menu(195, show_icons=True)

track_menu.add(_('Open Folder'), open_folder, pass_ref=True, icon=folder_icon)
track_menu.add(_('Track Info…'), activate_track_box, pass_ref=True, icon=info_icon)


def last_fm_test(ignore):
    if lastfm.connected:
        return True
    else:
        return False

def heart_xmenu_colour():
    global r_menu_index
    if love(False, r_menu_index):
        return [245, 60, 60, 255]
    else:
        if colours.lm:
            return [255, 150, 180, 255]
        return None

heartx_icon.colour = [55, 55, 55, 255]
heartx_icon.xoff = 1
heartx_icon.yoff = 0
heartx_icon.colour_callback = heart_xmenu_colour


def spot_heart_xmenu_colour():
    if not (pctl.playing_state == 1 or pctl.playing_state == 2):
        return None
    tr = pctl.playing_object()
    if tr and "spotify-liked" in tr.misc:
        return [30, 215, 96, 255]
    else:
        return None



spot_heartx_icon.colour = [30, 215, 96, 255]
spot_heartx_icon.xoff = 3
spot_heartx_icon.yoff = 0
spot_heartx_icon.colour_callback = spot_heart_xmenu_colour


def love_decox():
    global r_menu_index

    if love(False, r_menu_index):
        return [colours.menu_text, colours.menu_background, _("Un-Love Track")]
    else:
        return [colours.menu_text, colours.menu_background, _("Love Track")]


def love_index():
    global r_menu_index

    notify = False
    if not gui.show_hearts:
        notify = True

    #love(True, r_menu_index)
    shoot_love = threading.Thread(target=love, args=[True, r_menu_index, False, notify])
    shoot_love.daemon = True
    shoot_love.start()

# Mark track as 'liked'
track_menu.add('Love', love_index, love_decox, icon=heartx_icon)


def add_to_queue(ref):
    pctl.force_queue.append(queue_item_gen(ref, r_menu_position, pl_to_id(pctl.active_playlist_viewing)))
    queue_timer_set()
    if prefs.stop_end_queue:
        pctl.auto_stop = False

def add_selected_to_queue():
    gui.pl_update += 1
    if prefs.stop_end_queue:
        pctl.auto_stop = False
    if gui.album_tab_mode:
        add_album_to_queue(default_playlist[get_album_info(playlist_selected)[1][0]], playlist_selected)
        queue_timer_set()
    else:
        pctl.force_queue.append(queue_item_gen(default_playlist[playlist_selected],
                                               playlist_selected,
                                               pl_to_id(pctl.active_playlist_viewing)))
        queue_timer_set()

def add_selected_to_queue_multi():

    if prefs.stop_end_queue:
        pctl.auto_stop = False
    for index in shift_selection:
        pctl.force_queue.append(queue_item_gen(default_playlist[index],
                                               index,
                                               pl_to_id(pctl.active_playlist_viewing)))


def queue_timer_set(plural=False, queue_object=None):

    queue_add_timer.set()
    gui.frame_callback_list.append(TestTimer(2.51))
    gui.queue_toast_plural = plural
    if queue_object:
        gui.toast_queue_object = queue_object
    else:
        if pctl.force_queue:
            gui.toast_queue_object = pctl.force_queue[-1]


def split_queue_album(id):

    item = pctl.force_queue[0]

    pl = id_to_pl(item[2])
    if pl is None:
        return

    playlist = pctl.multi_playlist[pl][2]

    i = pctl.playlist_playing_position + 1
    parts = []
    album_parent_path = pctl.g(item[0]).parent_folder_path

    while i < len(playlist):
        if pctl.g(playlist[i]).parent_folder_path != album_parent_path:
            break

        parts.append((playlist[i], i))
        i += 1

    del pctl.force_queue[0]

    for part in reversed(parts):
        pctl.force_queue.insert(0, queue_item_gen(part[0], part[1], item[3]))
    return(len(parts))

def add_to_queue_next(ref):

    if pctl.force_queue and pctl.force_queue[0][4] == 1:
        split_queue_album(None)

    pctl.force_queue.insert(0, queue_item_gen(ref, r_menu_position, pl_to_id(pctl.active_playlist_viewing)))

# def toggle_queue(mode=0):
#     if mode == 1:
#         return prefs.show_queue
#     prefs.show_queue ^= True
#     prefs.show_queue ^= True


track_menu.add(_('Add to Queue'), add_to_queue, pass_ref=True, hint="MB3")

track_menu.add(_('↳ After Current Track'), add_to_queue_next, pass_ref=True, show_test=test_shift)

track_menu.add(_('Show in Gallery'), show_in_gal, pass_ref=True, show_test=test_show)


track_menu.add_sub(_("Meta…"), 160)

track_menu.br()
#track_menu.add('Cut', s_cut, pass_ref=False)
#track_menu.add('Remove', del_selected)
track_menu.add(_('Copy'), s_copy, pass_ref=False)

#track_menu.add(_('Paste + Transfer Folder'), lightning_paste, pass_ref=False, show_test=lightning_move_test)

track_menu.add(_('Paste'), menu_paste, paste_deco, pass_ref=True)


def delete_track(track_ref):

    tr = pctl.g(track_ref)
    fullpath = tr.fullpath

    if system == "windows" or msys:
        fullpath = fullpath.replace("/", "\\")

    if tr.is_network:
        show_message(_("Cannot delete a network track"))
        return

    while track_ref in default_playlist:
        default_playlist.remove(track_ref)

    try:
        send2trash(fullpath)

        if os.path.exists(fullpath):
            try:
                os.remove(fullpath)
                show_message(_("File deleted"), fullpath, mode='info')
            except:
                show_message(_("Error deleting file"), fullpath, mode='error')
        else:
            show_message(_("File moved to trash"))

    except:
        try:
            os.remove(fullpath)
            show_message(_("File deleted"), fullpath, mode='info')
        except:
            show_message(_("Error deleting file"), fullpath, mode='error')

    reload()
    refind_playing()
    pctl.notify_change()


track_menu.add(_('Delete Track File'), delete_track, pass_ref=True, icon=delete_icon, show_test=test_shift)

track_menu.br()


def rename_tracks_deco():

    if key_shift_down or key_shiftr_down:
        return [colours.menu_text, colours.menu_background, _("Rename (Single track)")]
    else:
        return [colours.menu_text, colours.menu_background, _("Rename Tracks…")]

# rename_tracks_icon.colour = [244, 241, 66, 255]
# rename_tracks_icon.colour = [204, 255, 66, 255]
rename_tracks_icon.colour = [204, 100, 205, 255]
rename_tracks_icon.xoff = 1
track_menu.add_to_sub("Rename Tracks…", 0, rename_track_box.activate, rename_tracks_deco, pass_ref=True, icon=rename_tracks_icon)

def activate_trans_editor():
    trans_edit_box.active = True

track_menu.add_to_sub(_("Edit fields…"), 0, activate_trans_editor)


def delete_folder(index, force=False):

    track = pctl.master_library[index]

    if track.is_network:
        show_message(_("Cannot physically delete"), _("One or more tracks is from a network location!"), mode='info')
        return

    old = track.parent_folder_path

    if len(old) < 5:
        show_message("This folder path seems short, I don't wanna try delete that", mode='warning')
        return

    if not os.path.exists(old):
        show_message("Error deleting folder. The folder seems to be missing.", "It's gone! Just gone!", mode='error')
        return

    protect = ("", "Documents", "Music", "Desktop", "Downloads")

    for fo in protect:
        if old.strip('\\/') == os.path.join(os.path.expanduser('~'), fo).strip("\\/"):
            show_message("Woah, careful there!", "I don't think we should delete that folder.", mode='warning')
            return

    if directory_size(old) > 1500000000:
        show_message(_("Delete size safety limit reached!") + " (1.5GB)", old, mode='warning')
        return

    try:

        if pctl.playing_state > 0 and os.path.normpath(
                pctl.master_library[pctl.track_queue[pctl.queue_step]].parent_folder_path) == os.path.normpath(old):
            pctl.stop(True)

        if force:
            shutil.rmtree(old)
        else:
            if system == "windows" or msys:
                send2trash(old.replace("/", "\\"))
            else:
                send2trash(old)

        for i in reversed(range(len(default_playlist))):

            if old == pctl.master_library[default_playlist[i]].parent_folder_path:
                del default_playlist[i]

        if not os.path.exists(old):
            if force:
                show_message("Folder deleted.", old, mode='done')
            else:
                show_message("Folder sent to trash.", old, mode='done')
        else:
            show_message("Hmm, its still there", old, mode='error')

        if album_mode:
            prep_gal()
            reload_albums()

    except:
        if force:
            show_message("Unable to comply.", "Could not delete folder. Try check permissions.", mode='error')
        else:
            show_message(_("Folder could not be trashed."), "Try again while holding shift to force delete.", mode='error')

    tree_view_box.clear_target_pl(pctl.active_playlist_viewing)
    gui.pl_update += 1
    pctl.notify_change()


def rename_parent(index, template):

    #template = prefs.rename_folder_template
    template = template.strip("/\\")
    track = pctl.master_library[index]

    if track.is_network:
        show_message("Cannot rename", "One or more tracks is from a network location!", mode='info')
        return

    old = track.parent_folder_path
    #print(old)

    new = parse_template2(template, track)

    if len(new) < 1:
        show_message("Rename error.", "The generated name is too short", mode='warning')
        return

    if len(old) < 5:
        show_message("Rename error.", "This folder path seems short, I don't wanna try rename that", mode='warning')
        return

    if not os.path.exists(old):
        show_message("Rename Failed. The original folder is missing.", mode='warning')
        return

    protect = ("", "Documents", "Music", "Desktop", "Downloads")

    for fo in protect:
        if os.path.normpath(old) == os.path.normpath(os.path.join(os.path.expanduser('~'), fo)):
            show_message("Woah, careful there!", "I don't think we should rename that folder.", mode='warning')
            return

    print(track.parent_folder_path)
    re = os.path.dirname(track.parent_folder_path.rstrip("/\\"))
    print(re)
    new_parent_path = os.path.join(re, new)
    print(new_parent_path)

    pre_state = 0

    for key, object in pctl.master_library.items():

        if object.fullpath == "":
            continue


        if old == object.parent_folder_path:

            new_fullpath = os.path.join(new_parent_path, object)

            if os.path.normpath(new_parent_path) == os.path.normpath(old):
                show_message("The folder already has that name.")
                return

            if os.path.exists(new_parent_path):
                show_message("Rename Failed.", "A folder with that name already exists", mode='warning')
                return

            if key == pctl.track_queue[pctl.queue_step] and pctl.playing_state > 0:
                pre_state = pctl.stop(True)


            object.parent_folder_name = new
            object.parent_folder_path = new_parent_path
            object.fullpath = new_fullpath

            search_string_cache.pop(object.index, None)
            search_dia_string_cache.pop(object.index, None)

        # Fix any other tracks paths that contain the old path
        if os.path.normpath(object.fullpath)[:len(old)] == os.path.normpath(old) \
                and os.path.normpath(object.fullpath)[len(old)] in ('/', '\\'):
            object.fullpath = os.path.join(new_parent_path, object.fullpath[len(old):].lstrip('\\/'))
            object.parent_folder_path = os.path.join(new_parent_path, object.parent_folder_path[len(old):].lstrip('\\/'))

            search_string_cache.pop(object.index, None)
            search_dia_string_cache.pop(object.index, None)

    if new_parent_path is not None:
        try:
            os.rename(old, new_parent_path)
            print(new_parent_path)
        except:

            show_message("Rename Failed!", mode='error' "Something went wrong, sorry.")
            return

    show_message("Folder renamed.", "Renamed to: " + new, mode='done')

    if pre_state == 1:
        pctl.revert()

    tree_view_box.clear_target_pl(pctl.active_playlist_viewing)
    pctl.notify_change()

def rename_folders(index):
    global track_box
    global rename_index
    global input_text

    track_box = False
    rename_index = index

    if pctl.g(index).is_network:
        show_message("Not applicable for a network track.")
        return

    gui.rename_folder_box = True
    input_text = ""
    shift_selection.clear()

    global quick_drag
    global playlist_hold
    quick_drag = False
    playlist_hold = False


mod_folder_icon.colour = [229, 98, 98, 255]
track_menu.add_to_sub(_("Modify Folder…"), 0, rename_folders, pass_ref=True, icon=mod_folder_icon)


def move_folder_up(index, do=False):

    track = pctl.master_library[index]

    if track.is_network:
        show_message("Cannot move", "One or more tracks is from a network location!", mode='info')
        return

    parent_folder = os.path.dirname(track.parent_folder_path)
    folder_name = track.parent_folder_name
    move_target = track.parent_folder_path
    upper_folder = os.path.dirname(parent_folder)

    if not os.path.exists(track.parent_folder_path):
        if do:
            show_message("Error shifting directory", "The directory does not appear to exist", mode='warning')
        return False

    if len(os.listdir(parent_folder)) > 1:
        return False

    if do is False:
        return True


    pre_state = 0
    if pctl.playing_state > 0 and track.parent_folder_path in pctl.playing_object().parent_folder_path:
        pre_state = pctl.stop(True)

    try:

        # Rename the track folder to something temporary
        os.rename(move_target, os.path.join(parent_folder, "RMTEMP000"))

        # Move the temporary folder up 2 levels
        shutil.move(os.path.join(parent_folder, "RMTEMP000"), upper_folder)

        # Delete the old directory that contained the original folder
        shutil.rmtree(parent_folder)

        # Rename the moved folder back to its original name
        os.rename(os.path.join(upper_folder, "RMTEMP000"), os.path.join(upper_folder, folder_name))

    except Exception as e:
        show_message("System Error!", str(e), mode='error')

    # Fix any other tracks paths that contain the old path
    old = track.parent_folder_path
    new_parent_path = os.path.join(upper_folder, folder_name)
    for key, object in pctl.master_library.items():

        if os.path.normpath(object.fullpath)[:len(old)] == os.path.normpath(old) \
                and os.path.normpath(object.fullpath)[len(old)] in ('/', '\\'):
            object.fullpath = os.path.join(new_parent_path, object.fullpath[len(old):].lstrip('\\/'))
            object.parent_folder_path = os.path.join(new_parent_path, object.parent_folder_path[len(old):].lstrip('\\/'))

            search_string_cache.pop(object.index, None)
            search_dia_string_cache.pop(object.index, None)

            print(object.fullpath)
            print(object.parent_folder_path)

    if pre_state == 1:
        pctl.revert()


def clean_folder(index, do=False):

    track = pctl.master_library[index]

    if track.is_network:
        show_message("Cannot clean", "One or more tracks is from a network location!", mode='info')
        return

    folder = track.parent_folder_path
    found = 0
    to_purge = []
    if not os.path.isdir(folder):
        return 0
    try:
        for item in os.listdir(folder):
            if ('AlbumArt' == item[:8] and '.jpg' in item.lower()) \
                    or 'desktop.ini' == item\
                    or 'Thumbs.db' == item\
                    or '.DS_Store' == item:

                to_purge.append(item)
                found += 1
            elif "__MACOSX" == item and os.path.isdir(os.path.join(folder, item)):
                found += 1
                found += 1
                if do:
                    print("Deleting Folder: " + os.path.join(folder, item))
                    shutil.rmtree(os.path.join(folder, item))

        if do:
            for item in to_purge:
                if os.path.isfile(os.path.join(folder, item)):
                    print('Deleting File: ' + os.path.join(folder, item))
                    os.remove(os.path.join(folder, item))
            #clear_img_cache()

            for track_id in default_playlist:
                if pctl.g(track_id).parent_folder_path == folder:
                    clear_track_image_cache(pctl.g(track_id))

    except Exception as e:
        #show_message(str(e))
        show_message("Error deleting files.", "May not have permission or file may be set to read-only", mode='warning')
        return 0

    return found



def reset_play_count(index):

    star_store.remove(index)


#track_menu.add_to_sub("Reset Track Play Count", 0, reset_play_count, pass_ref=True)


def vacuum_playtimes(index):

    todo = []
    for k in default_playlist:
        if pctl.master_library[index].parent_folder_name == pctl.master_library[k].parent_folder_name:
            todo.append(k)

    for track in todo:

        tr = pctl.g(track)

        total_playtime = 0
        flags = ""

        to_del = []

        for key, value in star_store.db.items():
            if key[0].lower() == tr.artist.lower() and tr.artist and key[1].lower().replace(" ", "") == tr.title.lower().replace(" ", "") and tr.title:
                to_del.append(key)
                total_playtime += value[0]
                flags = "".join(set(flags + value[1]))

        for key in to_del:
            del star_store.db[key]

        key = star_store.object_key(tr)
        value = [total_playtime, flags, 0]
        if key not in star_store.db:
            print("Saving value")
            star_store.db[key] = value
        else:
            print("ERROR KEY ALREADY HERE?")


def reload_metadata(input, keep_star=True):
    global todo

    # vacuum_playtimes(index)
    # return
    todo = []

    if isinstance(input, list):
        todo = input

    else:
        for k in default_playlist:
            if pctl.master_library[input].parent_folder_path == pctl.master_library[k].parent_folder_path:
                todo.append(pctl.master_library[k])

    for i in reversed(range(len(todo))):
        if todo[i].is_cue:
            del todo[i]

    for track in todo:

        search_string_cache.pop(track.index, None)
        search_dia_string_cache.pop(track.index, None)

        print('Reloading Metadata for ' + track.filename)

        if keep_star:
            star = star_store.full_get(track.index)
            star_store.remove(track.index)

        pctl.master_library[track.index] = tag_scan(track)

        if keep_star:
            if star is not None and (star[0] > 0 or star[1] or star[2] > 0):
                star_store.merge(track.index, star)

            pctl.notify_change()

    gui.pl_update += 1

def reload_metadata_selection():

    cargo = []
    for item in shift_selection:
        cargo.append(default_playlist[item])

    todo = []

    for k in cargo:
        if pctl.master_library[k].is_cue == False:
            todo.append(k)

    for track in todo:

        print('Reloading Metadate for ' + pctl.master_library[track].filename)

        star = star_store.full_get(track)
        star_store.remove(track)
        pctl.master_library[track] = tag_scan(pctl.master_library[track])

        if star is not None and (star[0] > 0 or star[1]):
            star_store.merge(track, star)

    gui.pl_update += 1


def editor(index):
    todo = []
    obs = []

    if key_shift_down and index is not None:
        todo = [index]
        obs = [pctl.master_library[index]]
    else:
        if index is None:
            for item in shift_selection:
                todo.append(default_playlist[item])
                obs.append(pctl.master_library[default_playlist[item]])
            if len(todo) > 0:
                index = todo[0]
        else:
            for k in default_playlist:
                if pctl.master_library[index].parent_folder_path == pctl.master_library[k].parent_folder_path:
                    if pctl.master_library[k].is_cue == False:
                        todo.append(k)
                        obs.append(pctl.master_library[k])

    # Keep copy of play times
    old_stars = []
    for track in todo:
        item = []
        item.append(pctl.g(track))
        item.append(star_store.key(track))
        item.append(star_store.full_get(track))
        old_stars.append(item)


    file_line = ""
    for track in todo:
        file_line += ' "'
        file_line += pctl.master_library[track].fullpath
        file_line += '"'

    if system == "windows" or msys:
        file_line = file_line.replace("/", "\\")

    prefix = ""
    app = prefs.tag_editor_target

    if (system == "windows" or msys) and app:
        if app[0] != '"':
            app = '"' + app
        if app[-1] != '"':
            app = app + '"'

    app_switch = ""

    ok = False


    prefix = launch_prefix

    if system == "linux":
        ok = whicher(prefs.tag_editor_target)
    else:

        if not os.path.isfile(prefs.tag_editor_target.strip('"')):
            print(prefs.tag_editor_target)
            show_message("Application not found", prefs.tag_editor_target, mode='info')
            return

        ok = True


    if not ok:
        show_message(_("Tag editor app does not appear to be installed."), mode='warning')

        if flatpak_mode:
            show_message(_("App not found on host OR insufficient Flatpak permissions."),
                         'See https://github.com/Taiko2k/TauonMusicBox/wiki/Flatpak-Extra-Steps for details.', mode='bubble')

        return

    if 'picard' in prefs.tag_editor_target:
        app_switch = " --d "

    line = prefix + app + app_switch + file_line

    show_message(prefs.tag_editor_name + " launched.", "Fields will be updated once application is closed.", mode='arrow')
    gui.update = 1

    complete = subprocess.run(shlex.split(line), stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    if 'picard' in prefs.tag_editor_target:
        r = complete.stderr.decode()
        for line in r.split("\n"):
            if 'file._rename' in line and ' Moving file ' in line:
                a, b = line.split(" Moving file ")[1].split(" => ")
                a = a.strip("'").strip('"')
                b = b.strip("'").strip('"')

                for track in todo:
                    if pctl.master_library[track].fullpath == a:
                        pctl.master_library[track].fullpath = b
                        pctl.master_library[track].filename = os.path.basename(b)
                        print("External Edit: File rename detected.")
                        print("    Renaming: " + a)
                        print("          To: " + b)
                        break
                else:
                    print("External Edit: A file rename was detected but track was not found.")

    gui.message_box = False
    reload_metadata(obs, keep_star=False)

    # Re apply playtime data in case file names change
    for item in old_stars:

        old_key = item[1]
        old_value = item[2]

        if not old_value:  # ignore if there was no old playcount metadata
            continue

        new_key = star_store.object_key(item[0])
        new_value = star_store.full_get(item[0].index)

        if old_key == new_key:
            continue

        if new_value is None:
            new_value = [0, "", 0]

        new_value[0] += old_value[0]
        new_value[1] = "".join(set(new_value[1] + old_value[1]))

        if old_key in star_store.db:
            del star_store.db[old_key]

        star_store.db[new_key] = new_value


    gui.pl_update = 1
    gui.update = 1
    pctl.notify_change()


def launch_editor(index):

    if snap_mode:
        show_message("Sorry, this feature isn't (yet) available with Snap.")
        return

    if pctl.g(index).is_network:
        show_message("Cannot edit tags of a network track.")
        return

    mini_t = threading.Thread(target=editor, args=[index])
    mini_t.daemon = True
    mini_t.start()

def launch_editor_selection(index):

    for position in shift_selection:
        if pctl.g(default_playlist[position]).is_network:
            show_message("Cannot edit tags of a network track.")
            return

    mini_t = threading.Thread(target=editor, args=[None])
    mini_t.daemon = True
    mini_t.start()

# track_menu.add('Reload Metadata', reload_metadata, pass_ref=True)
track_menu.add_to_sub(_("Rescan Tags"), 0, reload_metadata, pass_ref=True)


mbp_icon = MenuIcon(asset_loader('mbp-g.png'))
mbp_icon.base_asset = asset_loader('mbp-gs.png')

mbp_icon.xoff = 2
mbp_icon.yoff = -1

if gui.scale == 1.25:
    mbp_icon.yoff = 0

edit_icon = None
if prefs.tag_editor_name == "Picard":
    edit_icon = mbp_icon

def edit_deco():

    if key_shift_down or key_shiftr_down:
        return [colours.menu_text, colours.menu_background, prefs.tag_editor_name + " (Single track)"]
    else:
        return [colours.menu_text, colours.menu_background, _("Edit with ") + prefs.tag_editor_name]

track_menu.add_to_sub("Edit with", 0, launch_editor, pass_ref=True, icon=edit_icon, render_func=edit_deco)



def show_lyrics_menu(index):
    global track_box
    track_box = False
    enter_showcase_view(track_id=r_menu_index)
    inp.mouse_click = False

track_menu.add_to_sub(_("Lyrics..."), 0, show_lyrics_menu, pass_ref=True)

def recode(text, enc):
    return text.encode("Latin-1", 'ignore').decode(enc, 'ignore')

j_chars = "あおいえうんわらまやはなたさかみりひにちしきるゆむぬつすくれめへねてせけをろもほのとそこアイウエオンヲラマハナタサカミヒニチシキルユムフヌツスクレメヘネテセケロヨモホノトソコ"

def intel_moji(index):

    gui.pl_update += 1
    gui.update += 1

    track = pctl.master_library[index]

    lot = []

    for item in default_playlist:

        if track.album == pctl.master_library[item].album and \
            track.parent_folder_name == pctl.master_library[item].parent_folder_name:
            lot.append(item)

    lot = set(lot)


    l_artist = track.artist.encode("Latin-1", 'ignore')
    l_album = track.album.encode("Latin-1", 'ignore')
    detect = None

    if track.artist not in track.parent_folder_path:
        for enc in encodings:
            try:
                q_artist = l_artist.decode(enc,)
                if q_artist.strip(" ") in track.parent_folder_path.strip(" "):
                    detect = enc
                    break
            except:
                continue


    if detect is None and track.album not in track.parent_folder_path:
        for enc in encodings:
            try:
                q_album = l_album.decode(enc,)
                if q_album in track.parent_folder_path:
                    detect = enc
                    break
            except:
                continue

    for item in lot:
        t_track = pctl.master_library[item]

        if detect is None:
            for enc in encodings:
                test = recode(t_track.artist, enc)
                for cha in test:
                    if cha in j_chars:
                        detect = enc
                        print("This looks like Japanese: " + test)
                        break
                    if detect is not None:
                        break

        if detect is None:
            for enc in encodings:
                test = recode(t_track.title, enc)
                for cha in test:
                    if cha in j_chars:
                        detect = enc
                        print("This looks like Japanese: " + test)
                        break
                    if detect is not None:
                        break
        if detect is not None:
            break

    if detect is not None:
        print("Fix Mojibake: Detected encoding as: " + detect)
        for item in lot:
            track = pctl.master_library[item]
            #key = pctl.master_library[item].title + pctl.master_library[item].filename
            key = star_store.full_get(item)
            star_store.remove(item)

            track.title = recode(track.title, detect)
            track.album = recode(track.album, detect)
            track.artist = recode(track.artist, detect)
            track.album_artist = recode(track.album_artist, detect)
            track.genre = recode(track.genre, detect)
            track.comment = recode(track.comment, detect)
            track.lyrics = recode(track.lyrics, detect)

            if key != None:
                star_store.insert(item, key)

            search_string_cache.pop(track.index, None)
            search_dia_string_cache.pop(track.index, None)

    else:
        show_message("Autodetect failed")


track_menu.add_to_sub(_("Fix Mojibake"), 0, intel_moji, pass_ref=True)


def sel_to_car():

    global default_playlist
    cargo = []

    for item in shift_selection:
        cargo.append(default_playlist[item])


# track_menu.add_to_sub("Copy Playlist", 1, transfer, pass_ref=True, args=[1, 3])
def cut_selection():
    sel_to_car()
    del_selected()

def clip_ar_al(index):
    line = pctl.master_library[index].artist + " - " + \
           pctl.master_library[index].album
    SDL_SetClipboardText(line.encode('utf-8'))

def clip_ar(index):
    if pctl.master_library[index].album_artist != "":
        line = pctl.master_library[index].album_artist
    else:
        line = pctl.master_library[index].artist
    SDL_SetClipboardText(line.encode('utf-8'))

def clip_title(index):

    n_track = pctl.master_library[index]

    if not prefs.use_title and n_track.album_artist != "" and n_track.album != "":
        line = n_track.album_artist + " - " + n_track.album
    else:
        line = n_track.parent_folder_name

    SDL_SetClipboardText(line.encode('utf-8'))

selection_menu = Menu(200, show_icons=False)
folder_menu = Menu(193, show_icons=True)

folder_menu.add(_('Open Folder'), open_folder, pass_ref=True, icon=folder_icon)

folder_menu.add(_("Modify Folder…"), rename_folders, pass_ref=True, icon=mod_folder_icon)
folder_tree_menu.add(_("Modify Folder…"), rename_folders, pass_ref=True, icon=mod_folder_icon)
# folder_menu.add(_("Add Album to Queue"), add_album_to_queue, pass_ref=True)
folder_menu.add(_("Add Album to Queue"), add_album_to_queue, pass_ref=True)
folder_menu.add(_("Enqueue Album Next"), add_album_to_queue_fc, pass_ref=True)

gallery_menu.add(_("Modify Folder…"), rename_folders, pass_ref=True, icon=mod_folder_icon)

folder_menu.add(_("Rename Tracks…"), rename_track_box.activate, rename_tracks_deco, pass_ref=True, icon=rename_tracks_icon)
folder_tree_menu.add(_("Rename Tracks…"), rename_track_box.activate, pass_ref=True, icon=rename_tracks_icon)

if not snap_mode:
    folder_menu.add("Edit with", launch_editor_selection, pass_ref=True,
                       icon=edit_icon, render_func=edit_deco)

folder_tree_menu.add(_("Add Album to Queue"), add_album_to_queue, pass_ref=True)
folder_tree_menu.add(_("Enqueue Album Next"), add_album_to_queue_fc, pass_ref=True)

folder_tree_menu.br()
folder_tree_menu.add(_('Collapse All'), collapse_tree, collapse_tree_deco)
folder_tree_menu.add("lock", lock_folder_tree, lock_folder_tree_deco)


def lightning_copy():
    s_copy()
    gui.lightning_copy = True

#selection_menu.br()

def toggle_transcode(mode=0):
    if mode == 1:
        return prefs.enable_transcode
    prefs.enable_transcode ^= True

def toggle_chromecast(mode=0):
    if mode == 1:
        return prefs.show_chromecast
    prefs.show_chromecast ^= True

def toggle_transfer(mode=0):
    if mode == 1:
        return prefs.show_transfer
    prefs.show_transfer ^= True

    if prefs.show_transfer:
        show_message("Warning! Using this function moves physical folders.",
                     "This menu entry appears after selecting 'copy'. See manual (github wiki) for more info.", mode='info')


transcode_icon.colour = [239, 74, 157, 255]

def transcode_deco():

    if key_shift_down or key_shiftr_down:
        return [colours.menu_text, colours.menu_background, _("Transcode Single")]
    else:
        return [colours.menu_text, colours.menu_background, _('Transcode Folder')]


folder_menu.add(_('Rescan Tags'), reload_metadata, pass_ref=True)
folder_menu.add(_("Edit fields…"), activate_trans_editor)
folder_menu.add(_('Vacuum Playtimes'), vacuum_playtimes, pass_ref=True, show_test=test_shift)
folder_menu.add(_('Transcode Folder'), convert_folder, transcode_deco, pass_ref=True, icon=transcode_icon, show_test=toggle_transcode)
gallery_menu.add(_('Transcode Folder'), convert_folder, transcode_deco, pass_ref=True, icon=transcode_icon, show_test=toggle_transcode)
folder_menu.br()

spot_ctl = SpotCtl(tauon)
tauon.spot_ctl = spot_ctl

spot_ctl.cache_saved_albums = spot_cache_saved_albums

# Copy album title text to clipboard
folder_menu.add(_('Copy "Artist - Album"'), clip_title, pass_ref=True)

def get_album_spot_url(track_id):
    track_object = pctl.g(track_id)
    url = spot_ctl.get_album_url_from_local(track_object)
    if url:
        copy_to_clipboard(url)
        show_message(_("URL copied to clipboard"), mode="done")
    else:
        show_message(_("No results found"))



def get_album_spot_url_deco(track_id):
    track_object = pctl.g(track_id)
    if "spotify-album-url" in track_object.misc:
        text = _("Copy Spotify Album URL")
    else:
        text = _("Lookup Spotify Album URL")
    return [colours.menu_text, colours.menu_background, text]

folder_menu.add('Lookup Spotify Album URL', get_album_spot_url, get_album_spot_url_deco, pass_ref=True, pass_ref_deco=True, show_test=spotify_show_test, icon=spot_icon)


def add_to_spotify_library_deco(track_id):
    track_object = pctl.g(track_id)
    text = _("Save to Spotify Library")
    if track_object.file_ext != "SPTY":
        return (colours.menu_text_disabled, colours.menu_background, text)

    album_url = track_object.misc.get("spotify-album-url")
    if album_url and album_url in spot_ctl.cache_saved_albums:
        text = _("Remove from Spotify Library")

    return (colours.menu_text, colours.menu_background, text)

def add_to_spotify_library2(album_url):

    if album_url in spot_ctl.cache_saved_albums:
        spot_ctl.remove_album_from_library(album_url)
    else:
        spot_ctl.add_album_to_library(album_url)

    for i, p in enumerate(pctl.multi_playlist):
        code = pctl.gen_codes.get(p[6])
        if code and code.startswith("sal"):
            print("Fetching Spotify Library...")
            regenerate_playlist(i, silent=True)

def add_to_spotify_library(track_id):

    track_object = pctl.g(track_id)
    album_url = track_object.misc.get("spotify-album-url")
    if track_object.file_ext != "SPTY" or not album_url:
        return

    shoot_dl = threading.Thread(target=add_to_spotify_library2, args=([album_url]))
    shoot_dl.daemon = True
    shoot_dl.start()



folder_menu.add('Add to Spotify Library', add_to_spotify_library, add_to_spotify_library_deco, pass_ref=True, pass_ref_deco=True, show_test=spotify_show_test, icon=spot_icon)


# Copy artist name text to clipboard
#folder_menu.add(_('Copy "Artist"'), clip_ar, pass_ref=True)

def selection_queue_deco():

    total = 0
    for item in shift_selection:
        total += pctl.g(default_playlist[item]).length

    total = get_hms_time(total)

    text = (_('Queue %d') % len(shift_selection)) + f" [{total}]"

    return [colours.menu_text, colours.menu_background, text]


selection_menu.add(_('Add to queue'), add_selected_to_queue_multi, selection_queue_deco)

selection_menu.br()

selection_menu.add(_('Rescan Tags'), reload_metadata_selection)


selection_menu.add(_("Edit fields…"), activate_trans_editor)


selection_menu.add("Edit with ", launch_editor_selection, pass_ref=True, icon=edit_icon, render_func=edit_deco)

selection_menu.br()
folder_menu.br()

# It's complicated
# folder_menu.add(_('Copy Folder From Library'), lightning_copy)

selection_menu.add(_('Copy'), s_copy)
selection_menu.add(_('Cut'), s_cut)
selection_menu.add(_('Remove'), del_selected)
selection_menu.add(_('Delete Files'), force_del_selected, show_test=test_shift, icon=delete_icon)

folder_menu.add(_('Copy'), s_copy)
gallery_menu.add(_('Copy'), s_copy)
# folder_menu.add(_('Cut'), s_cut)
#folder_menu.add(_('Paste + Transfer Folder'), lightning_paste, pass_ref=False, show_test=lightning_move_test)
#gallery_menu.add(_('Paste + Transfer Folder'), lightning_paste, pass_ref=False, show_test=lightning_move_test)
folder_menu.add(_('Remove'), del_selected)
gallery_menu.add(_('Remove'), del_selected)

def toggle_rym(mode=0):
    if mode == 1:
        return prefs.show_rym
    prefs.show_rym ^= True

def toggle_band(mode=0):
    if mode == 1:
        return prefs.show_band
    prefs.show_band ^= True

def toggle_wiki(mode=0):
    if mode == 1:
        return prefs.show_wiki
    prefs.show_wiki ^= True

# def toggle_show_discord(mode=0):
#     if mode == 1:
#         return prefs.discord_show
#     if prefs.discord_show is False and discord_allow is False:
#         show_message("Warning: pypresence package not installed")
#     prefs.discord_show ^= True

def toggle_gen(mode=0):
    if mode == 1:
        return prefs.show_gen
    prefs.show_gen ^= True




def ser_band_done(result):
    if result:
        webbrowser.open(result, new=2, autoraise=True)
        gui.message_box = False
        gui.update += 1
    else:
        show_message(_("No matching artist result found"))


def ser_band(track_id):
    tr = pctl.g(track_id)
    if tr.artist:
        shoot_dl = threading.Thread(target=bandcamp_search, args=([tr.artist, ser_band_done]))
        shoot_dl.daemon = True
        shoot_dl.start()
        show_message(_("Searching..."))


def ser_rym(index):
    if len(pctl.master_library[index].artist) < 2:
        return
    line = "http://rateyourmusic.com/search?searchtype=a&searchterm=" + urllib.parse.quote(pctl.master_library[index].artist)
    webbrowser.open(line, new=2, autoraise=True)


def copy_to_clipboard(text):
    SDL_SetClipboardText(text.encode())

def copy_from_clipboard():
    return SDL_GetClipboardText().decode()


def clip_aar_al(index):
    if pctl.master_library[index].album_artist == "":
        line = pctl.master_library[index].artist + " - " + \
               pctl.master_library[index].album
    else:
        line = pctl.master_library[index].album_artist + " - " + \
               pctl.master_library[index].album
    SDL_SetClipboardText(line.encode('utf-8'))

def ser_gen_thread(tr):

    s_artist = tr.artist
    s_title = tr.title

    if s_artist in prefs.lyrics_subs:
        s_artist = prefs.lyrics_subs[s_artist]
    if s_title in prefs.lyrics_subs:
        s_title = prefs.lyrics_subs[s_title]

    line = genius(s_artist, s_title, return_url=True)

    r = requests.head(line)

    if r.status_code != 404:
        webbrowser.open(line, new=2, autoraise=True)
        gui.message_box = False
    else:
        line = "https://genius.com/search?q=" + urllib.parse.quote(f"{s_artist} {s_title}")
        webbrowser.open(line, new=2, autoraise=True)
        gui.message_box = False


def ser_gen(track_id, get_lyrics=False):
    tr = pctl.master_library[track_id]
    if len(tr.title) < 1:
        return

    show_message(_("Searching..."))

    shoot = threading.Thread(target=ser_gen_thread, args=[tr])
    shoot.daemon = True
    shoot.start()


def ser_wiki(index):
    if len(pctl.master_library[index].artist) < 2:
        return
    line = "http://en.wikipedia.org/wiki/Special:Search?search=" + \
           urllib.parse.quote(pctl.master_library[index].artist)
    webbrowser.open(line, new=2, autoraise=True)


track_menu.add(_('Search Artist on Wikipedia'), ser_wiki, pass_ref=True, show_test=toggle_wiki)

track_menu.add(_('Search Track on Genius'), ser_gen, pass_ref=True, show_test=toggle_gen)

son_icon = MenuIcon(asset_loader('sonemic-g.png'))
son_icon.base_asset = asset_loader('sonemic-gs.png')

son_icon.xoff = 1
track_menu.add(_('Search Artist on Sonemic'), ser_rym, pass_ref=True, icon=son_icon, show_test=toggle_rym)

band_icon = MenuIcon(asset_loader('band.png', True))
band_icon.xoff = 0
band_icon.yoff = 1
band_icon.colour = [96, 147, 158, 255]

track_menu.add(_('Search Artist on Bandcamp'), ser_band, pass_ref=True, icon=band_icon, show_test=toggle_band)


def clip_ar_tr(index):
    line = pctl.master_library[index].artist + " - " + \
           pctl.master_library[index].title

    SDL_SetClipboardText(line.encode('utf-8'))

# Copy metadata to clipboard
#track_menu.add(_('Copy "Artist - Album"'), clip_aar_al, pass_ref=True)
# Copy metadata to clipboard
track_menu.add(_('Copy "Artist - Track"'), clip_ar_tr, pass_ref=True)


def get_track_spot_url_show_test(_):

    if pctl.g(r_menu_index).misc.get("spotify-track-url"):
        return True
    return False

def get_track_spot_url(track_id):
    track_object = pctl.g(track_id)
    url = track_object.misc.get("spotify-track-url")
    if url:
        copy_to_clipboard(url)
        show_message("Url copied to clipboard", mode="done")
    else:
        show_message("No results found")

track_menu.add(_('Copy Spotify Track URL'), get_track_spot_url, pass_ref=True, show_test=get_track_spot_url_show_test, icon=spot_icon)


def drop_tracks_to_new_playlist(track_list, hidden=False):

    pl = new_playlist(switch=False)
    albums = []
    artists = []
    for item in track_list:
        albums.append(pctl.g(default_playlist[item]).album)
        artists.append(pctl.g(default_playlist[item]).artist)
        pctl.multi_playlist[pl][2].append(default_playlist[item])


    if len(track_list) > 1:
        if len(albums) > 0 and albums.count(albums[0]) == len(albums):
            track = pctl.g(default_playlist[track_list[0]])
            artist = track.artist
            if track.album_artist != "":
                artist = track.album_artist
            pctl.multi_playlist[pl][0] = artist + " - " + albums[0][:50]

    elif len(track_list) == 1 and artists:
        pctl.multi_playlist[pl][0] = artists[0]

    if tree_view_box.dragging_name:
        pctl.multi_playlist[pl][0] = tree_view_box.dragging_name

    pctl.notify_change()




def queue_deco():
    if len(pctl.force_queue) > 0:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled

    return [line_colour, colours.menu_background, None]


# def broadcast_feature_deco():
#     return menu_standard_or_grey(pctl.broadcast_active)


def broadcast_select_track(track_id):

    if shutil.which(ffmpeg) is None:
        show_message(_("FFmpeg does not appear to be installed"), mode="error")
        return

    pctl.broadcast_index = track_id
    track = pctl.g(track_id)

    pctl.broadcast_playlist = copy.deepcopy(pctl.multi_playlist[pctl.active_playlist_viewing][6])
    pctl.broadcast_position = default_playlist.index(pctl.broadcast_index)
    pctl.broadcast_time = 0
    pctl.target_open = track.fullpath
    pctl.b_start_time = track.start_time
    pctl.broadcast_line = track.artist + " - " + \
                          track.title

    if pctl.broadcast_active:
        pctl.broadcastCommand = "cast-next"
        pctl.broadcastCommandReady = True
    else:
        pctl.broadcast_update_train.clear()
        pctl.broadcastCommand = "encstart"
        pctl.broadcastCommandReady = True
    tm.ready("caster")

tauon.broadcast_select_track = broadcast_select_track

track_menu.br()
track_menu.add(_('Transcode Folder'), convert_folder, transcode_deco, pass_ref=True, icon=transcode_icon, show_test=toggle_transcode)


def bass_test(_):
    # return True
    return prefs.backend == 1

def gstreamer_test(_):
    # return True
    return prefs.backend == 2

def chromecast_select_track(track_id):
    gui.delay_frame(8)

    if not prefs.enable_web:
        show_message('Please first enable the setting "Ready Broadcaster" in MENU > settings > function')
        return
    if not chrome:
        show_message("pychromecast not found")
        return
    if pctl.broadcast_active:
        broadcast_select_track(track_id)
        return

    shooter(chrome.one, [pl_to_id(pctl.active_playlist_viewing), track_id])

track_menu.add(_('Broadcast This'), broadcast_select_track, pass_ref=True)
track_menu.add(_('Chromecast This'), chromecast_select_track, pass_ref=True, show_test=toggle_chromecast)

# Create top menu
x_menu = Menu(190, show_icons=True)
view_menu = Menu(170)
set_menu = Menu(150)
set_menu_hidden = Menu(100)
vis_menu = Menu(140)
field_menu = Menu(140)
dl_menu = Menu(90)

def field_copy(text_field):
    text_field.copy()



def field_paste(text_field):
    text_field.paste()

def field_clear(text_field):
    text_field.clear()

# Copy text
field_menu.add(_("Copy"), field_copy, pass_ref=True)
# Paste text
field_menu.add(_("Paste"), field_paste, pass_ref=True)
# Clear text
field_menu.add(_("Clear"), field_clear, pass_ref=True)


def vis_off():
    gui.vis_want = 0
    gui.update_layout()
    # gui.turbo = False

vis_menu.add(_("Off"), vis_off)

def level_on():
    if gui.vis_want == 1 and gui.turbo is True:
        gui.level_meter_colour_mode += 1
        if gui.level_meter_colour_mode > 4:
            gui.level_meter_colour_mode = 0

    gui.vis_want = 1
    gui.update_layout()
    # if prefs.backend == 2:
    #     show_message("Visualisers not implemented in GStreamer mode")
    #gui.turbo = True
vis_menu.add(_("Level Meter"), level_on)


def spec_on():
    gui.vis_want = 2
    # if prefs.backend == 2:
    #     show_message("Not implemented")
    gui.update_layout()

vis_menu.add(_("Spectrum Visualizer"), spec_on)


def spec2_def():

    if gui.vis_want == 3:
        prefs.spec2_colour_mode += 1
        if prefs.spec2_colour_mode > 1:
            prefs.spec2_colour_mode = 0

    gui.vis_want = 3
    if prefs.backend == 2:
        show_message("Not implemented")
    #gui.turbo = True
    prefs.spec2_colour_setting = 'custom'
    gui.update_layout()

# vis_menu.add(_("Spectrogram"), spec2_def)

def sa_remove(h):
    if len(gui.pl_st) > 1:
        del gui.pl_st[h]
        gui.update_layout()
    else:
        show_message("Cannot remove the only column.")

def sa_artist():
    gui.pl_st.insert(set_menu.reference + 1, ["Artist", 220, False])
    gui.update_layout()
def sa_album_artist():
    gui.pl_st.insert(set_menu.reference + 1, ["Album Artist", 220, False])
    gui.update_layout()
def sa_composer():
    gui.pl_st.insert(set_menu.reference + 1, ["Composer", 220, False])
    gui.update_layout()
def sa_title():
    gui.pl_st.insert(set_menu.reference + 1, ["Title", 220, False])
    gui.update_layout()
def sa_album():
    gui.pl_st.insert(set_menu.reference + 1, ["Album", 220, False])
    gui.update_layout()
def sa_comment():
    gui.pl_st.insert(set_menu.reference + 1, ["Comment", 300, False])
    gui.update_layout()
def sa_track():
    gui.pl_st.insert(set_menu.reference + 1, ["#", 25, True])
    gui.update_layout()
def sa_count():
    gui.pl_st.insert(set_menu.reference + 1, ["P", 25, True])
    gui.update_layout()
def sa_scrobbles():
    gui.pl_st.insert(set_menu.reference + 1, ["S", 25, True])
    gui.update_layout()
def sa_time():
    gui.pl_st.insert(set_menu.reference + 1, ["Time", 55, True])
    gui.update_layout()
def sa_date():
    gui.pl_st.insert(set_menu.reference + 1, ["Date", 55, True])
    gui.update_layout()
def sa_genre():
    gui.pl_st.insert(set_menu.reference + 1, ["Genre", 150, False])
    gui.update_layout()
def sa_file():
    gui.pl_st.insert(set_menu.reference + 1, ["Filepath", 350, False])
    gui.update_layout()
def sa_filename():
    gui.pl_st.insert(set_menu.reference + 1, ["Filename", 300, False])
    gui.update_layout()
def sa_codec():
    gui.pl_st.insert(set_menu.reference + 1, ["Codec", 65, True])
    gui.update_layout()
def sa_bitrate():
    gui.pl_st.insert(set_menu.reference + 1, ["Bitrate", 65, True])
    gui.update_layout()
def sa_lyrics():
    gui.pl_st.insert(set_menu.reference + 1, ["Lyrics", 50, True])
    gui.update_layout()
def sa_star():
    gui.pl_st.insert(set_menu.reference + 1, ["Starline", 80, True])
    gui.update_layout()
def sa_rating():
    gui.pl_st.insert(set_menu.reference + 1, ["Rating", 80, True])
    gui.update_layout()
def sa_love():
    gui.pl_st.insert(set_menu.reference + 1, ["❤", 25, True])
    #gui.pl_st.append(["❤", 25, True])
    gui.update_layout()

def key_love(index):
    return get_love_index(index)

def key_artist(index):
    return pctl.master_library[index].artist.lower()

def key_album_artist(index):
    return pctl.master_library[index].album_artist.lower()

def key_composer(index):
    return pctl.master_library[index].composer.lower()

def key_comment(index):
    return pctl.master_library[index].comment

def key_title(index):
    return pctl.master_library[index].title.lower()

def key_album(index):
    return pctl.master_library[index].album.lower()

def key_duration(index):
    return pctl.master_library[index].length

def key_date(index):
    return pctl.master_library[index].date

def key_genre(index):
    return pctl.master_library[index].genre.lower()

def key_t(index):
    #return str(pctl.master_library[index].track_number)
    return index_key(index)

def key_codec(index):
    return pctl.master_library[index].file_ext

def key_bitrate(index):
    return pctl.master_library[index].bitrate

def key_p(index):
    return pctl.master_library[index].bitrate

def key_hl(index):
    if len(pctl.master_library[index].lyrics) > 5:
        return 0
    else:
        return 1

def sort_ass(h, invert=False, custom_list=None, custom_name=""):
    global default_playlist

    if custom_list is None:
        if pl_is_locked(pctl.active_playlist_viewing):
            show_message("Playlist is locked")
            return

        name = gui.pl_st[h][0]
        playlist = pctl.multi_playlist[pctl.active_playlist_viewing][2]
    else:
        name = custom_name
        playlist = custom_list

    key = None

    if name == "Filepath":
        key = key_filepath
        # if use_natsort:
        #     key = natsort.natsort_keygen()
    if name == "Filename":
        key = key_filename
        if use_natsort:
            key = natsort.natsort_keygen()
    if name == "Artist":
        key = key_artist
    if name == "Album Artist":
        key = key_album_artist
    if name == "Title":
        key = key_title
    if name == "Album":
        key = key_album
    if name == "Composer":
        key = key_composer
    if name == "Time":
        key = key_duration
    if name == "Date":
        key = key_date
    if name == "Genre":
        key = key_genre
    if name == "#":
        key = key_t
    if name == "S":
        key = key_scrobbles
    if name == "P":
        key = key_playcount
    if name == 'Starline':
        key = best
    if name == 'Rating':
        key = key_rating
    if name == 'Comment':
        key = key_comment
    if name == "Codec":
        key = key_codec
    if name == "Bitrate":
        key = key_bitrate
    if name == "Lyrics":
        key = key_hl
    if name == "❤":
        key = key_love

    if custom_list is None:
        if key is not None:

            playlist.sort(key=key, reverse=invert)

            pctl.multi_playlist[pctl.active_playlist_viewing][2] = playlist
            default_playlist = pctl.multi_playlist[pctl.active_playlist_viewing][2]

            pctl.playlist_view_position = 0
            console.print("DEBUG: Position changed by sort")
            gui.pl_update = 1

    elif custom_list is not None:
            playlist.sort(key=key, reverse=invert)

    reload()


def sort_dec(h):
    sort_ass(h, True)


def hide_set_bar():
    gui.set_bar = False
    gui.update_layout()
    gui.pl_update = 1

def show_set_bar():
    gui.set_bar = True
    gui.update_layout()
    gui.pl_update = 1


# Mark for translation
_("Time")
_("Filepath")

#
# set_menu.add(_("Sort Ascending"), sort_ass, pass_ref=True, disable_test=view_pl_is_locked, pass_ref_deco=True)
# set_menu.add(_("Sort Decending"), sort_dec, pass_ref=True, disable_test=view_pl_is_locked, pass_ref_deco=True)
# set_menu.br()
set_menu.add(_("Auto Resize"), auto_size_columns)
set_menu.add(_("Hide bar"), hide_set_bar)
set_menu_hidden.add(_("Show bar"), show_set_bar)
set_menu.br()
set_menu.add("- " + _("Remove This"), sa_remove, pass_ref=True)
set_menu.br()
set_menu.add("+ " + _("Artist"), sa_artist)
set_menu.add("+ " + _("Title"), sa_title)
set_menu.add("+ " + _("Album"), sa_album)
set_menu.add("+ " + _("Album Artist"), sa_album_artist)
set_menu.add("+ " + _("Composer"), sa_composer)
set_menu.add("+ " + _("Duration"), sa_time)
set_menu.add("+ " + _("Date"), sa_date)
set_menu.add("+ " + _("Genre"), sa_genre)
set_menu.add("+ " + _("Track Number"), sa_track)
set_menu.add("+ " + _("Play Count"), sa_count)
set_menu.add("+ " + _("Scrobble Count"), sa_scrobbles)
set_menu.add("+ " + _("Codec"), sa_codec)
set_menu.add("+ " + _("Bitrate"), sa_bitrate)
set_menu.add("+ " + _("Has Lyrics"), sa_lyrics)
set_menu.add("+ " + _("Comment"), sa_comment)
set_menu.add("+ " + _("Filepath"), sa_file)
set_menu.add("+ " + _("Filename"), sa_filename)
set_menu.add("+ " + _("Starline"), sa_star)
set_menu.add("+ " + _("Rating"), sa_rating)
set_menu.add("+ " + _("Loved"), sa_love)



def bass_features_deco():
    line_colour = colours.menu_text
    if prefs.backend != 1:
        line_colour = colours.menu_text_disabled
    return [line_colour, colours.menu_background, None]


def toggle_dim_albums(mode=0):
    if mode == 1:
        return prefs.dim_art

    prefs.dim_art ^= True
    gui.pl_update = 1
    gui.update += 1


def toggle_gallery_click(mode=0):
    if mode == 1:
        return prefs.gallery_single_click

    prefs.gallery_single_click ^= True


def toggle_gallery_thin(mode=0):
    if mode == 1:
        return prefs.thin_gallery_borders

    prefs.thin_gallery_borders ^= True
    gui.update += 1
    update_layout_do()

def toggle_gallery_row_space(mode=0):
    if mode == 1:
        return prefs.increase_gallery_row_spacing

    prefs.increase_gallery_row_spacing ^= True
    gui.update += 1
    update_layout_do()



def toggle_galler_text(mode=0):
    if mode == 1:
        return gui.gallery_show_text

    gui.gallery_show_text ^= True
    gui.update += 1
    update_layout_do()

    # Jump to playing album
    if album_mode and gui.first_in_grid is not None:

        if gui.first_in_grid < len(default_playlist):
            goto_album(gui.first_in_grid, force=True)

def toggle_card_style(mode=0):
    if mode == 1:
        return prefs.use_card_style

    prefs.use_card_style ^= True
    gui.update += 1


def toggle_side_panel(mode=0):
    global update_layout
    global album_mode

    if mode == 1:
        return prefs.prefer_side

    prefs.prefer_side ^= True
    update_layout = True

    if album_mode:
        gui.rsp = True
    elif prefs.prefer_side is True:
        gui.rsp = True
    else:
        gui.rsp = False

    if prefs.prefer_side:
        gui.rspw = gui.pref_rspw

def force_album_view():
    toggle_album_mode(True)

def enter_combo():
    if not gui.combo_mode:
        gui.combo_was_album = album_mode
        gui.showcase_mode = False
        gui.radio_view = False
        if album_mode:
            toggle_album_mode()
        if gui.rsp:
            gui.rsp = False
        gui.combo_mode = True
        gui.update_layout()



def exit_combo(restore=False):
    if gui.combo_mode:
        if gui.combo_was_album and restore:
            force_album_view()
        gui.showcase_mode = False
        gui.radio_view = False
        if prefs.prefer_side:
            gui.rsp = True
        gui.update_layout()
        gui.combo_mode = False


def enter_showcase_view(track_id=None):
    if not gui.combo_mode:
        enter_combo()
    gui.showcase_mode = True
    gui.radio_view = False
    if track_id is None or pctl.playing_object() is None or pctl.playing_object().index == track_id:
        pass
    else:
        gui.force_showcase_index = track_id
    inp.mouse_click = False
    gui.update_layout()


def enter_radio_view():
    if not gui.combo_mode:
        enter_combo()
    gui.showcase_mode = False
    gui.radio_view = True
    inp.mouse_click = False
    gui.update_layout()


def standard_size():
    global album_mode
    global window_size
    global update_layout

    global album_mode_art_size

    album_mode = False
    gui.rsp = True
    window_size = window_default_size
    SDL_SetWindowSize(t_window, window_size[0], window_size[1])

    gui.rspw = 80 + int(window_size[0] * 0.18)
    update_layout = True
    album_mode_art_size = 130
    #clear_img_cache()


def path_stem_to_playlist(path, title):  # Used with gallery power bar

    playlist = []

    # Hack for networked tracks
    if path.lstrip("/") == title:
        for item in pctl.multi_playlist[pctl.active_playlist_viewing][2]:
            if title == os.path.basename(pctl.master_library[item].parent_folder_path):
                    playlist.append(item)

    else:
        for item in pctl.multi_playlist[pctl.active_playlist_viewing][2]:
            if path in pctl.master_library[item].parent_folder_path:
                    playlist.append(item)

    pctl.multi_playlist.append(pl_gen(title=os.path.basename(title).upper(),
                                      playlist=copy.deepcopy(playlist),
                                      hide_title=0))

    pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[pctl.active_playlist_viewing][0] + "\" f\"" + path + "\""

    switch_playlist(len(pctl.multi_playlist) - 1)


def goto_album(playlist_no, down=False, force=False):

    console.print("DEBUG: Postion set by album locate")

    if core_timer.get() < 0.5:
        return

    global album_dex

    # ----
    w = gui.rspw
    if window_size[0] < 750 * gui.scale:
        w = window_size[0] - 20 * gui.scale
        if gui.lsp:
            w -= gui.lspw
    area_x = w + 38 * gui.scale
    row_len = int((area_x - album_h_gap) / (album_mode_art_size + album_h_gap))
    global last_row
    last_row = row_len
    # ----

    px = 0
    row = 0
    re = 0

    for i in range(len(album_dex)):
        if i == len(album_dex) - 1:
            re = i
            break
        if album_dex[i + 1] - 1 > playlist_no - 1:
            re = i
            break
        row += 1
        if row > row_len - 1:
            row = 0
            px += album_mode_art_size + album_v_gap

    # If the album is within the view port already, dont jump to it
    # (unless we really want to with force)
    if not force and gui.album_scroll_px + album_v_slide_value < px < gui.album_scroll_px + window_size[1]:

        # Dont chance the view since its alread in the view port
        # But if the album is just out of view on the bottom, bring it into view on to bottom row
        if window_size[1] > (album_mode_art_size + album_v_gap) * 2:
            while not gui.album_scroll_px - 20 < px + (album_mode_art_size + album_v_gap + 3) < gui.album_scroll_px + window_size[
                1] - 40:
                gui.album_scroll_px += 1

    else:
        # Set the view to the calculated position
        gui.album_scroll_px = px
        gui.album_scroll_px -= album_v_slide_value

        if gui.album_scroll_px < 0 - album_v_slide_value:
            gui.album_scroll_px = 0 - album_v_slide_value

    if len(album_dex) > 0:
        return album_dex[re]
    else:
        return 0

    gui.update += 1

def toggle_album_mode(force_on=False):
    global album_mode
    global window_size
    global update_layout
    global album_playlist_width
    global old_album_pos

    gui.gall_tab_enter = False

    if album_mode is True:

        album_mode = False
        #album_playlist_width = gui.playlist_width
        #old_album_pos = gui.album_scroll_px
        gui.rspw = gui.pref_rspw
        gui.rsp = prefs.prefer_side
        gui.album_tab_mode = False
    else:
        album_mode = True
        if gui.combo_mode:
            exit_combo()

        gui.rsp = True

        gui.rspw = gui.pref_gallery_w

    space = window_size[0] - gui.rspw
    if gui.lsp:
        space -= gui.lspw

    if album_mode and gui.set_mode and len(gui.pl_st) > 6 and space < 600 * gui.scale:
        gui.set_mode = False
        gui.pl_update = True
        gui.update_layout()

    reload_albums(quiet=True)

    #if pctl.active_playlist_playing == pctl.active_playlist_viewing:
        #goto_album(pctl.playlist_playing_position)

    if album_mode:
        if playlist_selected < len(pctl.playing_playlist()):
            goto_album(playlist_selected)

def toggle_gallery_keycontrol(always_exit=False):
    if is_level_zero():
        if not album_mode:
            toggle_album_mode()
            gui.gall_tab_enter = True
            gui.album_tab_mode = True
            show_in_gal(playlist_selected, silent=True)
        elif gui.gall_tab_enter or always_exit:
            # Exit gallery and tab mode
            toggle_album_mode()
        else:
            gui.album_tab_mode ^= True
            if gui.album_tab_mode:
                show_in_gal(playlist_selected, silent=True)

def check_auto_update_okay(code, pl=None):
    try:
        cmds = shlex.split(code)
    except:
        print("Malformed generator code!")
        return False
    return "auto" in cmds or (prefs.always_auto_update_playlists and
                          pctl.active_playlist_playing != pl and
                          not "sf" in cmds and
                          not "rf" in cmds and
                          not "ra" in cmds and
                          not "sa" in cmds and
                          not "st" in cmds and
                          not "rt" in cmds and
                          not "plex" in cmds and
                          not "jelly" in cmds and
                          not "koel" in cmds and
                          not "air" in cmds and
                          not "sal" in cmds and
                          not "slt" in cmds and
                          not "spl\"" in code and
                          not "r" in cmds)

def switch_playlist(number, cycle=False, quiet=False):
    global default_playlist

    global playlist_selected
    global search_index
    global shift_selection

    # Close any active menus
    # for instance in Menu.instances:
    #     instance.active = False
    close_all_menus()
    if gui.radio_view:
        if cycle:
            pctl.radio_playlist_viewing += number
        else:
            pctl.radio_playlist_viewing = number
        if pctl.radio_playlist_viewing > len(pctl.radio_playlists) - 1:
            pctl.radio_playlist_viewing = 0
        return

    gui.previous_playlist_id = pctl.multi_playlist[pctl.active_playlist_viewing][6]

    gui.pl_update = 1
    search_index = 0
    gui.column_d_click_on = -1
    gui.search_error = False
    if quick_search_mode:
        gui.force_search = True

    # if pl_follow:
    #     pctl.multi_playlist[pctl.playlist_active][1] = copy.deepcopy(pctl.playlist_playing)

    if gui.showcase_mode and gui.combo_mode and not quiet:
        view_standard()

    pctl.multi_playlist[pctl.active_playlist_viewing][2] = default_playlist
    pctl.multi_playlist[pctl.active_playlist_viewing][3] = pctl.playlist_view_position
    pctl.multi_playlist[pctl.active_playlist_viewing][5] = playlist_selected

    if gall_pl_switch_timer.get() > 240:
        gui.gallery_positions.clear()
    gall_pl_switch_timer.set()

    gui.gallery_positions[gui.previous_playlist_id] = gui.album_scroll_px

    if cycle:
        pctl.active_playlist_viewing += number
    else:
        pctl.active_playlist_viewing = number

    while pctl.active_playlist_viewing > len(pctl.multi_playlist) - 1:
        pctl.active_playlist_viewing -= len(pctl.multi_playlist)
    while pctl.active_playlist_viewing < 0:
        pctl.active_playlist_viewing += len(pctl.multi_playlist)

    default_playlist = pctl.multi_playlist[pctl.active_playlist_viewing][2]
    pctl.playlist_view_position = pctl.multi_playlist[pctl.active_playlist_viewing][3]
    playlist_selected = pctl.multi_playlist[pctl.active_playlist_viewing][5]
    console.print("DEBUG: Position changed by playlist change")
    shift_selection = [playlist_selected]

    id = pctl.multi_playlist[pctl.active_playlist_viewing][6]

    code = pctl.gen_codes.get(id)
    if code is not None and check_auto_update_okay(code, pctl.active_playlist_viewing):
        gui.regen_single_id = id
        tm.ready("worker")

    if album_mode:
        reload_albums(True)
        if id in gui.gallery_positions:
            gui.album_scroll_px = gui.gallery_positions[id]
        else:
            goto_album(pctl.playlist_view_position)

    if prefs.auto_goto_playing:
        pctl.show_current(this_only=True, playing=False, highlight=True, no_switch=True)

    if prefs.shuffle_lock:
        view_box.lyrics(hit=True)
        if pctl.active_playlist_viewing:
            pctl.active_playlist_playing = pctl.active_playlist_viewing
            random_track()

def cycle_playlist_pinned(step):

    if gui.radio_view:

        pctl.radio_playlist_viewing += step * -1
        if pctl.radio_playlist_viewing > len(pctl.radio_playlists) - 1:
            pctl.radio_playlist_viewing = 0
        if pctl.radio_playlist_viewing < 0:
            pctl.radio_playlist_viewing = len(pctl.radio_playlists) - 1
        return

    if step > 0:
        p = pctl.active_playlist_viewing
        le = len(pctl.multi_playlist)
        on = p
        on -= 1
        while True:
            if on < 0:
                on = le - 1
            if on == p:
                break
            if pctl.multi_playlist[on][8] is False or not prefs.tabs_on_top or (gui.lsp and prefs.left_panel_mode == "playlist"):
                switch_playlist(on)
                break
            on -= 1

    elif step < 0:
        p = pctl.active_playlist_viewing
        le = len(pctl.multi_playlist)
        on = p
        on += 1
        while True:
            if on == le:
                on = 0
            if on == p:
                break
            if pctl.multi_playlist[on][8] is False or not prefs.tabs_on_top or (gui.lsp and prefs.left_panel_mode == "playlist"):
                switch_playlist(on)
                break
            on += 1


def activate_info_box():
    fader.rise()
    pref_box.enabled = True


def activate_radio_box():
    radiobox.active = True
    radiobox.radio_field.clear()
    radiobox.radio_field_title.clear()

def new_playlist_colour_callback():

    if gui.radio_view:
        return [120, 90, 245, 255]
    else:
        return [237, 80, 221, 255]

add_icon.xoff = 3
add_icon.yoff = 0
add_icon.colour = [237, 80, 221, 255]
add_icon.colour_callback = new_playlist_colour_callback

def new_playlist_deco():
    if gui.radio_view:
        text = _("New Radio List")
    else:
        text = _("New Playlist")
    return [colours.menu_text, colours.menu_background, text]

x_menu.add(_("New Playlist"), new_playlist, new_playlist_deco,  icon=add_icon)

def clean_db_show_test(_):
    return gui.suggest_clean_db

def clean_db_fast():
    keys = set(pctl.master_library.keys())
    for pl in pctl.multi_playlist:
        keys -= set(pl[2])
    for item in keys:
        pctl.purge_track(item, fast=True)
    gui.show_message(f"Done! {len(keys)} old items were removed.", mode="done")
    gui.suggest_clean_db = False

def clean_db_deco():
    return [colours.menu_text, [30, 150, 120, 255], _("Clean Database")]

x_menu.add(_("Clean Database"), clean_db_fast, clean_db_deco, show_test=clean_db_show_test)


# x_menu.add(_("Internet Radio…"), activate_radio_box)

tauon.switch_playlist = switch_playlist

def import_spotify_playlist():
    clip = copy_from_clipboard()
    for line in clip.split("\n"):
        if line.startswith("https://open.spotify.com/playlist/") or line.startswith("spotify:playlist:"):
            clip = clip.strip()
            spot_ctl.playlist(line)

    if album_mode:
        reload_albums()
    gui.pl_update += 1

def import_spotify_playlist_deco():
    clip = copy_from_clipboard()
    if clip.startswith("https://open.spotify.com/playlist/") or clip.startswith("spotify:playlist:"):
        return [colours.menu_text, colours.menu_background, None]
    return [colours.menu_text_disabled, colours.menu_background, None]

x_menu.add(_("Paste Spotify Playlist"), import_spotify_playlist, import_spotify_playlist_deco, icon=spot_icon, show_test=spotify_show_test)


def show_import_music(_):
    return gui.add_music_folder_ready

def import_music():

    pl = pl_gen(_("Music"))
    pl[7] = [music_directory]
    pctl.multi_playlist.append(pl)
    load_order = LoadClass()
    load_order.target = music_directory
    load_order.playlist = pl[6]
    load_orders.append(load_order)
    switch_playlist(len(pctl.multi_playlist) - 1)
    gui.add_music_folder_ready = False

x_menu.add(_("Import Music Folder"), import_music, show_test=show_import_music)

x_menu.br()

settings_icon.xoff = 0
settings_icon.yoff = 2
settings_icon.colour = [232, 200, 96, 255]#[230, 152, 118, 255]#[173, 255, 47, 255] #[198, 237, 56, 255]
#settings_icon.colour = [180, 140, 255, 255]
x_menu.add(_("Settings"), activate_info_box, icon=settings_icon)
x_menu.add_sub(_("Database…"), 190)
x_menu.br()

# x_menu.add('Toggle Side panel', toggle_combo_view, combo_deco)

def stt2(sec):
    days, rem = divmod(sec, 86400)
    hours, rem = divmod(rem, 3600)
    min, sec = divmod(rem, 60)

    s_day = str(days) + 'd'
    if s_day == '0d':
        s_day = "  "

    s_hours = str(hours) + 'h'
    if s_hours == '0h' and s_day == '  ':
        s_hours = "  "

    s_min = str(min) + 'm'

    return s_day.rjust(3) + ' ' + s_hours.rjust(3) + ' ' + s_min.rjust(3)


def export_database():

    path = user_directory + '/DatabaseExport.csv'
    xport = open(path, 'w')

    xport.write("Artist;Title;Album;Album artist;Track number;Type;Duration;Release date;Genre;Playtime;File path")

    for index, track in pctl.master_library.items():

        xport.write("\n")

        xport.write(csv_string(track.artist) + ",")
        xport.write(csv_string(track.title) + ",")
        xport.write(csv_string(track.album) + ",")
        xport.write(csv_string(track.album_artist) + ",")
        xport.write(csv_string(track.track_number) + ",")
        type = "File"
        if track.is_network:
            type = "Network"
        elif track.is_cue:
            type = "CUE File"
        xport.write(type + ",")
        xport.write(str(track.length) + ",")
        xport.write(csv_string(track.date) + ",")
        xport.write(csv_string(track.genre) + ",")
        xport.write(str(int(star_store.get_by_object(track))) + ",")
        xport.write(csv_string(track.fullpath))

    xport.close()
    show_message("Export complete.", "Saved as: " + path, mode='done')


def q_to_playlist():

    pctl.multi_playlist.append(pl_gen(title="Play History",
                                      playing=0,
                                      playlist=list(reversed(copy.deepcopy(pctl.track_queue))),
                                      position=0,
                                      hide_title=1,
                                      selected=0))



x_menu.add_to_sub(_("Export as CSV"), 0, export_database)
x_menu.add_to_sub(_("Play History to Playlist"), 0, q_to_playlist)
x_menu.add_to_sub(_("Reset Image Cache"), 0, clear_img_cache)


cm_clean_db = False

def clean_db():
    global cm_clean_db
    prefs.remove_network_tracks = False
    cm_clean_db = True
    tm.ready("worker")

def clean_db2():
    global cm_clean_db
    prefs.remove_network_tracks = True
    cm_clean_db = True
    tm.ready("worker")

x_menu.add_to_sub(_("Remove Network Tracks"), 0, clean_db2)
x_menu.add_to_sub(_("Remove Missing Tracks"), 0, clean_db)

# x_menu.add_to_sub(_("Mark Missing as Found"), 0, reset_missing_flags)


def import_fmps():
    unique = set()
    for playlist in pctl.multi_playlist:
        for id in playlist[2]:
            tr = pctl.g(id)
            if "FMPS_Rating" in tr.misc:
                rating = round(tr.misc["FMPS_Rating"] * 10)
                star_store.set_rating(tr.index, rating)
                unique.add(tr.index)

    show_message(str(len(unique)) + " ratings imported", mode="done")

    gui.pl_update += 1

x_menu.add_to_sub(_("Import FMPS_Ratings from Tags"), 0, import_fmps)


def clear_ratings():
    if not key_shift_down:
        show_message(_("This will delete all track and album ratings from the local database!"),
                     _("Press button again while holding shift key if you're sure you want to do that."), mode='warning')
        return
    else:
        for key, star in star_store.db.items():
            star[2] = 0
        album_star_store.db.clear()
    gui.pl_update += 1

x_menu.add_to_sub(_("Reset User Ratings"), 0, clear_ratings)

def find_incomplete():
    gen_incomplete(pctl.active_playlist_viewing)

x_menu.add_to_sub(_("Find Incomplete Albums"), 0, find_incomplete)

def toggle_broadcast():

    if pctl.broadcast_active is not True:
        if len(default_playlist) == 0:
            show_message(_("There are no tracks in this playlist to broadcast."), mode='error')
            return 0
        if not prefs.enable_web:
            show_message(_("You need to start the broadcast web server in settings."))
            return
        pctl.broadcast_playlist = copy.deepcopy(pctl.multi_playlist[pctl.active_playlist_viewing][6])
        pctl.broadcast_position = -1

        pctl.advance_broadcast(start=True)

    else:
        # if key_shift_down:
        #     pctl.broadcastCommand = "encpause"
        #     pctl.broadcastCommandReady = True
        #     return
        if chrome:
            chrome.stop()
        pctl.broadcastCommand = "encstop"
        pctl.broadcastCommandReady = True



def broadcast_deco():
    line_colour = colours.menu_text
    if pctl.broadcast_active:
        # if key_shift_down:
        #     return [line_colour, colours.menu_background, _("Pause Broadcast")]
        return [line_colour, colours.menu_background, _("Stop Broadcast")] # [24, 25, 60, 255]
    return [line_colour, colours.menu_background, None]


def broadcast_colour():
    if pctl.broadcast_active:
        return [56, 189, 237, 255]
    else:
        if colours.lm:
            return [171, 102, 249, 255]
        return None


broadcast_icon = MenuIcon(asset_loader('broadcast.png', True))
broadcast_icon.colour = [171, 102, 249, 255]
broadcast_icon.colour_callback = broadcast_colour
x_menu.add(_("Start Broadcast"), toggle_broadcast, broadcast_deco, icon=broadcast_icon)


def clear_queue():
    pctl.force_queue = []
    gui.pl_update = 1
    pctl.pause_queue = False


mode_menu = Menu(175)

def set_mini_mode_A1():
    prefs.mini_mode_mode = 0
    set_mini_mode()

def set_mini_mode_B1():
    prefs.mini_mode_mode = 1
    set_mini_mode()

def set_mini_mode_A2():
    prefs.mini_mode_mode = 2
    set_mini_mode()

def set_mini_mode_B2():
    prefs.mini_mode_mode = 3
    set_mini_mode()

def set_mini_mode_D():
    prefs.mini_mode_mode = 4
    set_mini_mode()

mode_menu.add(_('Mini'), set_mini_mode_A1)
#mode_menu.add(_('Mini Mode Large'), set_mini_mode_A2)
mode_menu.add(_('Square'), set_mini_mode_B1)
mode_menu.add(_('Square Large'), set_mini_mode_B2)
mode_menu.add(_('Micro'), set_mini_mode_D)


def copy_bb_metadata():
    tr = pctl.playing_object()
    if not tr.title and not tr.artist and pctl.playing_state == 3:
        return pctl.tag_meta
    text = f"{tr.artist} - {tr.title}".strip(" -")
    if text:
        copy_to_clipboard(text)
    else:
        show_message(_("No metadata available to copy"))

mode_menu.br()
mode_menu.add(_('Copy Title to Clipboard'), copy_bb_metadata)

extra_menu = Menu(175, show_icons=True)


def stop():
    pctl.stop()


def random_track():
    playlist = pctl.multi_playlist[pctl.active_playlist_playing][2]
    if playlist:
        random_position = random.randrange(0, len(playlist))
        track_id = playlist[random_position]
        pctl.jump(track_id, random_position)
        pctl.show_current()


extra_menu.add(_('Random Track'), random_track, hint=';')

def random_album():
    folders = {}
    playlist = pctl.multi_playlist[pctl.active_playlist_playing][2]
    if playlist:
        for i, id in enumerate(playlist):
            track = pctl.g(id)
            if track.parent_folder_path not in folders:
                folders[track.parent_folder_path] = (id, i)

        key = random.choice(list(folders.keys()))
        result = folders[key]
        pctl.jump(*result)
        pctl.show_current()

def radio_random():
    pctl.advance(rr=True)


radiorandom_icon = MenuIcon(asset_loader('radiorandom.png', True))
revert_icon = MenuIcon(asset_loader('revert.png', True))

radiorandom_icon.xoff = 1
radiorandom_icon.yoff = 0
radiorandom_icon.colour = [153, 229, 133, 255]
extra_menu.add(_('Radio Random'), radio_random, hint='/', icon=radiorandom_icon)


revert_icon.xoff = 1
revert_icon.yoff = 0
revert_icon.colour = [229, 102, 59, 255]
extra_menu.add(_('Revert'), pctl.revert, hint='Shift+/', icon=revert_icon)


# extra_menu.add('Toggle Repeat', toggle_repeat, hint='COMMA')



# extra_menu.add('Toggle Random', toggle_random, hint='PERIOD')
extra_menu.add(_('Clear Queue'), clear_queue, queue_deco, hint="Alt+Shift+Q")

def heart_menu_colour():
    if not (pctl.playing_state == 1 or pctl.playing_state == 2):
        if colours.lm:
            return [255, 150, 180, 255]
        return None
    if love(False):
        return [245, 60, 60, 255]
    else:
        if colours.lm:
            return [255, 150, 180, 255]
        return None


heart_icon = MenuIcon(asset_loader('heart-menu.png', True))
heart_row_icon = asset_loader('heart-track.png', True)
heart_notify_icon = asset_loader('heart-notify.png', True)
heart_notify_break_icon = asset_loader('heart-notify-break.png', True)
#spotify_row_icon = asset_loader('spotify-row.png', True)
star_pc_icon = asset_loader('star-pc.png', True)
star_row_icon = asset_loader('star.png', True)
star_half_row_icon = asset_loader('star-half.png', True)

def draw_rating_widget(x, y, n_track, album=False):

    if album:
        rat = album_star_store.get_rating(n_track)
    else:
        rat = star_store.get_rating(n_track.index)

    rect = (x - round(5 * gui.scale), y - round(4 * gui.scale), round(80 * gui.scale), round(16 * gui.scale))
    gui.heart_fields.append(rect)

    if coll(rect) and (inp.mouse_click or (is_level_zero() and not quick_drag)):
        gui.pl_update = 2
        pp = mouse_position[0] - x

        if pp < 5 * gui.scale:
            rat = 0
        elif pp > 70 * gui.scale:
            rat = 10
        else:
            rat = pp // (star_row_icon.w // 2)

        if inp.mouse_click:
            rat = min(rat, 10)
            if album:
                album_star_store.set_rating(n_track, rat)
            else:
                star_store.set_rating(n_track.index, rat, write=True)

    # bg = colours.grey(40)
    bg = [255, 255, 255, 17]
    fg = colours.grey(210)

    if gui.tracklist_bg_is_light:
        bg = [0, 0, 0, 25]
        fg = colours.grey(70)

    playtime_stars = 0
    if prefs.rating_playtime_stars and rat == 0 and not album:
        playtime_stars = star_count3(star_store.get(n_track.index), n_track.length)
        if gui.tracklist_bg_is_light:
            fg2 = alpha_blend([0, 0, 0, 70], ddt.text_background_colour)
        else:
            fg2 = alpha_blend([255, 255, 255, 50], ddt.text_background_colour)

    for ss in range(5):

        xx = x + ss * star_row_icon.w

        if playtime_stars:
            if playtime_stars - 1 < ss * 2:
                star_row_icon.render(xx, y, bg)
            elif playtime_stars - 1 == ss * 2:
                star_row_icon.render(xx, y, bg)
                star_half_row_icon.render(xx, y, fg2)
            else:
                star_row_icon.render(xx, y, fg2)
        else:

            if rat - 1 < ss * 2:
                star_row_icon.render(xx, y, bg)
            elif rat - 1 == ss * 2:
                star_row_icon.render(xx, y, bg)
                star_half_row_icon.render(xx, y, fg)
            else:
                star_row_icon.render(xx, y, fg)


heart_colours = ColourGenCache(0.7, 0.7)

heart_icon.colour = [245, 60, 60, 255]
heart_icon.xoff = 3
heart_icon.yoff = 0

if gui.scale == 1.25:
    heart_icon.yoff = 1

heart_icon.colour_callback = heart_menu_colour
def love_deco():

    if love(False):
        return [colours.menu_text, colours.menu_background, _("Un-Love Track")]
    else:
        if pctl.playing_state == 1 or pctl.playing_state == 2:
            return [colours.menu_text, colours.menu_background, _("Love Track")]
        else:
            return [colours.menu_text_disabled, colours.menu_background, _("Love Track")]


def bar_love(notify=False):
    shoot_love = threading.Thread(target=love, args=[True, None, False, notify])
    shoot_love.daemon = True
    shoot_love.start()

def bar_love_notify():
    bar_love(notify=True)

def select_love(notify=False):

    selected = playlist_selected
    playlist = pctl.multi_playlist[pctl.active_playlist_viewing][2]
    if -1 < selected < len(playlist):
        track_id = playlist[selected]

        shoot_love = threading.Thread(target=love, args=[True, track_id, False, notify])
        shoot_love.daemon = True
        shoot_love.start()


extra_menu.add('Love', bar_love_notify, love_deco, icon=heart_icon)

def toggle_spotify_like_active2(tr):

    if "spotify-track-url" in tr.misc:
        if "spotify-liked" in tr.misc:
            spot_ctl.unlike_track(tr)
        else:
            spot_ctl.like_track(tr)

    for i, p in enumerate(pctl.multi_playlist):
        code = pctl.gen_codes.get(p[6])
        if code and code.startswith("slt"):
            print("Fetching Spotify likes...")
            regenerate_playlist(i, silent=True)

def toggle_spotify_like_active():
    tr = pctl.playing_object()
    if tr:
        shoot_dl = threading.Thread(target=toggle_spotify_like_active2, args=([tr]))
        shoot_dl.daemon = True
        shoot_dl.start()

def toggle_spotify_like_active_deco():
    tr = pctl.playing_object()
    text = _("Spotify Like Track")

    if pctl.playing_state == 0 or not tr or not "spotify-track-url" in tr.misc:
        return [colours.menu_text_disabled, colours.menu_background, text]
    if "spotify-liked" in tr.misc:
        text = _("Un-like Spotify Track")

    return [colours.menu_text, colours.menu_background, text]


extra_menu.add('Spotify Like Track', toggle_spotify_like_active, toggle_spotify_like_active_deco, show_test=spotify_show_test, icon=spot_heartx_icon)




def locate_artist():
    track = pctl.playing_object()
    if not track:
        return

    artist = track.artist
    if track.album_artist:
        artist = track.album_artist

    block_starts = []
    current = False
    for i in range(len(default_playlist)):
        track = pctl.g(default_playlist[i])
        if current is False:
            if track.artist == artist or track.album_artist == artist or (
                    'artists' in track.misc and artist in track.misc['artists']):
                block_starts.append(i)
                current = True
        else:
            if track.artist != artist and track.album_artist != artist or (
                    'artists' in track.misc and artist in track.misc['artists']):
                current = False

    if block_starts:

        global playlist_selected
        next = False
        for start in block_starts:

            if next:
                playlist_selected = start
                pctl.playlist_view_position = start
                shift_selection.clear()
                break

            if playlist_selected == start:
                next = True
                continue

        else:
            playlist_selected = block_starts[0]
            pctl.playlist_view_position = block_starts[0]
            shift_selection.clear()

        tree_view_box.show_track(pctl.g(default_playlist[playlist_selected]))
    else:
        show_message("No exact matching artist could be found in this playlist")

    console.print("DEBUG: Position changed by artist locate")

    gui.pl_update += 1


def activate_search_overlay():
    if cm_clean_db:
        show_message("Please wait for cleaning process to finish")
        return
    search_over.active = True
    search_over.delay_enter = False
    search_over.search_text.selection = 0
    search_over.search_text.cursor_position = 0
    search_over.spotify_mode = False


extra_menu.add(_('Global Search'), activate_search_overlay, hint="Ctrl+G")

def get_album_spot_url_active():
    tr = pctl.playing_object()
    if tr:
        url = spot_ctl.get_album_url_from_local(tr)
        if url:
            copy_to_clipboard(url)
            show_message(_("URL copied to clipboard"), mode="done")
        else:
            show_message(_("No results found"))

def get_album_spot_url_actove_deco():
    tr = pctl.playing_object()
    text = _("Copy Album URL")
    if not tr:
        return [colours.menu_text_disabled, colours.menu_background, text]
    if not "spotify-album-url" in tr.misc:
        text = _("Lookup Spotify Album")

    return [colours.menu_text, colours.menu_background, text]

extra_menu.add("Copy Spotify URL", get_album_spot_url_active, get_album_spot_url_actove_deco, show_test=spotify_show_test, icon=spot_icon)

def goto_playing_extra():
    pctl.show_current(highlight=True)

extra_menu.add(_("Locate Artist"), locate_artist)

extra_menu.add(_("Go To Playing"), goto_playing_extra, hint="'")


def toggle_auto_theme(mode=0):

    if mode == 1:
        return prefs.colour_from_image

    prefs.colour_from_image ^= True
    gui.theme_temp_current = -1

    gui.reload_theme = True

    # if prefs.colour_from_image and prefs.art_bg and not key_shift_down:
    #     toggle_auto_bg()

def toggle_auto_bg(mode=0):

    if mode == 1:
        return prefs.art_bg
    prefs.art_bg ^= True

    if prefs.art_bg:
        gui.update = 60

    style_overlay.flush()
    tm.ready("style")

    # if prefs.colour_from_image and prefs.art_bg and not key_shift_down:
    #     toggle_auto_theme()


def toggle_auto_bg_strong(mode=0):

    if mode == 1:
        return prefs.art_bg_stronger == 2

    if prefs.art_bg_stronger == 2:
        prefs.art_bg_stronger = 1
    else:
        prefs.art_bg_stronger = 2
    gui.update_layout()

def toggle_auto_bg_strong1(mode=0):

    if mode == 1:
        return prefs.art_bg_stronger == 1
    prefs.art_bg_stronger = 1
    gui.update_layout()

def toggle_auto_bg_strong2(mode=0):

    if mode == 1:
        return prefs.art_bg_stronger == 2
    prefs.art_bg_stronger = 2
    gui.update_layout()
    if prefs.art_bg:
        gui.update = 60


def toggle_auto_bg_strong3(mode=0):

    if mode == 1:
        return prefs.art_bg_stronger == 3
    prefs.art_bg_stronger = 3
    gui.update_layout()
    if prefs.art_bg:
        gui.update = 60


def toggle_auto_bg_blur(mode=0):

    if mode == 1:
        return prefs.art_bg_always_blur
    prefs.art_bg_always_blur ^= True
    style_overlay.flush()
    tm.ready("style")


def toggle_auto_bg_showcase(mode=0):

    if mode == 1:
        return prefs.bg_showcase_only
    prefs.bg_showcase_only ^= True
    gui.update_layout()


def toggle_notifications(mode=0):

    if mode == 1:
        return prefs.show_notifications

    prefs.show_notifications ^= True

    if prefs.show_notifications:
        if not de_notify_support:
            show_message("Notifications for this DE not supported", '', mode='warning')

# def toggle_al_pref_album_artist(mode=0):
#
#     if mode == 1:
#         return prefs.artist_list_prefer_album_artist
#
#     prefs.artist_list_prefer_album_artist ^= True
#     artist_list_box.saves.clear()


def toggle_mini_lyrics(mode=0):

    if mode == 1:
        return prefs.show_lyrics_side

    prefs.show_lyrics_side ^= True


def toggle_showcase_vis(mode=0):

    if mode == 1:
        return prefs.showcase_vis

    prefs.showcase_vis ^= True
    gui.update_layout()


def toggle_level_meter(mode=0):

    if mode == 1:
        return gui.vis_want != 0

    if gui.vis_want == 0:
        gui.vis_want = 1
    else:
        gui.vis_want = 0

    gui.update_layout()

# def toggle_force_subpixel(mode=0):
#
#     if mode == 1:
#         return prefs.force_subpixel_text != 0
#
#     prefs.force_subpixel_text ^= True
#     ddt.force_subpixel_text = prefs.force_subpixel_text
#     ddt.clear_text_cache()


def level_meter_special_2():

    gui.level_meter_colour_mode = 2


theme_files = os.listdir(install_directory + '/theme')
theme_files.sort()


def last_fm_menu_deco():

    if prefs.scrobble_hold:
        if not prefs.auto_lfm and lb.enable:
            line = _("ListenBrainz is Paused")
        else:
            line = _("Scrobbling is Paused")
        bg = colours.menu_background
    else:
        if not prefs.auto_lfm and lb.enable:
            line = _("ListenBrainz is Active")
        else:
            line = _("Scrobbling is Active")

        bg = colours.menu_background

    return [colours.menu_text, bg, line]


def lastfm_colour():
    if not prefs.scrobble_hold:
        return [250, 50, 50, 255]
    else:
        return None

last_fm_icon = asset_loader('as.png', True)
lastfm_icon = MenuIcon(last_fm_icon)

if gui.scale == 2:
    lastfm_icon.xoff = 0
elif gui.scale == 1.25:
    lastfm_icon.xoff = 0
else:
    lastfm_icon.xoff = -1

lastfm_icon.yoff = 1

lastfm_icon.colour = [249, 70, 70, 255]
lastfm_icon.colour_callback = lastfm_colour

def lastfm_menu_test(a):

    if (prefs.auto_lfm and prefs.last_fm_token is not None) or prefs.enable_lb or prefs.maloja_enable:
        return True
    return False


lb_icon = MenuIcon(asset_loader('lb-g.png'))
lb_icon.base_asset = asset_loader('lb-gs.png')

def lb_mode():
    return prefs.enable_lb


lb_icon.mode_callback = lb_mode

lb_icon.xoff = 3
lb_icon.yoff = -1

if gui.scale == 1.25:
    lb_icon.yoff = 0

if prefs.auto_lfm:
    listen_icon = lastfm_icon
elif lb.enable:
    listen_icon = lb_icon
else:
    listen_icon = None

x_menu.add("LFM", lastfm.toggle, last_fm_menu_deco, icon=listen_icon, show_test=lastfm_menu_test)


def discord_loop():

    prefs.discord_active = True

    if not pctl.playing_ready():
        #show_message("Please start playing a track first")
        return

    asyncio.set_event_loop(asyncio.new_event_loop())

    try:
        #print("Attempting to connect to Discord...")
        client_id = '434627346574606351'
        RPC = Presence(client_id)
        RPC.connect()

        print("Discord RPC connection successful.")
        time.sleep(1)
        start_time = time.time()
        idle_time = Timer()

        state = 0
        index = -1
        br = False
        gui.discord_status = "Connected"
        gui.update += 1
        current_state = 0

        while True:
            while True:

                current_index = pctl.playing_object().index
                if pctl.playing_state == 3:
                    current_index = radiobox.song_key

                if current_state == 0 and pctl.playing_state in (1, 3):
                    current_state = 1
                elif current_state == 1 and pctl.playing_state not in (1, 3):
                    current_state = 0
                    idle_time.set()

                if state != current_state or index != current_index:
                    if pctl.playing_time > 4 or current_state != 1:
                        state = current_state
                        index = current_index
                        start_time = time.time() - pctl.playing_time

                        break

                if current_state == 0 and idle_time.get() > 13:
                    print("Pause discord RPC...")
                    gui.discord_status = "Idle"
                    RPC.clear(pid)
                    #RPC.close()

                    while True:
                        if prefs.disconnect_discord:
                            break
                        if pctl.playing_state == 1:
                            print("Reconnect discord...")
                            RPC.connect()
                            gui.discord_status = "Connected"
                            break
                        time.sleep(2)

                    if not prefs.disconnect_discord:
                        continue

                time.sleep(2)

                if prefs.disconnect_discord:
                    RPC.clear(pid)
                    RPC.close()
                    prefs.disconnect_discord = False
                    gui.discord_status = "Not connected"
                    br = True
                    break

            if br:
                break

            title = "Unknown Track"
            tr = pctl.playing_object()
            if tr.artist != "" and tr.title != "":
                title = tr.artist + " - " + tr.title
                if len(title) > 150:
                    title = "Unknown Track"

            if tr.album:
                album = tr.album
            else:
                album = "Unknown Album"
                if pctl.playing_state == 3:
                    album = radiobox.loaded_station["title"]

            if len(album) == 1:
                album += " "

            if state == 1:
                #print("PLAYING: " + title)
                # print(start_time)
                RPC.update(pid=pid,
                           state=album,
                           details=title,
                           start=int(start_time),
                           large_image="tauon-standard",)

            else:
                #print("Discord RPC - Stop")
                RPC.update(pid=pid,
                           state="Idle",
                           large_image="tauon-standard",)

            time.sleep(15)

            if prefs.disconnect_discord:
                RPC.clear(pid)
                RPC.close()
                prefs.disconnect_discord = False
                break

    except:
        #show_message("Error connecting to Discord", mode='error')
        gui.discord_status = "Error - Discord not running?"
        prefs.disconnect_discord = False

    prefs.discord_active = False


def hit_discord():
    if prefs.discord_enable and prefs.discord_allow and not prefs.discord_active:
        discord_t = threading.Thread(target=discord_loop)
        discord_t.daemon = True
        discord_t.start()


def show_spot_playing_deco():
    if pctl.playing_state == 0:
        return [colours.menu_text, colours.menu_background, None]
    else:
        return [colours.menu_text_disabled, colours.menu_background, None]

def show_spot_playing():
    if pctl.playing_state == 0:
        spot_ctl.update(start=True)
x_menu.add("Start Spotify Remote", show_spot_playing, show_spot_playing_deco, show_test=spotify_show_test, icon=spot_icon)

x_menu.add(_("Exit Shuffle Lockdown"), toggle_shuffle_layout, show_test=exit_shuffle_layout)
x_menu.add(_("Exit"), tauon.exit, hint="Alt+F4", )

def stop_quick_add():
    pctl.quick_add_target = None

def show_stop_quick_add(_):
    return pctl.quick_add_target is not None

x_menu.add(_("Disengage Quick Add"), stop_quick_add, show_test=show_stop_quick_add, )

def view_tracks():
    # if gui.show_playlist is False:
    #     gui.show_playlist = True
    if album_mode:
        toggle_album_mode()
    if gui.combo_mode:
        exit_combo()
    if gui.rsp:
        toggle_side_panel()

#
# def view_standard_full():
#     # if gui.show_playlist is False:
#     #     gui.show_playlist = True
#
#     if album_mode:
#         toggle_album_mode()
#     if gui.combo_mode:
#         toggle_combo_view(off=True)
#     if not gui.rsp:
#         toggle_side_panel()
#     global update_layout
#     update_layout = True
#     gui.rspw = window_size[0]


def view_standard_meta():
    # if gui.show_playlist is False:
    #     gui.show_playlist = True
    if album_mode:
        toggle_album_mode()

    if gui.combo_mode:
        exit_combo()

    if not gui.rsp:
        toggle_side_panel()

    global update_layout
    update_layout = True
    #gui.rspw = 80 + int(window_size[0] * 0.18)


def view_standard():
    # if gui.show_playlist is False:
    #     gui.show_playlist = True
    if album_mode:
        toggle_album_mode()
    if gui.combo_mode:
        exit_combo()
    if not gui.rsp:
        toggle_side_panel()


def standard_view_deco():
    if album_mode or gui.combo_mode or not gui.rsp:
        line_colour = colours.menu_text
    else:
        line_colour = colours.menu_text_disabled
    return [line_colour, colours.menu_background, None]


# def gallery_only_view():
#     if gui.show_playlist is False:
#         return
#     if not album_mode:
#         toggle_album_mode()
#     gui.show_playlist = False
#     global album_playlist_width
#     global update_layout
#     update_layout = True
#     gui.rspw = window_size[0]
#     album_playlist_width = gui.playlist_width
#     #gui.playlist_width = -19


def toggle_library_mode():
    if gui.set_mode:
        gui.set_mode = False
        #gui.set_bar = False
    else:
        gui.set_mode = True
        #gui.set_bar = True
    gui.update_layout()

def library_deco():
    tc = colours.menu_text
    if gui.combo_mode or (gui.show_playlist is False and album_mode):
        tc = colours.menu_text_disabled

    if gui.set_mode:
        return [tc, colours.menu_background, "Disable Columns"]
    else:
        return [tc, colours.menu_background, 'Enable Columns']

def break_deco():
    tex = colours.menu_text
    if gui.combo_mode or (gui.show_playlist is False and album_mode):
        tex = colours.menu_text_disabled
    if not break_enable:
        tex = colours.menu_text_disabled


    if pctl.multi_playlist[pctl.active_playlist_viewing][4] == 0:
        return [tex, colours.menu_background, "Disable Title Breaks"]
    else:
        return [tex, colours.menu_background, 'Enable Title Breaks']

def toggle_playlist_break():
    pctl.multi_playlist[pctl.active_playlist_viewing][4] ^= 1
    gui.pl_update = 1

# ---------------------------------------------------------------------------------------


def transcode_single(item, manual_directroy=None, manual_name=None):
    global core_use

    if manual_directroy != None:
        codec = "opus"
        output = manual_directroy
        track = item
        core_use += 1
        bitrate = 48
    else:
        track = item[0]
        codec = prefs.transcode_codec
        output = prefs.encoder_output + item[1] + "/"
        bitrate = prefs.transcode_bitrate

    if not os.path.isfile(pctl.master_library[track].fullpath):
        show_message("Encoding warning: Missing one or more files")
        core_use -= 1
        return

    t = pctl.master_library[track]
    if t.is_cue:
        out_line = str(t.track_number) + ". "
        out_line += t.artist + " - " + t.title
        out_line = filename_safe(out_line)

    else:
        out_line = os.path.splitext(pctl.master_library[track].filename)[0]

    target_out = output + 'output' + str(track) + "." + codec

    command = user_directory + "/encoder/ffmpeg "


    if system != 'windows' and not msys:
        command = ffmpeg + " "
    else:
        command = command.replace("/", "\\")

    if not pctl.master_library[track].is_cue:
        command += '-i "'
    else:
        command += '-ss ' + str(pctl.master_library[track].start_time)
        command += ' -t ' + str(pctl.master_library[track].length)

        command += ' -i "'

    command += pctl.master_library[track].fullpath.replace('"', '\\"')

    command += '" '
    if pctl.master_library[track].is_cue:
        if t.title != "":
            command += '-metadata title="' + t.title.replace('"', "").replace("'", "") + '" '
        if t.artist != "":
            command += '-metadata artist="' + t.artist.replace('"', "").replace("'", "") + '" '
        if t.album != "":
            command += '-metadata album="' + t.album.replace('"', "").replace("'", "") + '" '
        if t.track_number != "":
            command += '-metadata track="' + str(t.track_number).replace('"', "").replace("'", "") + '" '
        if t.date != "":
            command += '-metadata year="' + str(t.date).replace('"', "").replace("'", "") + '" '

    if codec != 'flac':
        command += " -b:a " + str(bitrate) + "k -vn "

    command += '"' + target_out.replace('"', '\\"') + '"'

    # print(shlex.split(command))
    startupinfo = None
    if system == 'windows' or msys:
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

    if not msys:
        command = shlex.split(command)

    subprocess.call(command, stdout=subprocess.PIPE, shell=False,
                    startupinfo=startupinfo)

    print("FFmpeg finished")
    if codec == "opus" and prefs.transcode_opus_as:
        codec = 'ogg'

    #print(target_out)

    if manual_name is None:
        final_out = output + out_line + "." + codec
        final_name = out_line + "." + codec
        os.rename(target_out, final_out)
    else:
        final_out = output + manual_name + "." + codec
        final_name = manual_name + "." + codec
        os.rename(target_out, final_out)
    if prefs.transcode_inplace:
        print("MOVE AND REPLACE!")
        if os.path.isfile(final_out) and os.path.getsize(final_out) > 1000:
            new_name = os.path.join(pctl.master_library[track].parent_folder_path, final_name)
            print(new_name)
            shutil.move(final_out, new_name)

            old_key = star_store.key(track)
            old_star = star_store.full_get(track)

            try:
                send2trash(pctl.master_library[track].fullpath)
            except:
                print("File trash error")

            if os.path.isfile(pctl.master_library[track].fullpath):
                try:
                    os.remove(pctl.master_library[track].fullpath)
                except:
                    print("File detete error")

            pctl.master_library[track].fullpath = new_name
            pctl.master_library[track].file_ext = codec.upper()

            # Update and merge playtimes
            new_key = star_store.key(track)
            if old_star and (new_key != old_key):

                new_star = star_store.full_get(track)
                if new_star is None:
                    new_star = star_store.new_object()

                new_star[0] += old_star[0]
                if old_star[2] > 0 and new_star[2] == 0:
                    new_star[2] = old_star[2]
                new_star[1] = "".join(set(new_star[1] + old_star[1]))

                if old_key in star_store.db:
                    del star_store.db[old_key]

                star_store.db[new_key] = new_star

    gui.transcoding_bach_done += 1
    core_use -= 1
    gui.update += 1


# ---------------------
added = []

def cue_scan(content, tn):
    # Get length from backend

    lasttime = tn.length

    content = content.replace("\r", "")
    content = content.split("\n")

    #print(content)

    global added

    cued = []

    LENGTH = 0
    PERFORMER = ""
    TITLE = ""
    START = 0
    DATE = ""
    ALBUM = ""
    GENRE = ""
    MAIN_PERFORMER = ""

    for LINE in content:
        if 'TITLE "' in LINE:
            ALBUM = LINE[7:len(LINE) - 2]

        if 'PERFORMER "' in LINE:
            while LINE[0] != "P":
                LINE = LINE[1:]

            MAIN_PERFORMER = LINE[11:len(LINE) - 2]

        if 'REM DATE' in LINE:
            DATE = LINE[9:len(LINE) - 1]

        if 'REM GENRE' in LINE:
            GENRE = LINE[10:len(LINE) - 1]

        if 'TRACK ' in LINE:
            break

    for LINE in reversed(content):
        if len(LINE) > 100:
            return 1
        if "INDEX 01 " in LINE:
            temp = ""
            pos = len(LINE)
            pos -= 1
            while LINE[pos] != ":":
                pos -= 1
                if pos < 8:
                    break

            START = int(LINE[pos - 2:pos]) + (int(LINE[pos - 5:pos - 3]) * 60)
            LENGTH = int(lasttime) - START
            lasttime = START

        elif 'PERFORMER "' in LINE:
            switch = 0
            for i in range(len(LINE)):
                if switch == 1 and LINE[i] == '"':
                    break
                if switch == 1:
                    PERFORMER += LINE[i]
                if LINE[i] == '"':
                    switch = 1

        elif 'TITLE "' in LINE:

            switch = 0
            for i in range(len(LINE)):
                if switch == 1 and LINE[i] == '"':
                    break
                if switch == 1:
                    TITLE += LINE[i]
                if LINE[i] == '"':
                    switch = 1

        elif 'TRACK ' in LINE:

            pos = 0
            while LINE[pos] != 'K':
                pos += 1
                if pos > 15:
                    return 1
            TN = LINE[pos + 2:pos + 4]

            TN = int(TN)

            # try:
            #     bitrate = audio.info.bitrate
            # except:
            #     bitrate = 0

            if PERFORMER == "":
                PERFORMER = MAIN_PERFORMER

            nt = copy.deepcopy(tn) #TrackClass()

            nt.cue_sheet = ""
            nt.is_embed_cue = True

            nt.index = pctl.master_count
            # nt.fullpath = filepath.replace('\\', '/')
            # nt.filename = filename
            # nt.parent_folder_path = os.path.dirname(filepath.replace('\\', '/'))
            # nt.parent_folder_name = os.path.splitext(os.path.basename(filepath))[0]
            # nt.file_ext = os.path.splitext(os.path.basename(filepath))[1][1:].upper()

            nt.album_artist = MAIN_PERFORMER
            nt.artist = PERFORMER
            nt.genre = GENRE
            nt.title = TITLE
            nt.length = LENGTH
            # nt.bitrate = source_track.bitrate
            nt.album = ALBUM
            nt.date = DATE.replace('"', '')
            nt.track_number = TN
            nt.start_time = START
            nt.is_cue = True
            nt.size = 0  # source_track.size
            # nt.samplerate = source_track.samplerate
            if TN == 1:
                nt.size = os.path.getsize(nt.fullpath)

            pctl.master_library[pctl.master_count] = nt

            cued.append(pctl.master_count)
            # loaded_pathes_cache[filepath.replace('\\', '/')] = pctl.master_count
            #added.append(pctl.master_count)

            pctl.master_count += 1
            LENGTH = 0
            PERFORMER = ""
            TITLE = ""
            START = 0
            TN = 0

    added += reversed(cued)

    # cue_list.append(filepath)


def get_album_from_first_track(track_position, track_id=None,  pl_number=None, pl_id=None):

    if pl_number is None:

        if pl_id:
            pl_number = id_to_pl(pl_id)
        else:
            pl_number = pctl.active_playlist_viewing

    playlist = pctl.multi_playlist[pl_number][2]

    if track_id is None:
        track_id = playlist[track_position]

    if playlist[track_position] != track_id:
        return []

    tracks = []
    album_parent_path = pctl.g(track_id).parent_folder_path

    i = track_position

    while i < len(playlist):
        if pctl.g(playlist[i]).parent_folder_path != album_parent_path:
            break

        tracks.append(playlist[i])
        i += 1

    return tracks


class SearchOverlay:

    def __init__(self):

        self.active = False
        self.search_text = TextBox()

        self.results = []
        self.searched_text = ""
        self.on = 0
        self.force_select = -1
        self.old_mouse = [0,0]
        self.sip = False
        self.delay_enter = False
        self.last_animate_time = 0
        self.animate_timer = Timer(100)
        self.input_timer = Timer(100)
        self.all_folders = False
        self.spotify_mode = False


    def clear(self):
        self.search_text.text = ""
        self.results.clear()
        self.searched_text = ""
        self.on = 0
        self.all_folders = False

    def click_artist(self, name, get_list=False, search_lists=None):

        playlist = []

        if search_lists is None:
            search_lists = []
            for pl in pctl.multi_playlist:
                search_lists.append(pl[2])

        for pl in search_lists:
            for item in pl:
                tr = pctl.master_library[item]
                n = name.lower()
                if tr.artist.lower() == n \
                        or tr.album_artist.lower() == n \
                        or ('artists' in tr.misc and name in tr.misc['artists']):
                    if item not in playlist:
                        playlist.append(item)

        if get_list:
            return playlist

        pctl.multi_playlist.append(pl_gen(title="Artist: " + name,
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))

        switch_playlist(len(pctl.multi_playlist) - 1)

        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "a\"" + name + "\""

        inp.key_return_press = False


    def click_year(self, name, get_list=False):

        playlist = []
        for pl in pctl.multi_playlist:
            for item in pl[2]:
                if name in pctl.master_library[item].date:
                    if item not in playlist:
                        playlist.append(item)

        if get_list:
            return playlist

        pctl.multi_playlist.append(pl_gen(title="Year: " + name,
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))

        switch_playlist(len(pctl.multi_playlist) - 1)


        inp.key_return_press = False

    def click_composer(self, name, get_list=False):

        playlist = []
        for pl in pctl.multi_playlist:
            for item in pl[2]:
                if pctl.master_library[item].composer.lower() == name.lower():
                    if item not in playlist:
                        playlist.append(item)

        if get_list:
            return playlist

        pctl.multi_playlist.append(pl_gen(title="Composer: " + name,
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))

        switch_playlist(len(pctl.multi_playlist) - 1)


        inp.key_return_press = False

    def click_meta(self, name, get_list=False, search_lists=None):

        if search_lists is None:
            search_lists = []
            for pl in pctl.multi_playlist:
                search_lists.append(pl[2])

        playlist = []
        for pl in search_lists:
            for item in pl:
                if name in pctl.master_library[item].parent_folder_path:
                    if item not in playlist:
                        playlist.append(item)

        if get_list:
            return playlist

        pctl.multi_playlist.append(pl_gen(title=os.path.basename(name).upper(),
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))

        switch_playlist(len(pctl.multi_playlist) - 1)

        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "p\"" + name + "\""

        inp.key_return_press = False

    def click_genre(self, name, get_list=False, search_lists=None):

        playlist = []


        if search_lists is None:
            search_lists = []
            for pl in pctl.multi_playlist:
                search_lists.append(pl[2])

        include_multi = False
        if name.endswith("+") or not prefs.sep_genre_multi:
            name = name.rstrip("+")
            include_multi = True

        for pl in search_lists:
            for item in pl:
                track = pctl.master_library[item]
                if track.genre.lower().replace("-", "") == name.lower().replace("-", ""):
                    if item not in playlist:
                        playlist.append(item)
                elif include_multi and ("/" in track.genre or "," in track.genre):
                    for split in track.genre.replace(",", "/").split("/"):
                        split = split.strip()
                        if name.lower().replace("-", "") == split.lower().replace("-", ""):
                            if item not in playlist:
                                playlist.append(item)


        if get_list:
            return playlist

        pctl.multi_playlist.append(pl_gen(title="Genre: " + name,
                                          playlist=copy.deepcopy(playlist),
                                          hide_title=0))

        switch_playlist(len(pctl.multi_playlist) - 1)

        if include_multi:
            pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "gm\"" + name + "\""
        else:
            pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "g=\"" + name + "\""


        inp.key_return_press = False


    def click_album(self, index):

        pctl.jump(index)
        pctl.show_current()

        inp.key_return_press = False

    def render(self):
        global input_text
        if self.active is False:

            # Activate search overlay on key presses
            if input_text != "" and gui.layer_focus == 0 and \
                 not key_lalt and not key_ralt and \
                    not key_ctrl_down and not radiobox.active and not rename_track_box.active and \
                    not quick_search_mode and not pref_box.enabled and not gui.rename_playlist_box \
                    and not gui.rename_folder_box and input_text.isalnum() and not gui.box_over \
                    and not trans_edit_box.active:

                # Divert to artist list if mouse over
                if gui.lsp and prefs.left_panel_mode == "artist list" and 2 < mouse_position[0] < gui.lspw \
                        and gui.panelY < mouse_position[1] < window_size[1] - gui.panelBY:

                    artist_list_box.locate_artist_letter(input_text)
                    return

                activate_search_overlay()
                self.old_mouse = copy.deepcopy(mouse_position)

        if self.active:

            x = 0
            y = 0
            w = window_size[0]
            h = window_size[1]

            if keymaps.test("add-to-queue"):
                input_text = ""

            if inp.backspace_press:
                # self.searched_text = ""
                # self.results.clear()

                if len(self.search_text.text) - inp.backspace_press < 1:
                    self.active = False
                    self.search_text.text = ""
                    self.results.clear()
                    self.searched_text = ""
                    return

            if key_esc_press:
                if self.delay_enter:
                    self.delay_enter = False
                else:
                    self.active = False
                    self.search_text.text = ""
                    self.results.clear()
                    self.searched_text = ""
                    return

            if gui.level_2_click and mouse_position[0] > 350 * gui.scale:
                self.active = False
                self.search_text.text = ""

            mouse_change = False
            if not point_proximity_test(self.old_mouse, mouse_position, 25):
                mouse_change = True
            # mouse_change = True

            ddt.rect((x, y, w, h), [3, 3, 3, 235])
            ddt.text_background_colour = [12, 12, 12, 255]

            # if window_size[0] > 1200 * gui.scale:
            #     hint_x = window_size[0] - 320 * gui.scale
            #     hint_y = 100 * gui.scale
            #     hint_colour = colours.grey(60)
            #     ddt.draw_text((hint_x, hint_y, 2), "Right-click an item to locate.", hint_colour, 314)
            #     hint_y += 23 * gui.scale
            #     ddt.draw_text((hint_x, hint_y, 2), "Hold Ctrl and click to add items to current viewed playlist",
            #                   hint_colour, 314)

            # Search active animation
            if self.sip:
                x = round(15 * gui.scale)
                y = x
                s = round(7 * gui.scale)
                g = round(4 * gui.scale)

                t = self.animate_timer.get()
                if abs(t - self.last_animate_time) > 0.3:
                    self.animate_timer.set()
                    t = 0

                self.last_animate_time = t

                for item in range(4):
                    a = 100
                    if round((t * 14)) % 4 == item:
                        a = 255
                    if self.spotify_mode:
                        colour = (145, 245, 78, a)
                    else:
                        colour = (140, 100, 255, a)

                    ddt.rect((x, y, s, s), colour)
                    x += g + s

                gui.update += 1

            # No results found message
            elif not self.results and len(self.search_text.text) > 1:
                if self.input_timer.get() > 0.5 and not self.sip:
                    ddt.text((130 * gui.scale, 200 * gui.scale), "No results found", [250, 250, 250, 255], 216, bg=[12, 12, 12, 255])

            # Spotify search text
            if prefs.spot_mode and not self.spotify_mode:
                text = _("Press Tab key to switch to Spotify search")
                ddt.text((window_size[0] // 2, window_size[1] - 30 * gui.scale, 2), text, [250, 250, 250, 255], 212,
                         bg=[12, 12, 12, 255])

            self.search_text.draw(80 * gui.scale, 60 * gui.scale, [230, 230, 230, 255], True, False, 30, window_size[0] - 100, big=True, click=gui.level_2_click, selection_height=30)

            if inp.key_tab_press:
                search_over.spotify_mode ^= True
                self.sip = True
                search_over.searched_text = search_over.search_text.text
                try:
                    worker2_lock.release()
                except:
                    pass

            if input_text or key_backspace_press:
                self.input_timer.set()

                gui.update += 1
            else:
                if self.input_timer.get() >= 0.20 and len(search_over.search_text.text) > 1 and search_over.search_text.text != search_over.searched_text:
                    try:
                        self.sip = True
                        worker2_lock.release()
                    except:
                        pass

            if self.input_timer.get() < 10:
                gui.frame_callback_list.append(TestTimer(0.1))

            yy = 110 * gui.scale

            if key_down_press:

                self.force_select += 1
                if self.force_select > 4:
                    self.on = self.force_select - 4
                if self.force_select > len(self.results) - 1:
                    self.force_select = len(self.results) - 1
                self.old_mouse = copy.deepcopy(mouse_position)

            if key_up_press:

                if self.force_select > -1:
                    self.force_select -= 1
                    if self.force_select < 0:
                        self.force_select = 0

                    if self.force_select < self.on + 4:
                        self.on = self.force_select - 4
                        if self.on < 0:
                            self.on = 0

                self.old_mouse = copy.deepcopy(mouse_position)

            if mouse_wheel == -1:
                self.on += 1
                self.force_select += 1
            if mouse_wheel == 1 and self.on > -1:
                self.on -= 1
                self.force_select -= 1

            enter = False

            if self.delay_enter and not self.sip and self.search_text.text == self.searched_text:
                enter = True
                self.delay_enter = False

            elif inp.key_return_press:
                if self.results:
                    enter = True
                    self.delay_enter = False
                else:
                    if self.sip or self.input_timer.get() < 0.25:
                        self.delay_enter = True
                    else:
                        enter = True
                        self.delay_enter = False

            inp.key_return_press = False

            bar_colour = [140, 80, 240, 255]
            track_in_bar_colour = [244, 209, 66, 255]

            self.on = max(self.on, 0)
            self.on = min(len(self.results) - 1, self.on)

            full_count = 0

            sec = False

            p = -1

            if self.on > 4:
                p += self.on - 4
            p = self.on - 1

            for i, item in enumerate(self.results):

                p += 1

                if p > len(self.results) - 1:
                    break

                item = self.results[p]

                fade = 1
                selected = self.on
                if self.force_select > -1:
                    selected = self.force_select

                # print(selected)

                if selected != p:
                    fade = 0.85

                # Block separating lower search results
                if item[4] < 4 and not sec:
                    if i != 0:
                        ddt.rect((50 * gui.scale, yy + 5 * gui.scale, 400 * gui.scale, 4 * gui.scale),
                                 [255, 255, 255, 40])
                        yy += 20 * gui.scale

                    sec = True

                full = False

                start = yy

                if item[0] == 0:
                    cl = [250, 140, 190, int(255 * fade)]
                    text = "Artist"
                    yy += 3 * gui.scale
                    xx = ddt.text((120 * gui.scale, yy), item[1], [255, 255, 255, int(255 * fade)], 215, bg=[12, 12, 12, 255])

                    ddt.text((65 * gui.scale, yy), text, cl, 214, bg=[12, 12, 12, 255])

                    if fade == 1:
                        ddt.rect((30 * gui.scale, yy - 3 * gui.scale, 4 * gui.scale, 23 * gui.scale), bar_colour)

                    show = False
                    rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                    fields.add(rect)
                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2

                        if gui.level_2_click:

                            if key_ctrl_down:
                                default_playlist.extend(self.click_artist(item[1], get_list=True))
                                gui.pl_update += 1
                            else:

                                self.click_artist(item[1])
                                #pctl.show_current(index=item[2], playing=False)
                                self.active = False
                                self.search_text.text = ""

                        if level_2_right_click:
                            show = True

                    if enter and key_shift_down and fade == 1:
                        show = True
                    elif enter and fade == 1:
                        if key_shift_down or key_shiftr_down:
                            #self.click_artist(item[1])
                            pctl.show_current(index=item[2], playing=False)
                        else:
                            #pctl.show_current(index=item[2], playing=False)
                            self.click_artist(item[1])

                        self.active = False
                        self.search_text.text = ""
                    if show:
                        pctl.show_current(index=item[2], playing=False)
                        self.active = False
                        self.search_text.text = ""

                    yy += 6 * gui.scale

                # Spotify Artist
                if item[0] == 10:
                    cl = [145, 245, 78, int(255 * fade)]
                    text = "Artist"
                    yy += 3 * gui.scale
                    xx = ddt.text((120 * gui.scale, yy), item[1], [255, 255, 255, int(255 * fade)], 215, bg=[12, 12, 12, 255])

                    ddt.text((65 * gui.scale, yy), text, cl, 214, bg=[12, 12, 12, 255])

                    if fade == 1:
                        ddt.rect((30 * gui.scale, yy - 3 * gui.scale, 4 * gui.scale, 23 * gui.scale), bar_colour)

                    rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                    fields.add(rect)
                    go = False
                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2

                        if gui.level_2_click:

                            if key_ctrl_down:
                                #default_playlist.extend(self.click_artist(item[1], get_list=True))
                                gui.pl_update += 1
                            else:
                                go = True
                                #spot_ctl.artist_playlist(item[2])
                                self.active = False
                                self.search_text.text = ""

                        if level_2_right_click:

                            #pctl.show_current(index=item[2], playing=False)
                            self.active = False
                            self.search_text.text = ""

                    if enter and fade == 1:
                        go = True
                        #spot_ctl.artist_playlist(item[2])
                        self.active = False
                        self.search_text.text = ""

                    yy += 6 * gui.scale
                    if go:
                        show_message(_("Searching for albums by artist: ") + item[1], _("This may take a moment"))
                        shoot = threading.Thread(target=spot_ctl.artist_playlist, args=([item[2]]))
                        shoot.daemon = True
                        shoot.start()

                # Spotify Album
                if item[0] == 11:

                    if not item[5]:
                        cl = [130, 237, 69, int(255 * fade)]
                        text = "Album"
                        yy += 3 * gui.scale
                        xx = ddt.text((120 * gui.scale, yy), " - ".join(item[1]), [255, 255, 255, int(255 * fade)], 214, bg=[12, 12, 12, 255])


                        ddt.text((65 * gui.scale, yy), text, cl, 214, bg=[12, 12, 12, 255])

                        if fade == 1:
                            ddt.rect((30 * gui.scale, yy - 3 * gui.scale, 4 * gui.scale, 23 * gui.scale), bar_colour)

                        rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                        fields.add(rect)
                        full = False

                    else:

                        yy += 3 * gui.scale
                        xx = ddt.text((120 * gui.scale, yy + round(5 * gui.scale)), item[1][0], [255, 255, 255, int(255 * fade)], 214, bg=[12, 12, 12, 255])

                        artist = item[1][1]

                        ddt.text((125 * gui.scale, yy + 30 * gui.scale), "BY", [250, 240, 110, int(255 * fade)], 212, bg=[12, 12, 12, 255])
                        xx += 8 * gui.scale

                        xx += ddt.text((150 * gui.scale, yy + 30 * gui.scale), artist, [250, 250, 250, int(255 * fade)], 15, bg=[12, 12, 12, 255])

                        ddt.rect((50 * gui.scale, yy + 5, 50 * gui.scale, 50 * gui.scale), [50, 50, 50, 150])
                        #gall_ren.render(pctl.g(item[2]), (50 * gui.scale, yy + 5), 50 * gui.scale)
                        if not item[5].draw(50 * gui.scale, yy + 5):
                            try:
                                gall_ren.lock.release()
                            except:
                                pass

                        if fade != 1:
                            ddt.rect((50 * gui.scale, yy + 5, 50 * gui.scale, 50 * gui.scale), [0, 0, 0, 70])
                        full = True
                        full_count += 1

                        if fade == 1:
                            ddt.rect((30 * gui.scale, yy + 5, 4 * gui.scale, 50 * gui.scale), bar_colour)

                        if key_ctrl_down and item[2] in default_playlist:
                            ddt.rect((24 * gui.scale, yy + 5, 4 * gui.scale, 50 * gui.scale), track_in_bar_colour)

                        rect = (30 * gui.scale, yy, 600 * gui.scale, 55 * gui.scale)
                        fields.add(rect)


                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2

                        if gui.level_2_click:

                            if key_ctrl_down:
                                #default_playlist.extend(self.click_artist(item[1], get_list=True))
                                gui.pl_update += 1
                            else:
                                spot_ctl.append_album(item[2])
                                reload_albums()
                                #self.click_artist(item[1])
                                self.active = False
                                self.search_text.text = ""

                        if level_2_right_click:

                            #pctl.show_current(index=item[2], playing=False)
                            self.active = False
                            self.search_text.text = ""

                    if enter and fade == 1:
                        spot_ctl.append_album(item[2])
                        reload_albums()
                        #self.click_artist(item[1])
                        self.active = False
                        self.search_text.text = ""

                    if full:
                        yy += 47 * gui.scale
                    else:
                        yy += 6 * gui.scale


                if item[0] == 1:

                    yy += 3 * gui.scale
                    xx = ddt.text((120 * gui.scale, yy + round(5 * gui.scale)), item[1], [255, 255, 255, int(255 * fade)], 214, bg=[12, 12, 12, 255])

                    artist = pctl.master_library[item[2]].album_artist
                    if artist == "":
                        artist = pctl.master_library[item[2]].artist

                    if full_count < 7:

                        ddt.text((125 * gui.scale, yy + 30 * gui.scale), "BY", [250, 240, 110, int(255 * fade)], 212, bg=[12, 12, 12, 255])
                        xx += 8 * gui.scale

                        xx += ddt.text((150 * gui.scale, yy + 30 * gui.scale), artist, [250, 250, 250, int(255 * fade)], 15, bg=[12, 12, 12, 255])

                        ddt.rect((50 * gui.scale, yy + 5, 50 * gui.scale, 50 * gui.scale), [50, 50, 50, 150])
                        gall_ren.render(pctl.g(item[2]), (50 * gui.scale, yy + 5), 50 * gui.scale)
                        if fade != 1:
                            ddt.rect((50 * gui.scale, yy + 5, 50 * gui.scale, 50 * gui.scale), [0, 0, 0, 70])
                        full = True
                        full_count += 1

                        if fade == 1:
                            ddt.rect((30 * gui.scale, yy + 5, 4 * gui.scale, 50 * gui.scale), bar_colour)

                        if key_ctrl_down and item[2] in default_playlist:
                            ddt.rect((24 * gui.scale, yy + 5, 4 * gui.scale, 50 * gui.scale), track_in_bar_colour)

                        rect = (30 * gui.scale, yy, 600 * gui.scale, 55 * gui.scale)
                        fields.add(rect)
                    else:

                        ddt.text((120 + xx + 11 * gui.scale, yy), "BY", [250, 240, 110, int(255 * fade)], 212, bg=[12, 12, 12, 255])
                        xx += 8 * gui.scale

                        xx += ddt.text((120 + xx + 30 * gui.scale, yy), artist, [255, 255, 255, int(255 * fade)], 15, bg=[12, 12, 12, 255])

                        rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                        fields.add(rect)

                    show = False
                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2
                        if gui.level_2_click:

                            if key_ctrl_down:

                                for k, pl in enumerate(pctl.multi_playlist):
                                    if item[2] in pl[2]:
                                        default_playlist.extend(get_album_from_first_track(pl[2].index(item[2]), item[2], k))
                                        break
                                gui.pl_update += 1

                            else:

                                self.click_album(item[2])
                                pctl.playlist_view_position = playlist_selected
                                console.print("DEBUG: Position changed by global search")
                                self.active = False
                                self.search_text.text = ""

                        if level_2_right_click:
                            show = True

                    if enter and key_shift_down and fade == 1:
                        show = True
                    elif enter and fade == 1:
                        self.click_album(item[2])
                        pctl.show_current(index=item[2])
                        pctl.playlist_view_position = playlist_selected
                        console.print("DEBUG: Position changed by global search")
                        self.active = False
                        self.search_text.text = ""
                    if show:
                        pctl.show_current(index=item[2], playing=False)
                        pctl.playlist_view_position = playlist_selected
                        if album_mode:
                            show_in_gal(0)
                        self.active = False
                        self.search_text.text = ""
                    if keymaps.test("add-to-queue") and fade == 1:
                        add_album_to_queue(item[2], pctl.multi_playlist[id_to_pl(item[3])][2].index(item[2]), item[3])


                    if full:
                        yy += 47 * gui.scale

                if item[0] == 2:
                    cl = [250, 220, 190, int(255 * fade)]
                    text = "Track"
                    track = pctl.master_library[item[2]]

                    if track.artist == track.title == "":
                        ddt.text((120 * gui.scale, yy), os.path.splitext(track.filename)[0], [255, 255, 255, int(255 * fade)], 15,
                                 bg=[12, 12, 12, 255])
                    else:
                        xx = ddt.text((120 * gui.scale, yy), item[1], [255, 255, 255, int(255 * fade)], 15, bg=[12, 12, 12, 255])

                        ddt.text((xx + (120 + 11) * gui.scale, yy), "BY", [250, 160, 110, int(255 * fade)], 212, bg=[12, 12, 12, 255])
                        xx += 8 * gui.scale
                        artist = track.artist
                        xx += ddt.text((xx + (120 + 30) * gui.scale, yy), artist, [255, 255, 255, int(255 * fade)], 214, bg=[12, 12, 12, 255])

                        if track.album:
                            xx += 9 * gui.scale
                            xx += ddt.text((xx + (120 + 30) * gui.scale, yy), "FROM", [120, 120, 120, int(255 * fade)], 212,
                                     bg=[12, 12, 12, 255])
                            xx += 8 * gui.scale
                            xx += ddt.text((xx + (120 + 30) * gui.scale, yy), track.album, [80, 80, 80, int(255 * fade)], 212,
                                     bg=[12, 12, 12, 255])

                    ddt.text((65 * gui.scale, yy), text, cl, 314, bg=[12, 12, 12, 255])
                    if fade == 1:
                        ddt.rect((30 * gui.scale, yy, 4 * gui.scale, 17 * gui.scale), bar_colour)

                    if key_ctrl_down and item[2] in default_playlist:
                        ddt.rect((24 * gui.scale, yy, 4 * gui.scale, 17 * gui.scale), track_in_bar_colour)

                    show = False
                    rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                    fields.add(rect)
                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2

                        if gui.level_2_click:
                            if key_ctrl_down:
                                default_playlist.append(item[2])
                                gui.pl_update += 1
                            else:
                                self.click_album(item[2])
                                self.active = False
                                self.search_text.text = ""

                        if level_2_right_click:
                            show = True


                    if enter and key_shift_down and fade == 1:
                        show = True
                    elif enter and fade == 1:
                        self.click_album(item[2])
                        self.active = False
                        self.search_text.text = ""
                    if show:
                        pctl.show_current(index=item[2], playing=False)
                        if album_mode:
                            show_in_gal(0)
                        self.active = False
                        self.search_text.text = ""

                    if keymaps.test("add-to-queue") and fade == 1:
                        queue_object = queue_item_gen(item[2],
                                                      pctl.multi_playlist[id_to_pl(item[3])][2].index(item[2]),
                                                      item[3])
                        pctl.force_queue.append(queue_object)
                        queue_timer_set(queue_object=queue_object)

                # spotify track
                if item[0] == 12:
                    cl = [200, 255, 150, int(255 * fade)]
                    text = "Track"

                    xx = ddt.text((120 * gui.scale, yy), item[1][0], [255, 255, 255, int(255 * fade)], 15, bg=[12, 12, 12, 255])
                    ddt.text((xx + (120 + 11) * gui.scale, yy), "BY", [250, 160, 110, int(255 * fade)], 212, bg=[12, 12, 12, 255])
                    xx += 8 * gui.scale
                    xx += ddt.text((xx + (120 + 30) * gui.scale, yy), item[1][1], [255, 255, 255, int(255 * fade)], 214, bg=[12, 12, 12, 255])

                    ddt.text((65 * gui.scale, yy), text, cl, 314, bg=[12, 12, 12, 255])
                    if fade == 1:
                        ddt.rect((30 * gui.scale, yy, 4 * gui.scale, 17 * gui.scale), bar_colour)

                    if key_ctrl_down and item[2] in default_playlist:
                        ddt.rect((24 * gui.scale, yy, 4 * gui.scale, 17 * gui.scale), track_in_bar_colour)

                    rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                    fields.add(rect)
                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2
                        if gui.level_2_click:

                            if key_ctrl_down:
                                #default_playlist.append(item[2])
                                gui.pl_update += 1
                            else:
                                spot_ctl.append_track(item[2])
                                reload_albums()
                                self.active = False
                                self.search_text.text = ""

                        if level_2_right_click:
                            #pctl.show_current(index=item[2], playing=False)
                            self.active = False
                            self.search_text.text = ""
                    if enter and fade == 1:
                        spot_ctl.append_track(item[2])
                        reload_albums()
                        self.active = False
                        self.search_text.text = ""


                if item[0] == 3:
                    cl = [240, 240, 160, int(255 * fade)]
                    text = "Genre"
                    xx = ddt.text((120 * gui.scale, yy), item[1].rstrip("+"), [255, 255, 255, int(255 * fade)], 215, bg=[12, 12, 12, 255])

                    if item[1].endswith("+"):
                        ddt.text((xx + 127 * gui.scale, yy - 1 * gui.scale), "(Include multi-tag results)", [255, 255, 255, int(255 * fade) // 2], 313,
                                 bg=[12, 12, 12, 255])

                    ddt.text((65 * gui.scale, yy), text, cl, 214, bg=[12, 12, 12, 255])
                    if fade == 1:
                        ddt.rect((30 * gui.scale, yy - 3 * gui.scale, 4 * gui.scale, 20 * gui.scale), bar_colour)

                    rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                    fields.add(rect)
                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2
                        if gui.level_2_click:

                            if key_ctrl_down:
                                default_playlist.extend(self.click_genre(item[1], get_list=True))
                                gui.pl_update += 1
                            else:
                                self.click_genre(item[1])
                                self.active = False
                                self.search_text.text = ""

                        if level_2_right_click:
                            pctl.show_current(index=item[2], playing=False)
                            self.active = False
                            self.search_text.text = ""
                    if enter and fade == 1:
                        self.click_genre(item[1])
                        self.active = False
                        self.search_text.text = ""

                if item[0] == 5:
                    cl = [250, 100, 50, int(255 * fade)]
                    text = "FOLDER"
                    xx = ddt.text((120 * gui.scale, yy), item[1], [255, 255, 255, int(255 * fade)], 214, bg=[12, 12, 12, 255])

                    ddt.text((49 * gui.scale, yy), text, cl, 214, bg=[12, 12, 12, 255])
                    if fade == 1:
                        ddt.rect((30 * gui.scale, yy - 3 * gui.scale, 4 * gui.scale, 20 * gui.scale), bar_colour)

                    rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                    fields.add(rect)
                    show = False
                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2
                        if gui.level_2_click:
                            if key_ctrl_down:
                                default_playlist.extend(self.click_meta(item[1], get_list=True))
                                gui.pl_update += 1
                            else:
                                self.click_meta(item[1])
                                self.active = False
                                self.search_text.text = ""
                        if level_2_right_click:
                            show = True

                    if enter and key_shift_down and fade == 1:
                        show = True
                    elif enter and fade == 1:
                        self.click_meta(item[1])
                        self.active = False
                        self.search_text.text = ""
                    if show:
                        pctl.show_current(index=item[2], playing=False)
                        self.active = False
                        self.search_text.text = ""

                if item[0] == 6:
                    cl = [180, 250, 190, int(255 * fade)]
                    text = "Composer"
                    yy += 3 * gui.scale
                    xx = ddt.text((124 * gui.scale, yy), item[1], [255, 255, 255, int(255 * fade)], 215, bg=[12, 12, 12, 255])

                    ddt.text((40 * gui.scale, yy), text, cl, 214, bg=[12, 12, 12, 255])

                    if fade == 1:
                        ddt.rect((30 * gui.scale, yy - 3 * gui.scale, 4 * gui.scale, 23 * gui.scale), bar_colour)

                    rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                    fields.add(rect)
                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2

                        if gui.level_2_click:
                            if key_ctrl_down:
                                default_playlist.extend(self.click_composer(item[1], get_list=True))
                                gui.pl_update += 1
                            else:
                                self.click_composer(item[1])
                                self.active = False
                                self.search_text.text = ""

                        if level_2_right_click:

                            pctl.show_current(index=item[2], playing=False)
                            self.active = False
                            self.search_text.text = ""

                    if enter and fade == 1:
                        self.click_composer(item[1])
                        self.active = False
                        self.search_text.text = ""

                    yy += 6 * gui.scale

                if item[0] == 7:
                    cl = [250, 50, 140, int(255 * fade)]
                    text = "Year"
                    yy += 3 * gui.scale
                    xx = ddt.text((124 * gui.scale, yy), item[1], [255, 255, 255, int(255 * fade)], 215, bg=[12, 12, 12, 255])

                    ddt.text((65 * gui.scale, yy), text, cl, 214, bg=[12, 12, 12, 255])

                    if fade == 1:
                        ddt.rect((30 * gui.scale, yy - 3 * gui.scale, 4 * gui.scale, 23 * gui.scale), bar_colour)

                    rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                    fields.add(rect)
                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2

                        if gui.level_2_click:
                            if key_ctrl_down:
                                default_playlist.extend(self.click_year(item[1], get_list=True))
                                gui.pl_update += 1
                            else:
                                self.click_year(item[1])
                                self.active = False
                                self.search_text.text = ""

                        if level_2_right_click:

                            pctl.show_current(index=item[2], playing=False)
                            self.active = False
                            self.search_text.text = ""

                    if enter and fade == 1:
                        self.click_year(item[1])
                        self.active = False
                        self.search_text.text = ""

                    yy += 6 * gui.scale

                if item[0] == 8:
                    cl = [100, 210, 250, int(255 * fade)]
                    text = "Playlist"
                    yy += 3 * gui.scale
                    xx = ddt.text((120 * gui.scale, yy), item[1], [255, 255, 255, int(255 * fade)], 215, bg=[12, 12, 12, 255])

                    ddt.text((105 * gui.scale, yy, 1), text, cl, 214, bg=[12, 12, 12, 255])

                    if fade == 1:
                        ddt.rect((30 * gui.scale, yy - 3 * gui.scale, 4 * gui.scale, 23 * gui.scale), bar_colour)

                    rect = (30 * gui.scale, yy, 600 * gui.scale, 20 * gui.scale)
                    fields.add(rect)
                    if coll(rect) and mouse_change:
                        if self.force_select != p:
                            self.force_select = p
                            gui.update = 2

                        if gui.level_2_click:
                            if key_ctrl_down:
                                pl = id_to_pl(item[3])
                                if pl:
                                    default_playlist.extend(pctl.multi_playlist[pl][2])
                                gui.pl_update += 1
                            else:
                                pl = id_to_pl(item[3])
                                if pl:
                                    switch_playlist(pl)
                                    self.active = False
                                    self.search_text.text = ""

                        if level_2_right_click:

                            pl = id_to_pl(item[3])
                            if pl:
                                switch_playlist(pl)
                                self.active = False
                                self.search_text.text = ""

                    if enter and fade == 1:
                        pl = id_to_pl(item[3])
                        if pl:
                            switch_playlist(pl)
                            self.active = False
                            self.search_text.text = ""

                    yy += 6 * gui.scale

                if i > 40:
                    break

                if yy > window_size[1] - (100 * gui.scale):
                    break

                yy += 22 * gui.scale

            if enter:
                self.results.clear()
                self.searched_text = ""

search_over = SearchOverlay()



class MessageBox:

    def __init__(self):
        pass

    def get_rect(self):

        w1 = ddt.get_text_w(gui.message_text, 15) + 74 * gui.scale
        w2 = ddt.get_text_w(gui.message_subtext, 12) + 74 * gui.scale
        w3 = ddt.get_text_w(gui.message_subtext2, 12) + 74 * gui.scale
        w = max(w1, w2, w3)

        if w < 210 * gui.scale:
            w = 210 * gui.scale

        h = round(60 * gui.scale)
        if gui.message_subtext2:
            h += round(15 * gui.scale)

        x = int(window_size[0] / 2) - int(w / 2)
        y = int(window_size[1] / 2) - int(h / 2)

        return x, y, w, h

    def render(self):

        if inp.mouse_click or inp.key_return_press or right_click or key_esc_press or inp.backspace_press \
                or keymaps.test("quick-find") or (k_input and message_box_min_timer.get() > 1.2):

            if not key_focused and message_box_min_timer.get() > 0.4:
                gui.message_box = False
                gui.update += 1
                inp.key_return_press = False

        x, y, w, h = self.get_rect()

        ddt.rect_a((x - 2 * gui.scale, y - 2 * gui.scale), (w + 4 * gui.scale, h + 4 * gui.scale),
                   colours.box_text_border)
        ddt.rect_a((x, y), (w, h), colours.message_box_bg)

        ddt.text_background_colour = colours.message_box_bg

        if gui.message_mode == 'info':
            message_info_icon.render(x + 14 * gui.scale, y + int(h / 2) - int(message_info_icon.h / 2) - 1)
        elif gui.message_mode == 'warning':
            message_warning_icon.render(x + 14 * gui.scale, y + int(h / 2) - int(message_info_icon.h / 2) - 1)
        elif gui.message_mode == 'done':
            message_tick_icon.render(x + 14 * gui.scale, y + int(h / 2) - int(message_info_icon.h / 2) - 1)
        elif gui.message_mode == 'arrow':
            message_arrow_icon.render(x + 14 * gui.scale, y + int(h / 2) - int(message_info_icon.h / 2) - 1)
        elif gui.message_mode == 'download':
            message_download_icon.render(x + 14 * gui.scale, y + int(h / 2) - int(message_info_icon.h / 2) - 1)
        elif gui.message_mode == 'error':
            message_error_icon.render(x + 14 * gui.scale, y + int(h / 2) - int(message_error_icon.h / 2) - 1)
        elif gui.message_mode == 'bubble':
            message_bubble_icon.render(x + 14 * gui.scale, y + int(h / 2) - int(message_bubble_icon.h / 2) - 1)
        elif gui.message_mode == 'link':
            message_info_icon.render(x + 14 * gui.scale, y + int(h / 2) - int(message_bubble_icon.h / 2) - 1)
        elif gui.message_mode == 'confirm':
            message_info_icon.render(x + 14 * gui.scale, y + int(h / 2) - int(message_info_icon.h / 2) - 1)
            ddt.text((x + 62 * gui.scale, y + 9 * gui.scale), gui.message_text, colours.message_box_text, 15)
            if draw.button("Confirm", (w - 62 * gui.scale) // 2 + x, y + 32 * gui.scale):
                gui.message_box_confirm_callback(*gui.message_box_confirm_reference)
            return

        if gui.message_subtext:
            ddt.text((x + 62 * gui.scale, y + 11 * gui.scale), gui.message_text, colours.message_box_text, 15)
            if gui.message_mode == "bubble" or gui.message_mode == 'link':
                link_pa = draw_linked_text((x + 63 * gui.scale, y + (9 + 22) * gui.scale), gui.message_subtext,
                                           colours.message_box_text, 12)
                link_activate(x + 63 * gui.scale, y + (9 + 22) * gui.scale, link_pa)
            else:
                ddt.text((x + 63 * gui.scale, y + (9 + 22) * gui.scale), gui.message_subtext, colours.message_box_text,
                         12)

            if gui.message_subtext2:
                ddt.text((x + 63 * gui.scale, y + (9 + 42) * gui.scale), gui.message_subtext2, colours.message_box_text,
                         12)

        else:
            ddt.text((x + 62 * gui.scale, y + 20 * gui.scale), gui.message_text, colours.message_box_text, 15)


message_box = MessageBox()

class NagBox:
    def __init__(self):
        self.wiggle_timer = Timer(10)
    def draw(self):

        w = 485 * gui.scale
        h = 170 * gui.scale
        x = int(window_size[0] / 2) - int(w / 2)
        if self.wiggle_timer.get() < 0.5:
            gui.update += 1
            x += math.sin(core_timer.get() * 40) * 4
        y = int(window_size[1] / 2) - int(h / 2)

        ddt.rect_a((x - 2 * gui.scale, y - 2 * gui.scale), (w + 4 * gui.scale, h + 4 * gui.scale),
                   colours.box_text_border)
        ddt.rect_a((x, y), (w, h), colours.message_box_bg)

        if gui.level_2_click and not coll((x, y, w, h)):
            self.wiggle_timer.set()
            gui.update += 1

        ddt.text_background_colour = colours.message_box_bg

        x += round(10 * gui.scale)
        y += round(13 * gui.scale)
        ddt.text((x, y), "Welcome to v7.0.0", colours.message_box_text, 212)
        y += round(20 * gui.scale)

        ddt.text((x, y), "The time has come to open thy heart and thy wallet!", colours.message_box_text, 312)
        heart_notify_icon.render(x + round(400 * gui.scale), y - round(10 * gui.scale), [255, 90, 90, 255])

        y += round(30 * gui.scale)
        ddt.text((x, y), "Hi, developer Taiko2k here. I hope you like Tauon Music Box!", colours.message_box_text, 312)
        y += round(20 * gui.scale)
        ddt.text((x, y), "If you do please consider showing your appreciation by becoming a monthly", colours.message_box_text, 312)
        y += round(20 * gui.scale)
        ddt.text((x, y), "supporter on Github. Even if its only for a few months, it would mean a lot!", colours.message_box_text, 312)
        y += round(30 * gui.scale)

        if draw.button("Nope!", x, y, press=gui.level_2_click):
            prefs.show_nag = False
            show_message("Oh... :( 💔")
        if draw.button("Show supporter page", x + round(70 * gui.scale), y, background_colour=[60, 140, 60, 255], background_highlight_colour=[60, 150, 60, 255], press=gui.level_2_click):
            webbrowser.open("https://github.com/sponsors/Taiko2k", new=2, autoraise=True)
            prefs.show_nag = False
        if draw.button("I already am!", x + round(360), y, press=gui.level_2_click):
            show_message("Oh hey, thanks! :)")
            prefs.show_nag = False


nagbox = NagBox()

def worker3():

    while True:
        #time.sleep(0.04)

        # if tm.exit_worker3:
        #     tm.exit_worker3 = False
        #     return
        #time.sleep(1)

        gall_ren.worker_render()


def worker4():
    gui.style_worker_timer.set()
    while True:
        if prefs.art_bg:
            style_overlay.worker()

        time.sleep(0.01)
        if pctl.playing_state > 0 and pctl.playing_time < 5:
            gui.style_worker_timer.set()
        if gui.style_worker_timer.get() > 5:
            return


worker2_lock = threading.Lock()
spot_search_rate_timer = Timer()

def worker2():

    while True:
        worker2_lock.acquire()

        if len(search_over.search_text.text) > 1:

            if search_over.spotify_mode:
                t = spot_search_rate_timer.get()
                if t < 1:
                    time.sleep(1 - t)
                    spot_search_rate_timer.set()
                print("Spotify search")
                search_over.results.clear()
                results = spot_ctl.search(search_over.search_text.text)
                if results is not None:
                    search_over.results = results
                else:
                    search_over.active = False
                    gui.show_message("Global search + Tab triggers Spotify search but Spotify is not enabled in settings!", mode="warning")
                search_over.searched_text = search_over.search_text.text
                search_over.sip = False

            elif True:
                #perf_timer.set()

                temp_results = []

                search_over.searched_text = search_over.search_text.text

                artists = {}
                albums = {}
                genres = {}
                metas = {}
                composers = {}
                years = {}

                tracks = set()

                br = 0

                if search_over.searched_text in ('the', 'and'):
                    continue

                search_over.sip = True
                gui.update += 1

                s_text = search_over.search_text.text.lower().replace("-", "")

                dia_mode = False
                if all([ord(c) < 128 for c in s_text]):
                    dia_mode = True


                artist_mode = False
                if s_text.startswith("artist "):
                    s_text = s_text[7:]
                    artist_mode = True

                album_mode = False
                if s_text.startswith("album "):
                    s_text = s_text[6:]
                    album_mode = True

                composer_mode = False
                if s_text.startswith("composer "):
                    s_text = s_text[9:]
                    composer_mode = True

                year_mode = False
                if s_text.startswith("year "):
                    s_text = s_text[5:]
                    year_mode = True

                searched = set()

                for playlist in pctl.multi_playlist:

                    # if "<" in playlist[0]:
                    #     # print("Skipping search on derivative playlist: " + playlist[0])
                    #     continue

                    for track in playlist[2]:

                        if track in searched:
                            continue
                        else:
                            searched.add(track)

                        if dia_mode:
                            cache_string = search_dia_string_cache.get(track)
                            if cache_string is not None:
                                if not search_magic_any(s_text, cache_string):
                                    continue
                                # if s_text not in cache_string:
                                #     continue
                        else:
                            cache_string = search_string_cache.get(track)
                            if cache_string is not None:
                                if not search_magic_any(s_text, cache_string):
                                    continue

                        t = pctl.master_library[track]

                        title = t.title.lower().replace("-", "")
                        artist = t.artist.lower().replace("-", "")
                        album_artist = t.album_artist.lower().replace("-", "")
                        composer = t.composer.lower().replace("-", "")
                        date = t.date.lower().replace("-", "")
                        album = t.album.lower().replace("-", "")
                        genre = t.genre.lower().replace("-", "")
                        filename = t.filename.lower().replace("-", "")
                        stem = os.path.dirname(t.parent_folder_path).lower().replace("-", "")

                        if cache_string is None:
                            if not dia_mode:
                                search_string_cache[track] = title + artist + album_artist + composer + date + album + genre + filename + stem

                        if dia_mode:
                            title = unidecode(title).decode()

                            artist = unidecode(artist).decode()
                            album_artist = unidecode(album_artist).decode()
                            composer = unidecode(composer).decode()
                            album = unidecode(album).decode()
                            filename = unidecode(filename).decode()

                            if cache_string is None:
                                search_dia_string_cache[track] = title + artist + album_artist + composer + date + album + genre + filename + stem

                        stem = os.path.dirname(t.parent_folder_path)

                        if len(s_text) > 2 and s_text in stem.replace("-", "").lower():
                            #if search_over.all_folders or (artist not in stem.lower() and album not in stem.lower()):

                            if stem in metas:
                                metas[stem] += 2
                            else:
                                temp_results.append([5, stem, track, playlist[6], 0])
                                metas[stem] = 2

                        if s_text in genre:

                            if "/" in genre or "," in genre:

                                for split in genre.replace(",", "/").split("/"):
                                    if s_text in split:

                                        split = genre_correct(split)
                                        if prefs.sep_genre_multi:
                                            split += "+"
                                        if split in genres:
                                            genres[split] += 3
                                        else:
                                            temp_results.append([3, split, track, playlist[6], 0])
                                            genres[split] = 1
                            else:
                                name = genre_correct(t.genre)
                                if name in genres:
                                    genres[name] += 3
                                else:
                                    temp_results.append([3, name, track, playlist[6], 0])
                                    genres[name] = 1

                        if s_text in composer:

                            if t.composer in composers:
                                composers[t.composer] += 2
                            else:
                                temp_results.append([6, t.composer, track, playlist[6], 0])
                                composers[t.composer] = 2

                        if s_text in date:

                            year = year_from_string(date)
                            if year:

                                if year in years:
                                    years[year] += 1
                                else:
                                    temp_results.append([7, year, track, playlist[6], 0])
                                    years[year] = 1000

                        if search_magic(s_text, title + artist + filename + album + album_artist):

                            if 'artists' in t.misc and t.misc['artists']:
                                for a in t.misc['artists']:
                                    if search_magic(s_text, a.lower()):

                                        value = 1
                                        if a.lower().startswith(s_text):
                                            value = 5

                                        # Add artist
                                        if a in artists:
                                            artists[a] += value
                                        else:
                                            temp_results.append([0, a, track, playlist[6], 0])
                                            artists[a] = value

                                        if t.album in albums:
                                            albums[t.album] += 1
                                        else:
                                            temp_results.append([1, t.album, track, playlist[6], 0])
                                            albums[t.album] = 1

                            elif search_magic(s_text, artist):

                                value = 1
                                if artist.startswith(s_text):
                                    value = 10

                                # Add artist
                                if t.artist in artists:
                                    artists[t.artist] += value
                                else:
                                    temp_results.append([0, t.artist, track, playlist[6], 0])
                                    artists[t.artist] = value


                                if t.album in albums:
                                    albums[t.album] += 1
                                else:
                                    temp_results.append([1, t.album, track, playlist[6], 0])
                                    albums[t.album] = 1

                            elif search_magic(s_text, album_artist):

                                # Add album artist
                                value = 1
                                if t.album_artist.startswith(s_text):
                                    value = 5

                                if t.album_artist in artists:
                                    artists[t.album_artist] += value
                                else:
                                    temp_results.append([0, t.album_artist, track, playlist[6], 0])
                                    artists[t.album_artist] = value


                                if t.album in albums:
                                    albums[t.album] += 1
                                else:
                                    temp_results.append([1, t.album, track, playlist[6], 0])
                                    albums[t.album] = 1


                            if s_text in album:

                                value = 1
                                if s_text == album:
                                    value = 3

                                if t.album in albums:
                                    albums[t.album] += value
                                else:
                                    temp_results.append([1, t.album, track, playlist[6], 0])
                                    albums[t.album] = value

                            if search_magic(s_text, artist) or search_magic(s_text, album):

                                if t.album in albums:
                                    albums[t.album] += 3
                                else:
                                    temp_results.append([1, t.album, track, playlist[6], 0])
                                    albums[t.album] = 3

                            elif search_magic_any(s_text, artist) and search_magic_any(s_text, album):

                                if t.album in albums:
                                    albums[t.album] += 3
                                else:
                                    temp_results.append([1, t.album, track, playlist[6], 0])
                                    albums[t.album] = 3

                            if s_text in title:

                                if t not in tracks:

                                    value = 50
                                    if s_text == title:
                                        value = 200

                                    temp_results.append([2, t.title, track, playlist[6], value])

                                    tracks.add(t)

                            else:
                                if t not in tracks:
                                    temp_results.append([2, t.title, track, playlist[6], 1])

                                    tracks.add(t)

                        br += 1
                        if br > 800:
                            time.sleep(0.005)  # Throttle thread
                            br = 0
                            if search_over.searched_text != search_over.search_text.text:
                                break

                search_over.sip = False
                search_over.on = 0
                gui.update += 1


                # Remove results not matching any filter keyword

                if artist_mode:
                    for i in reversed(range(len(temp_results))):
                        if temp_results[i][0] != 0:
                            del temp_results[i]

                elif album_mode:
                    for i in reversed(range(len(temp_results))):
                        if temp_results[i][0] != 1:
                            del temp_results[i]

                elif composer_mode:
                    for i in reversed(range(len(temp_results))):
                        if temp_results[i][0] != 6:
                            del temp_results[i]

                elif year_mode:
                    for i in reversed(range(len(temp_results))):
                        if temp_results[i][0] != 7:
                            del temp_results[i]

                # Sort results by weightings
                for i, item in enumerate(temp_results):
                    if item[0] == 0:
                        temp_results[i][4] = artists[item[1]]
                    if item[0] == 1:
                        temp_results[i][4] = albums[item[1]]
                    if item[0] == 3:
                        temp_results[i][4] = genres[item[1]]
                    if item[0] == 5:
                        temp_results[i][4] = metas[item[1]]
                        if not search_over.all_folders:
                            if metas[item[1]] < 42:
                                temp_results[i] = None
                    if item[0] == 6:
                        temp_results[i][4] = composers[item[1]]
                    if item[0] == 7:
                        temp_results[i][4] = years[item[1]]
                    # 8 is playlists

                temp_results[:] = [item for item in temp_results if item is not None]
                search_over.results = sorted(temp_results, key=lambda x: x[4], reverse=True)
                # print(search_over.results)

                i = 0
                for playlist in pctl.multi_playlist:
                    if search_magic(s_text, playlist[0].lower()):
                        item = [8, playlist[0], None, playlist[6], 100000]
                        search_over.results.insert(0, item)
                        i += 1
                        if i > 3:
                            break

                search_over.on = 0
                search_over.force_select = 0
                #print(perf_timer.get())

def encode_folder_name(track_object):

    folder_name = track_object.artist + " - " + track_object.album

    if folder_name == " - ":
        folder_name = track_object.filename

    "".join([c for c in folder_name if c.isalpha() or c.isdigit() or c == ' ']).rstrip()

    if folder_name[-1:] == ' ':
        folder_name = track_object.filename

    folder_name = filename_safe(folder_name)

    if "cd" not in folder_name.lower() or "disc" not in folder_name.lower():
        if track_object.disc_total not in ("", "0", 0, "1", 1) or (str(track_object.disc_number).isdigit() and int(track_object.disc_number) > 1):
            folder_name += " CD" + str(track_object.disc_number)

    return folder_name


def worker1():
    global cue_list
    global loaderCommand
    global loaderCommandReady
    global DA_Formats
    global home
    global loading_in_progress
    global added
    global to_get
    global to_got

    loaded_pathes_cache = {}
    added = []

    def get_end_folder(direc):

        for w in range(len(direc)):
            if direc[-w - 1] == '\\' or direc[-w - 1] == '/':
                direc = direc[-w:]
                return direc
        return None

    def get_quoted_from_line(line):

        # e.g extract quoted file name from: 'FILE "01 - Track01.wav" WAVE'

        file_name = ""
        enable = False

        for cha in line:
            if not enable and cha == "\"":
                enable = True
            elif enable:
                if cha == "\"":
                    return file_name
                file_name += cha

        return file_name



    def add_from_cue(path):

        global added

        if not msys:  # Windows terminal doesn't like unicode
            console.print("Reading CUE file: " + path)

        try:

            try:
                with open(path, encoding="utf_8") as f:
                    content = f.readlines()
            except:
                try:
                    with open(path, encoding="utf_16") as f:
                        content = f.readlines()
                        print("CUE: Detected encoding as UTF-16")
                except:
                    try:
                        with open(path, encoding='shiftjis') as f:
                            content = f.readlines()
                        print("CUE: Detected encoding as SHIFT-JIS")

                    except:
                        print("WARNING: Can't detect encoding of CUE file")
                        return 1

            f.close()

            # We want to detect if this is a cue sheet that points to either a single file with subtracks, or multiple
            # files with mutiple subtracks, but not multiple files that are individual tracks.
            # i.e, is there really any splitting going on.

            files = 0
            files_with_subtracks = 0
            subtrack_count = 0
            for line in content:
                if line.startswith("FILE "):
                    files += 1
                    if subtrack_count > 2:  # A hack way to avoid non-compliant EAC CUE sheet
                        files_with_subtracks += 1
                    subtrack_count = 0
                elif line.strip().startswith("TRACK "):
                    subtrack_count += 1
            if subtrack_count > 2:
                files_with_subtracks += 1


            if files == 1:
                pass
            elif files_with_subtracks > 1:
                pass
            else:
                return 1

            cue_performer = ""
            cue_date = ""
            cue_album = ""
            cue_genre = ""
            cue_main_performer = ""
            cue_songwriter = ""

            cd = []
            cds = []

            file_name = ""
            file_path = ""

            in_header = True

            i = -1
            while True:
                i += 1

                if i > len(content) - 1:
                    break

                line = content[i]

                line = line.strip()

                if in_header:
                    if line.startswith("REM "):
                        line = line[4:]

                    if line.startswith("TITLE \""):
                        cue_album = get_quoted_from_line(line)
                    if line.startswith("PERFORMER \""):
                        cue_performer = get_quoted_from_line(line)
                    if line.startswith("MAIN PERFORMER \""):
                        cue_main_performer = get_quoted_from_line(line)
                    if line.startswith("SONGWRITER \""):
                        cue_songwriter = get_quoted_from_line(line)
                    if line.startswith("GENRE "):
                        cue_genre = line[5:].strip().replace("\"", "")
                    if line.startswith("DATE "):
                        cue_date = line[5:].strip().replace("\"", "")

                    if line.startswith("FILE "):
                        in_header = False
                    else:
                        continue

                if line.startswith("FILE "):

                    if cd:
                        cds.append(cd)
                        cd = []

                    file_name = get_quoted_from_line(line)
                    file_path = os.path.join(os.path.dirname(path), file_name)

                    if not os.path.isfile(file_path):
                        if files == 1:
                            print("CUE: The referenced source file wasn't found. Searching for matching file name...")
                            for item in os.listdir(os.path.dirname(path)):
                                if os.path.splitext(item)[0] == os.path.splitext(os.path.basename(path))[0]:
                                    if ".cue" not in item.lower() and item.split(".")[-1].lower() in DA_Formats:
                                        file_name = item
                                        file_path = os.path.join(os.path.dirname(path), file_name)
                                        print("CUE: Source found")
                                        break
                            else:
                                print("CUE: Source file not found")
                                return 1
                        else:
                            print("CUE: Source file not found")
                            return 1

                if line.startswith("TRACK "):
                    line = line[6:]
                    if line.endswith("AUDIO"):
                        line = line[:-5]

                    nt = TrackClass()
                    nt.index = pctl.master_count
                    pctl.master_count += 1
                    nt.fullpath = file_path
                    nt.filename = file_name
                    nt.parent_folder_path = os.path.dirname(file_path.replace('\\', '/'))
                    nt.parent_folder_name = os.path.splitext(os.path.basename(file_path))[0]
                    nt.file_ext = os.path.splitext(file_name)[1][1:].upper()
                    nt.is_cue = True

                    nt.album_artist = cue_main_performer
                    if not cue_main_performer:
                        nt.album_artist = cue_performer
                    nt.artist = cue_performer
                    nt.composer = cue_songwriter
                    nt.genre = cue_genre
                    nt.album = cue_album
                    nt.date = cue_date.replace('"', '')
                    nt.track_number = int(line.strip())
                    if nt.track_number == 1:
                        nt.size = os.path.getsize(nt.fullpath)

                    while True:
                        i += 1
                        if i > len(content) - 1 or content[i].startswith("FILE ") or content[i].strip().startswith("TRACK"):
                            break

                        line = content[i]
                        line = line.strip()

                        if line.startswith("TITLE"):
                            nt.title = get_quoted_from_line(line)
                        if line.startswith("PERFORMER"):
                            nt.artist = get_quoted_from_line(line)
                        if line.startswith("SONGWRITER"):
                            nt.composer = get_quoted_from_line(line)
                        if line.startswith("INDEX 01 ") and ":" in line:
                            line = line[9:]
                            times = line.split(":")
                            nt.start_time = int(times[0]) * 60 + int(times[1]) + int(times[2]) / 100

                    i -= 1
                    cd.append(nt)

            if cd:
                cds.append(cd)


            for cd in cds:

                last_end = None
                end_track = TrackClass()
                end_track.fullpath = cd[-1].fullpath
                tag_scan(end_track)

                # Remove target track if already imported
                for i in reversed(range(len(added))):
                    if pctl.g(added[i]).fullpath == end_track.fullpath:
                        del added[i]

                # Update with with proper length
                for track in reversed(cd):

                    if last_end == None:
                        last_end = end_track.length

                    track.length = last_end - track.start_time
                    track.samplerate = end_track.samplerate
                    track.bitrate = end_track.bitrate
                    track.bit_depth = end_track.bit_depth
                    last_end = track.start_time

            # Add all tracks for import to playlist
            for cd in cds:
                for track in cd:
                    pctl.master_library[track.index] = track
                    if track.fullpath not in cue_list:
                        cue_list.append(track.fullpath)
                    loaded_pathes_cache[track.fullpath] = track.index
                    added.append(track.index)

        except:
            print("Error in processing CUE file")
            #raise

    def add_file(path, force_scan=False):
        # bm.get("add file start")
        global DA_Formats
        global to_got

        if not os.path.isfile(path):
            print("file to import missing")
            return 0

        if os.path.splitext(path)[1][1:] in {"CUE", 'cue'}:
            add_from_cue(path)
            return 0

        if path.lower().endswith('.xspf'):
            print('found XSPF file at: ' + path)
            load_xspf(path)
            return 0

        if path.lower().endswith(".m3u"):
            load_m3u(path)
            return 0

        if path.endswith(".pls"):
            load_pls(path)
            return 0

        if os.path.splitext(path)[1][1:].lower() not in DA_Formats:
            if os.path.splitext(path)[1][1:].lower() in Archive_Formats:
                if not prefs.auto_extract:
                    show_message("You attempted to drop an archive.",
                                 'However the "extract archive" function is not enabled.', mode='info')
                else:
                    type = os.path.splitext(path)[1][1:].lower()
                    split = os.path.splitext(path)
                    target_dir = split[0]
                    if prefs.extract_to_music and music_directory is not None:
                        target_dir = os.path.join(music_directory, os.path.basename(target_dir))
                    # print(os.path.getsize(path))
                    if os.path.getsize(path) > 2e+9:
                        print("Archive file is large!")
                        show_message("Skipping oversize zip file (>2GB)")
                        return 1
                    if not os.path.isdir(target_dir) and not os.path.isfile(target_dir):
                        if type == "zip":
                            try:
                                b = to_got
                                to_got = "ex"
                                gui.update += 1
                                zip_ref = zipfile.ZipFile(path, 'r')

                                zip_ref.extractall(target_dir)
                                zip_ref.close()
                            except RuntimeError as e:
                                to_got = b
                                print("Zip error")
                                if 'encrypted' in e:
                                    show_message("Failed to extract zip archive.",
                                                 "The archive is encrypted. You'll need to extract it manually with the password.",
                                                 mode='warning')
                                else:
                                    show_message("Failed to extract zip archive.",
                                                 "Maybe archive is corrupted? Does disk have enough space and have write permission?",
                                                 mode='warning')
                                return 1
                            except:
                                print("Zip error 2")
                                to_got = b
                                show_message("Failed to extract zip archive.",
                                             "Maybe archive is corrupted? Does disk have enough space and have write permission?",
                                             mode='warning')
                                return 1

                        elif type == 'rar':
                            b = to_got
                            try:
                                to_got = "ex"
                                gui.update += 1
                                line = launch_prefix + "unrar x -y -p- " + shlex.quote(path) + " " + shlex.quote(target_dir) + os.sep
                                result = subprocess.run(shlex.split(line))
                                print(result)
                            except:
                                to_got = b
                                show_message("Failed to extract rar archive.", mode='warning')

                                return 1

                        elif type == '7z':
                            b = to_got
                            try:
                                to_got = "ex"
                                gui.update += 1
                                line = launch_prefix + "7z x -y " + shlex.quote(path) + " -o" + shlex.quote(target_dir) + os.sep
                                result = subprocess.run(shlex.split(line))
                                print(result)
                            except:
                                to_got = b
                                show_message("Failed to extract 7z archive.", mode='warning')

                                return 1

                        upper = os.path.dirname(target_dir)
                        cont = os.listdir(target_dir)
                        new = upper + "/temporaryfolderd"
                        error = False
                        if len(cont) == 1 and os.path.isdir(split[0] + "/" + cont[0]):
                            print("one thing")
                            os.rename(target_dir, new)
                            try:
                                shutil.move(new + "/" + cont[0], upper)
                            except:
                                error = True
                            shutil.rmtree(new)
                            print(new)
                            target_dir = upper + "/" + cont[0]
                            if not os.path.isdir(target_dir):
                                print("Extract error, expected directory not found")

                        if True and not error and prefs.auto_del_zip:
                            print("Moving archive file to trash: " + path)
                            try:
                                send2trash(path)
                            except:
                                show_message("Could not move archive to trash", path, mode='info')


                        to_got = b
                        gets(target_dir)
                        quick_import_done.append(target_dir)
                    # gets(target_dir)

            return 1

        to_got += 1
        gui.update = 1

        path = path.replace('\\', '/')

        if path in loaded_pathes_cache:
            de = loaded_pathes_cache[path]

            if pctl.master_library[de].fullpath in cue_list:
                print("File has an associated .cue file... Skipping")
                return

            added.append(de)

            # if gui.auto_play_import:
            #     pctl.jump(copy.deepcopy(de))
            #
            #     gui.auto_play_import = False
            # bm.get("dupe track")
            return

        time.sleep(0.002)

        # audio = auto.File(path)

        nt = TrackClass()

        nt.index = pctl.master_count
        nt.fullpath = path.replace('\\', '/')
        nt.filename = os.path.basename(path)
        nt.parent_folder_path = os.path.dirname(path.replace('\\', '/'))
        nt.parent_folder_name = get_end_folder(os.path.dirname(path))
        nt.file_ext = os.path.splitext(os.path.basename(path))[1][1:].upper()

        #nt = tag_scan(nt)
        if nt.cue_sheet != "":
            cue_scan(nt.cue_sheet, nt)
            del nt

        else:

            pctl.master_library[pctl.master_count] = nt
            added.append(pctl.master_count)


            if prefs.auto_sort or force_scan:
                tag_scan(nt)
            else:
                after_scan.append(nt)
                tm.ready("worker")

            pctl.master_count += 1

        # bm.get("fill entry")
        if gui.auto_play_import:
            pctl.jump(pctl.master_count - 1)
            gui.auto_play_import = False

    # Count the approx number of files to be imported
    def pre_get(direc):

        global to_get

        to_get = 0
        for root, dirs, files in os.walk(direc):
            to_get += len(files)
            if gui.im_cancel:
                return
            gui.update = 3


    def gets(direc, force_scan=False):

        global DA_Formats

        if os.path.basename(direc) == "__MACOSX":
            return

        try:
            items_in_dir = os.listdir(direc)
            if use_natsort:
                items_in_dir = natsort.os_sorted(items_in_dir)
            else:
                items_in_dir.sort()
        except PermissionError:

            if snap_mode:
                show_message(_("Permission error accessing one or more files."),
                             _("If this location is on external media, see") + " https://github.com/Taiko2k/TauonMusicBox/wiki/Snap-Permissions",
                             mode='bubble')
            else:
                show_message(_("Permission error accessing one or more files"), mode='warning')

            return

        for q in range(len(items_in_dir)):
            if items_in_dir[q][0] == ".":
                continue
            if os.path.isdir(os.path.join(direc, items_in_dir[q])):
                gets(os.path.join(direc, items_in_dir[q]))
            if gui.im_cancel:
                return

        for q in range(len(items_in_dir)):
            if items_in_dir[q][0] == ".":
                continue
            if os.path.isdir(os.path.join(direc, items_in_dir[q])) is False:

                if os.path.splitext(items_in_dir[q])[1][1:].lower() in DA_Formats:

                    if len(items_in_dir[q]) > 2 and items_in_dir[q][0:2] == "._":
                        continue

                    add_file(os.path.join(direc, items_in_dir[q]).replace('\\', '/'), force_scan)

                elif os.path.splitext(items_in_dir[q])[1][1:] in {"CUE", 'cue'}:
                    add_from_cue(os.path.join(direc, items_in_dir[q]).replace('\\', '/'))

            if gui.im_cancel:
                return

    def cache_paths():
        dic = {}
        for key, value in pctl.master_library.items():
            dic[value.fullpath.replace('\\', '/')] = key
        return dic

    # print(pctl.master_library)

    global transcode_list
    global transcode_state
    global album_art_gen
    global cm_clean_db
    global to_got
    global to_get
    global move_in_progress

    active_timer = Timer()
    while True:
        if not after_scan:
            time.sleep(0.1)

        if after_scan or load_orders or \
                artist_list_box.load or \
                artist_list_box.to_fetch or \
                gui.regen_single_id or \
                gui.regen_single > -1 or \
                pctl.after_import_flag or \
                tauon.worker_save_state or \
                move_jobs or \
                cm_clean_db or \
                transcode_list or \
                to_scan or \
                loaderCommandReady:
            active_timer.set()
        elif active_timer.get() > 5:
            return

        if after_scan:
            i = 0
            while after_scan:
                i += 1
                if i > 123:
                        break

                tag_scan(after_scan[0])
                gui.update = 2
                gui.pl_update = 1
                #time.sleep(0.001)
                if pctl.running:
                    del after_scan[0]
                else:
                    break

            album_artist_dict.clear()

        artist_list_box.worker()

        # Update smart playlists
        if gui.regen_single_id is not None:
            regenerate_playlist(pl=-1, silent=True, id=gui.regen_single_id)
            gui.regen_single_id = None

        # Update smart playlists
        if gui.regen_single > -1:
            target = gui.regen_single
            gui.regen_single = -1
            regenerate_playlist(target, silent=True)

        if pctl.after_import_flag and not after_scan and not search_over.active and not loading_in_progress:
            pctl.after_import_flag = False

            for i, plist in enumerate(pctl.multi_playlist):
                if pl_to_id(i) in pctl.gen_codes:
                    code = pctl.gen_codes[pl_to_id(i)]
                    try:
                        if check_auto_update_okay(code, pl=i):
                            if not pl_is_locked(i):
                                print("Reloading smart playlist: " + plist[0])
                                regenerate_playlist(i, silent=True)
                                time.sleep(0.02)
                    except:
                        pass

        if tauon.worker_save_state and \
                not gui.pl_pulse and \
                not loading_in_progress and \
                not to_scan and \
                not plex.scanning and \
                not jellyfin.scanning and \
                not cm_clean_db and \
                not lastfm.scanning_friends and \
                not move_in_progress and \
                (gui.lowered or not window_is_focused() or not gui.mouse_in_window):

            save_state()
            cue_list.clear()
            tauon.worker_save_state = False

        # Folder moving
        if len(move_jobs) > 0:
            gui.update += 1
            move_in_progress = True
            job = move_jobs[0]
            del move_jobs[0]

            if job[0].strip("\\/") == job[1].strip("\\/"):
                show_message("Folder copy error.", "The target and source are the same.", mode='info')
                gui.update += 1
                move_in_progress = False
                continue

            try:
                shutil.copytree(job[0], job[1])
            except:
                move_in_progress = False
                gui.update += 1
                show_message("The folder copy has failed!", 'Some files may have been written.', mode='warning')
                continue

            if job[2] == True:
                try:
                    shutil.rmtree(job[0])

                except:
                    show_message("Something has gone horribly wrong!.", "Could not delete " + job[0], mode='error')
                    gui.update += 1
                    move_in_progress = False
                    return

                show_message("Folder move complete.", "Folder name: " + job[3], mode='done')
            else:
                show_message("Folder copy complete.", "Folder name: " + job[3], mode='done')

            move_in_progress = False
            load_orders.append(job[4])
            gui.update += 1


        # Clean database
        if cm_clean_db is True:
            items_removed = 0

            #old_db = copy.deepcopy(pctl.master_library)
            to_got = 0
            to_get = len(pctl.master_library)
            search_over.results.clear()

            keys = set(pctl.master_library.keys())
            for index in keys:
                time.sleep(0.0001)
                track = pctl.master_library[index]
                to_got += 1

                if to_got % 100 == 0:
                    gui.update = 1
                    
                if not prefs.remove_network_tracks and track.file_ext == "SPTY":
                    
                    for playlist in pctl.multi_playlist:
                        if index in playlist[2]:
                            break
                    else:
                        pctl.purge_track(index)
                        items_removed += 1

                    continue

                if (prefs.remove_network_tracks is False and not track.is_network and not os.path.isfile(track.fullpath)) or \
                (prefs.remove_network_tracks is True and track.is_network):

                    if track.is_network and track.file_ext == "SPTY":
                        continue

                    pctl.purge_track(index)
                    items_removed += 1

            cm_clean_db = False
            show_message("Cleaning complete.", str(items_removed) + " items were removed from the database.", mode='done')
            if album_mode:
                reload_albums(True)
            if gui.combo_mode:
                reload_albums()

            gui.update = 1
            gui.pl_update = 1
            pctl.notify_change()

            search_dia_string_cache.clear()
            search_string_cache.clear()
            search_over.results.clear()

            pctl.notify_change()


        # FOLDER ENC
        if transcode_list:

            try:
                transcode_state = ""
                gui.update += 1

                folder_items = transcode_list[0]

                ref_track_object = pctl.master_library[folder_items[0]]
                ref_album = ref_track_object.album

                # Generate a folder name based on artist and album of first track in batch
                folder_name = encode_folder_name(ref_track_object)

                # If folder contains tracks from multiple albums, use original folder name instead
                for item in folder_items:
                    test_object = pctl.master_library[item]
                    if test_object.album != ref_album:
                        folder_name = ref_track_object.parent_folder_name
                        break

                print("Transcoding folder: " + folder_name)

                # Remove any existing matching folder
                if os.path.isdir(prefs.encoder_output + folder_name):
                    shutil.rmtree(prefs.encoder_output + folder_name)

                # Create new empty folder to output tracks to
                os.makedirs(prefs.encoder_output + folder_name)

                working_folder = prefs.encoder_output + folder_name

                full_wav_out = '"' + prefs.encoder_output + 'output.wav"'
                full_wav_out_p = prefs.encoder_output + 'output.wav'
                full_target_out_p = prefs.encoder_output + 'output.' + prefs.transcode_codec
                full_target_out = '"' + prefs.encoder_output + 'output.' + prefs.transcode_codec + '"'

                if os.path.isfile(full_wav_out_p):
                    os.remove(full_wav_out_p)
                if os.path.isfile(full_target_out_p):
                    os.remove(full_target_out_p)

                if prefs.transcode_codec in ('opus', 'ogg', 'flac', 'mp3'):
                    global core_use
                    cores = os.cpu_count()

                    total = len(folder_items)
                    gui.transcoding_batch_total = total
                    gui.transcoding_bach_done = 0
                    dones = []

                    q = 0
                    while True:
                        if core_use < cores and q < len(folder_items):
                            agg = [[folder_items[q], folder_name]]
                            if agg not in dones:
                                core_use += 1
                                dones.append(agg)
                                loaderThread = threading.Thread(target=transcode_single, args=agg)
                                loaderThread.daemon = True
                                loaderThread.start()

                            q += 1
                            gui.update += 1
                        time.sleep(0.05)
                        if gui.tc_cancel:
                            while core_use > 0:
                                time.sleep(1)
                            break
                        if q == len(folder_items) and core_use == 0:
                            gui.update += 1
                            break

                else:
                    print("Codec error")

                output_dir = prefs.encoder_output + folder_name + "/"
                if prefs.transcode_inplace:
                    remove_target = output_dir.rstrip("/")
                    try:
                        os.remove(remove_target)
                    except:
                        print("Encode folder not removed")
                    reload_metadata(folder_items[0])
                else:
                    album_art_gen.save_thumb(pctl.g(folder_items[0]), (1080, 1080), output_dir + "cover")

                #print(transcode_list[0])

                del transcode_list[0]
                transcode_state = ""
                gui.update += 1

            except:
                transcode_state = "Transcode Error"
                time.sleep(0.2)
                show_message("Transcode failed.", "An error was encountered.", mode='error')
                gui.update += 1
                time.sleep(0.1)
                del transcode_list[0]

            if len(transcode_list) == 0:
                if gui.tc_cancel:
                    gui.tc_cancel = False
                    show_message("The transcode was canceled before completion.", "Incomplete files will remain.",
                                 mode='warning')
                else:
                    line = "Press F9 to show output."
                    if prefs.transcode_codec == 'flac':
                        line = "Note that any associated output picture is a thumbnail and not an exact copy."
                    if not gui.sync_progress:
                        if not gui.message_box:
                            show_message("Encoding complete.", line, mode='done')
                        if system == 'linux' and de_notify_support:
                            g_tc_notify.show()

        if to_scan:
            while to_scan:
                track = to_scan[0]
                star = star_store.full_get(track)
                star_store.remove(track)
                pctl.master_library[track] = tag_scan(pctl.master_library[track])
                star_store.merge(track, star)
                del to_scan[0]
                gui.update += 1
            album_artist_dict.clear()

        if loaderCommandReady is True:
            for order in load_orders:
                if order.stage == 1:
                    if loaderCommand == LC_Folder:
                        to_get = 0
                        to_got = 0
                        loaded_pathes_cache = cache_paths()
                        #pre_get(order.target)
                        if order.force_scan:
                            gets(order.target, force_scan=True)
                        else:
                            gets(order.target)
                    elif loaderCommand == LC_File:
                        loaded_pathes_cache = cache_paths()
                        add_file(order.target)

                    if gui.im_cancel:
                        gui.im_cancel = False
                        to_get = 0
                        to_got = 0
                        load_orders.clear()
                        added = []
                        loaderCommand = LC_Done
                        loaderCommandReady = False
                        break

                    loaderCommand = LC_Done
                    # print("LOAD ORDER")
                    order.tracks = added

                    # Double check for cue dupes
                    for i in reversed(range(len(order.tracks))):
                        if pctl.master_library[order.tracks[i]].fullpath in cue_list:
                            if pctl.master_library[order.tracks[i]].is_cue is False:
                                del order.tracks[i]

                    added = []
                    order.stage = 2
                    loaderCommandReady = False
                    # print("DONE LOADING")
                    break

album_info_cache = {}
perfs = []
album_info_cache_key = (-1, -1)

def get_album_info(position, pl=None):

    playlist = default_playlist
    if pl is not None:
        playlist = pctl.multi_playlist[pl][2]

    global album_info_cache_key

    if album_info_cache_key != (playlist_selected, pctl.playing_object()):  # Premature optimisation?
        album_info_cache.clear()
        album_info_cache_key = (playlist_selected, pctl.playing_object())

    if position in album_info_cache:
        return album_info_cache[position]

    if position > len(playlist) - 1:
        position = len(playlist) - 1
    current = position

    while position > 0 and current > 0:

        if pctl.master_library[playlist[position]].parent_folder_name == pctl.master_library[
                playlist[current - 1]].parent_folder_name:
            current -= 1
            continue
        else:
            break

    album = []
    playing = 0
    select = False

    first_track = pctl.master_library[playlist[current]]
    while current < len(playlist):
        album.append(current)
        if len(pctl.track_queue) > 0 and playlist[current] == pctl.track_queue[pctl.queue_step]:
            playing = 1
        if current == playlist_selected:
            select = True

        if current < len(playlist) - 1 and first_track.parent_folder_name != pctl.master_library[playlist[current + 1]].parent_folder_name:

            if first_track.album and first_track.album == pctl.master_library[playlist[current + 1]].album:
                current += 1
            else:
                break
        else:
            current += 1
    if not album:
        #album = [playlist[len(playlist) - 1]]
        album = [len(playlist) - 1]

    album_info_cache[position] = playing, album, select

    return playing, album, select

tauon.get_album_info = get_album_info

def get_folder_list(index):
    playlist = []

    for item in default_playlist:
        if pctl.master_library[item].parent_folder_name == pctl.master_library[index].parent_folder_name and \
                        pctl.master_library[item].album == pctl.master_library[index].album:
            playlist.append(item)
    return list(set(playlist))


def gal_jump_select(up=False, num=1):

    global playlist_selected
    old_selected = playlist_selected
    old_num = num

    if not default_playlist:
        return

    on = playlist_selected
    if on > len(default_playlist) - 1:
        on = 0
        playlist_selected = 0

    if up is False:

        while num > 0:
            while pctl.master_library[
                default_playlist[on]].parent_folder_name == pctl.master_library[
                    default_playlist[playlist_selected]].parent_folder_name:
                on += 1

                if on > len(default_playlist) - 1:
                    playlist_selected = old_selected
                    return

            playlist_selected = on
            num -= 1
    else:

        if num > 1:
            if playlist_selected > len(default_playlist) - 1:
                playlist_selected = old_selected
                return

            alb = get_album_info(playlist_selected)
            if alb[1][0] in album_dex[:num]:
                playlist_selected = old_selected
                return

        while num > 0:
            alb = get_album_info(playlist_selected)

            if alb[1][0] > -1:
                on = alb[1][0] - 1

            playlist_selected = max(get_album_info(on)[1][0], 0)
            num -= 1


power_tag_colours = ColourGenCache(0.5, 0.8)

class PowerTag:

    def __init__(self):

        self.name = "BLANK"
        self.path = ""
        self.position = 0
        self.colour = None

        self.peak_x = 0
        self.ani_timer = Timer()
        self.ani_timer.force_set(10)

gui.pt_on = Timer()
gui.pt_off = Timer()
gui.pt = 0


def gen_power2():

    tags = {}  # [tag name]: (first position, number of times we saw it)
    tag_list = []

    last = 'a'
    noise = 0

    def key(tag):
        return tags[tag][1]

    for position in album_dex:

        index = default_playlist[position]
        track = pctl.g(index)

        crumbs = track.parent_folder_path.split("/")

        for i, b in enumerate(crumbs):

            if i > 0 and (track.artist in b and track.artist):
                tag = crumbs[i - 1]

                if tag != last:
                    noise += 1
                last = tag

                if tag in tags:
                    tags[tag][1] += 1
                else:
                    tags[tag] = [position, 1, "/".join(crumbs[:i])]
                    tag_list.append(tag)
                break

    if noise > len(album_dex) / 2:
        # print("Playlist is too noisy for power bar.")
        return []

    tag_list_sort = sorted(tag_list, key=key, reverse=True)

    max_tags = round((window_size[1] - gui.panelY - gui.panelBY - 10) // 30 * gui.scale)

    tag_list_sort = tag_list_sort[:max_tags]

    for i in reversed(range(len(tag_list))):
        if tag_list[i] not in tag_list_sort:
            del tag_list[i]

    h = []

    for tag in tag_list:

        if tags[tag][1] > 2:
            t = PowerTag()
            t.path = tags[tag][2]
            t.name = tag.upper()
            t.position = tags[tag][0]
            h.append(t)

    cc = random.random()
    cj = 0.03
    if len(h) < 5:
        cj = 0.11

    cj = 0.5 / max(len(h), 2)

    for item in h:
        item.colour = hsl_to_rgb(cc, 0.8, 0.7)
        cc += cj

    return h


def reload_albums(quiet=False, return_playlist=-1, custom_list=None):
    global album_dex
    global update_layout
    global old_album_pos

    if cm_clean_db:
        # Doing reload while things are being removed may cause crash
        return

    dex = []
    current_folder = ""
    current_album = ""

    if custom_list is not None:
        playlist = custom_list
    else:
        target_pl_no = pctl.active_playlist_viewing
        if return_playlist > -1:
            target_pl_no = return_playlist

        playlist = pctl.multi_playlist[target_pl_no][2]

    for i in range(len(playlist)):
        tr = pctl.master_library[playlist[i]]

        if i == 0:
            dex.append(i)
            current_folder = tr.parent_folder_name
            current_album = tr.album
        else:
            if tr.parent_folder_name != current_folder:
                if tr.album and tr.album == current_album:
                    pass
                else:
                    dex.append(i)
                current_folder = tr.parent_folder_name
                current_album = tr.album

    if return_playlist > -1 or custom_list:
        return dex

    album_dex = dex
    album_info_cache.clear()
    gui.update += 2
    gui.pl_update = 1
    update_layout = True


    if not quiet:
        goto_album(pctl.playlist_playing_position)

    # Generate POWER BAR
    gui.power_bar = gen_power2()
    gui.pt = 0


tauon.reload_albums = reload_albums

# ------------------------------------------------------------------------------------
# WEBSERVER

from t_modules.t_webserve import webserve
from t_modules.t_webserve import webserve2
from t_modules.t_webserve import authserve
from t_modules.t_webserve import controller
from t_modules.t_webserve import stream_proxy

if prefs.enable_web is True:
    webThread = threading.Thread(target=webserve, args=[pctl, prefs, gui, album_art_gen, install_directory, strings, tauon])
    webThread.daemon = True
    webThread.start()

ctlThread = threading.Thread(target=controller, args=[tauon])
ctlThread.daemon = True
ctlThread.start()

if prefs.enable_remote:
    webThread2 = threading.Thread(target=webserve2, args=[pctl, prefs, gui, album_art_gen, install_directory, strings, tauon])
    webThread2.daemon = True
    webThread2.start()


# --------------------------------------------------------------

def star_line_toggle(mode=0):

    if mode == 1:
        return gui.star_mode == 'line'

    if gui.star_mode == 'line':
        gui.star_mode = 'none'
    else:
        gui.star_mode = 'line'

    gui.show_ratings = False

    gui.update += 1
    gui.pl_update = 1

def star_toggle(mode=0):

    if gui.show_ratings:
        if mode == 1:
            return prefs.rating_playtime_stars
        prefs.rating_playtime_stars ^= True

    else:
        if mode == 1:
            return gui.star_mode == 'star'

        if gui.star_mode == 'star':
            gui.star_mode = 'none'
        else:
            gui.star_mode = 'star'

    # gui.show_ratings = False
    gui.update += 1
    gui.pl_update = 1

def heart_toggle(mode=0):

    if mode == 1:
        return gui.show_hearts

    gui.show_hearts ^= True
    #gui.show_ratings = False

    gui.update += 1
    gui.pl_update = 1


def album_rating_toggle(mode=0):

    if mode == 1:
        return gui.show_album_ratings

    gui.show_album_ratings ^= True

    gui.update += 1
    gui.pl_update = 1

def rating_toggle(mode=0):

    if mode == 1:
        return gui.show_ratings

    gui.show_ratings ^= True

    if gui.show_ratings:
        # gui.show_hearts = False
        gui.star_mode = 'none'
        prefs.rating_playtime_stars = True
        if not prefs.write_ratings:
            show_message(_("Note that ratings are stored in the local database and not written to tags."))

    gui.update += 1
    gui.pl_update = 1

def toggle_titlebar_line(mode=0):
    global update_title
    if mode == 1:
        return update_title

    line = window_title
    SDL_SetWindowTitle(t_window, line)
    update_title ^= True
    if update_title:
        update_title_do()


def toggle_meta_persists_stop(mode=0):
    if mode == 1:
        return prefs.meta_persists_stop
    prefs.meta_persists_stop ^= True

def toggle_side_panel_layout(mode=0):
    if mode == 1:
        return prefs.side_panel_layout == 1

    if prefs.side_panel_layout == 1:
        prefs.side_panel_layout = 0
    else:
        prefs.side_panel_layout = 1

def toggle_meta_shows_selected(mode=0):
    if mode == 1:
        return prefs.meta_shows_selected_always
    prefs.meta_shows_selected_always ^= True


def scale1(mode=0):

    if mode == 1:
        if prefs.ui_scale == 1:
            return True
        else:
            return False

    prefs.ui_scale = 1
    pref_box.large_preset()

    if prefs.ui_scale != gui.scale:
        show_message(_("Change will be applied on restart."))


def scale125(mode=0):
    if mode == 1:
        if prefs.ui_scale == 1.25:
            return True
        else:
            return False

    prefs.ui_scale = 1.25
    pref_box.large_preset()

    if prefs.ui_scale != gui.scale:
        show_message(_("Change will be applied on restart."))

def toggle_use_tray(mode=0):
    if mode == 1:
        return prefs.use_tray
    prefs.use_tray ^= True
    if not prefs.use_tray:
        prefs.min_to_tray = False
        gnome.hide_indicator()
    else:
        gnome.show_indicator()


def toggle_text_tray(mode=0):
    if mode == 1:
        return prefs.tray_show_title
    prefs.tray_show_title ^= True
    pctl.notify_update()

def toggle_min_tray(mode=0):
    if mode == 1:
        return prefs.min_to_tray
    prefs.min_to_tray ^= True

def scale2(mode=0):
    if mode == 1:
        if prefs.ui_scale == 2:
            return True
        else:
            return False

    prefs.ui_scale = 2
    pref_box.large_preset()

    if prefs.ui_scale != gui.scale:
        show_message(_("Change will be applied on restart."))



def toggle_borderless(mode=0):
    global draw_border
    global update_layout


    if mode == 1:
        return draw_border

    update_layout = True
    draw_border ^= True

    if draw_border:
        SDL_SetWindowBordered(t_window, False)
    else:
        SDL_SetWindowBordered(t_window, True)



def toggle_break(mode=0):
    global break_enable
    if mode == 1:
        return break_enable ^ True
    else:
        break_enable ^= True
        gui.pl_update = 1


def toggle_scroll(mode=0):
    global scroll_enable
    global update_layout

    if mode == 1:
        if scroll_enable:
            return False
        else:
            return True

    else:
        scroll_enable ^= True
        gui.pl_update = 1
        update_layout = True


def toggle_hide_bar(mode=0):
    if mode == 1:
        return gui.set_bar ^ True
    gui.update_layout()
    gui.set_bar ^= True
    show_message(_("Tip: You can also toggle this from a right-click context menu"))

def toggle_append_total_time(mode=0):
    if mode == 1:
        return prefs.append_total_time
    prefs.append_total_time ^= True
    gui.pl_update = 1
    gui.update += 1

def toggle_append_date(mode=0):
    if mode == 1:
        return prefs.append_date
    prefs.append_date ^= True
    gui.pl_update = 1
    gui.update += 1

def toggle_true_shuffle(mode=0):
    if mode == 1:
        return prefs.true_shuffle
    prefs.true_shuffle ^= True

def toggle_auto_artist_dl(mode=0):
    if mode == 1:
        return prefs.auto_dl_artist_data
    prefs.auto_dl_artist_data ^= True
    for artist, value in list(artist_list_box.thumb_cache.items()):
        if value is None:
            del artist_list_box.thumb_cache[artist]

def toggle_enable_web(mode=0):
    if mode == 1:
        return prefs.enable_web

    prefs.enable_web ^= True

    if prefs.enable_web and not gui.web_running:
        webThread = threading.Thread(target=webserve, args=[pctl, prefs, gui, album_art_gen, install_directory, strings, tauon])
        webThread.daemon = True
        webThread.start()
        show_message(_("Web server starting"), _("External connections will be accepted."), mode='done')

    elif prefs.enable_web is False:
        if tauon.radio_server is not None:
            tauon.radio_server.shutdown()
            gui.web_running = False

        time.sleep(0.25)


def toggle_scrobble_mark(mode=0):
    if mode == 1:
        return prefs.scrobble_mark
    prefs.scrobble_mark ^= True


def toggle_lfm_auto(mode=0):
    if mode == 1:
        return prefs.auto_lfm
    prefs.auto_lfm ^= True
    # if prefs.auto_lfm:
    #     lastfm.hold = False
    # else:
    #     lastfm.hold = True

def toggle_lb(mode=0):
    if mode == 1:
        return lb.enable
    if not lb.enable and not prefs.lb_token:
        show_message("Can't enable this if there's no token.", mode='warning')
        return
    lb.enable ^= True

def toggle_maloja(mode=0):
    if mode == 1:
        return prefs.maloja_enable
    if not prefs.maloja_url or not prefs.maloja_key:
        show_message(_("One or more fields is missing."), mode='warning')
        return
    prefs.maloja_enable ^= True


def toggle_ex_del(mode=0):
    if mode == 1:
        return prefs.auto_del_zip
    prefs.auto_del_zip ^= True
    # if prefs.auto_del_zip is True:
    #     show_message("Caution! This function deletes things!", mode='info', "This could result in data loss if the process were to malfunction.")

def toggle_dl_mon(mode=0):
    if mode == 1:
        return prefs.monitor_downloads

    prefs.monitor_downloads ^= True

def toggle_music_ex(mode=0):
    if mode == 1:
        return prefs.extract_to_music

    prefs.extract_to_music ^= True


def toggle_extract(mode=0):
    if mode == 1:
        return prefs.auto_extract
    prefs.auto_extract ^= True
    if prefs.auto_extract is False:
        prefs.auto_del_zip = False

def toggle_top_tabs(mode=0):
    if mode == 1:
        return prefs.tabs_on_top
    prefs.tabs_on_top ^= True


def toggle_guitar_chords(mode=0):
    if mode == 1:
        return prefs.guitar_chords
    prefs.guitar_chords ^= True


# def toggle_auto_lyrics(mode=0):
#     if mode == 1:
#         return prefs.auto_lyrics
#     prefs.auto_lyrics ^= True


def switch_single(mode=0):
    if mode == 1:
        if prefs.transcode_mode == 'single':
            return True
        else:
            return False
    prefs.transcode_mode = 'single'


def switch_mp3(mode=0):
    if mode == 1:
        if prefs.transcode_codec == 'mp3':
            return True
        else:
            return False
    prefs.transcode_codec = 'mp3'


def switch_ogg(mode=0):
    if mode == 1:
        if prefs.transcode_codec == 'ogg':
            return True
        else:
            return False
    prefs.transcode_codec = 'ogg'


def switch_opus(mode=0):
    if mode == 1:
        if prefs.transcode_codec == 'opus':
            return True
        else:
            return False
    prefs.transcode_codec = 'opus'

def switch_opus_ogg(mode=0):
    if mode == 1:
        if prefs.transcode_opus_as:
            return True
        else:
            return False
    prefs.transcode_opus_as ^= True


def toggle_transcode_output(mode=0):
    if mode == 1:
        if prefs.transcode_inplace:
            return False
        else:
            return True
    prefs.transcode_inplace ^= True
    if prefs.transcode_inplace:
        transcode_icon.colour = [250, 20, 20, 255]
        show_message(
            _("DANGER! This will delete the original files. Keeping a backup is recommended in case of malfunction."),
            _("For safety, this setting will default to off. Embedded thumbnails are not kept so you may want to extract them first."),
            mode='warning')
    else:
        transcode_icon.colour = [239, 74, 157, 255]

def toggle_transcode_inplace(mode=0):
    if mode == 1:
        if prefs.transcode_inplace:
            return True
        else:
            return False

    if gui.sync_progress:
        prefs.transcode_inplace = False
        return

    prefs.transcode_inplace ^= True
    if prefs.transcode_inplace:
        transcode_icon.colour = [250, 20, 20, 255]
        show_message(
        _("DANGER! This will delete the original files. You may want to have backups in case of malfunction."),
            _("For safety, this setting will reset on restart. Embedded thumbnails are not kept so you may want to extract them first."),
            mode='warning')
    else:
        transcode_icon.colour = [239, 74, 157, 255]

def switch_flac(mode=0):
    if mode == 1:
        if prefs.transcode_codec == 'flac':
            return True
        else:
            return False
    prefs.transcode_codec = 'flac'


def toggle_sbt(mode=0):
    if mode == 1:
        return prefs.prefer_bottom_title
    prefs.prefer_bottom_title ^= True

def toggle_bba(mode=0):
    if mode == 1:
        return gui.bb_show_art
    gui.bb_show_art ^= True
    gui.update_layout()

def toggle_use_title(mode=0):
    if mode == 1:
        return prefs.use_title
    prefs.use_title ^= True

def switch_rg_off(mode=0):
    if mode == 1:
        return True if prefs.replay_gain == 0 else False
    prefs.replay_gain = 0

def switch_rg_track(mode=0):
    if mode == 1:
        return True if prefs.replay_gain == 1 else False
    prefs.replay_gain = 0 if prefs.replay_gain == 1 else 1
    #prefs.replay_gain = 1

def switch_rg_album(mode=0):
    if mode == 1:
        return True if prefs.replay_gain == 2 else False
    prefs.replay_gain = 0 if prefs.replay_gain == 2 else 2

def toggle_jump_crossfade(mode=0):
    if mode == 1:
        return True if prefs.use_jump_crossfade else False
    prefs.use_jump_crossfade ^= True

def toggle_pause_fade(mode=0):
    if mode == 1:
        return True if prefs.use_pause_fade else False
    prefs.use_pause_fade ^= True

def toggle_transition_crossfade(mode=0):
    if mode == 1:
        return True if prefs.use_transition_crossfade else False
    prefs.use_transition_crossfade ^= True

def toggle_transition_gapless(mode=0):
    if mode == 1:
        return False if prefs.use_transition_crossfade else True
    prefs.use_transition_crossfade ^= True

def toggle_eq(mode=0):
    if mode == 1:
        return prefs.use_eq
    prefs.use_eq ^= True
    pctl.playerCommand = 'seteq'
    pctl.playerCommandReady = True


key_shiftr_down = False
key_ctrl_down = False
key_rctrl_down = False
key_meta = False
key_ralt = False
key_lalt = False


def reload_backend():
    gui.backend_reloading = True
    print("Reload backend...")
    wait = 0
    pre_state = pctl.stop(True)

    while pctl.playerCommandReady:
        time.sleep(0.01)
        wait += 1
        if wait > 20:
            break
    try:
        tm.player_lock.release()
    except:
        pass

    pctl.playerCommand = "unload"
    pctl.playerCommandReady = True

    wait = 0
    while pctl.playerCommand != 'done':
        time.sleep(0.01)
        wait += 1
        if wait > 200:
            break

    tm.ready_playback()

    if pre_state == 1:
        pctl.revert()
    gui.backend_reloading = False

def set_player_phazor(mode=0):

    if mode == 1:
        return True if prefs.backend == 4 else False

    if prefs.backend != 4:
        prefs.backend = 4
        reload_backend()

def set_player_gstreamer(mode=0):

    if mode == 1:
        return True if prefs.backend == 2 else False

    if prefs.backend != 2:
        prefs.backend = 2
        reload_backend()
        gui.spec = None
        pctl.bass_devices.clear()

def gen_chart():

    try:

        topchart = t_topchart.TopChart(tauon, album_art_gen)

        tracks = []

        source_tracks = pctl.multi_playlist[pctl.active_playlist_viewing][2]

        if prefs.topchart_sorts_played:
            source_tracks = gen_folder_top(0, custom_list=source_tracks)
            dex = reload_albums(quiet=True, custom_list=source_tracks)
        else:
            dex = reload_albums(quiet=True, return_playlist=pctl.active_playlist_viewing)

        for item in dex:
            tracks.append(pctl.g(source_tracks[item]))

        cascade = False
        if prefs.chart_cascade:
            cascade = ((prefs.chart_c1, prefs.chart_c2, prefs.chart_c3),
                       (prefs.chart_d1, prefs.chart_d2, prefs.chart_d3))

        path = topchart.generate(tracks, prefs.chart_bg, prefs.chart_rows, prefs.chart_columns, prefs.chart_text, prefs.chart_font, prefs.chart_tile, cascade)

    except:
        gui.generating_chart = False
        show_message(_("There was an error generating the chart"), "Sorry!", mode='error')
        return

    gui.generating_chart = False

    if path:
        open_file(path)
    else:
        show_message(_("There was an error generating the chart"), "Sorry!", mode='error')
        return

    show_message(_("Chart generated"), mode='done')


class Over:
    def __init__(self):

        global window_size

        self.init2done = False

        self.about_image = asset_loader('v4-a.png')
        self.about_image2 = asset_loader('v4-b.png')
        self.about_image3 = asset_loader('v4-c.png')
        self.about_image4 = asset_loader('v4-d.png')
        self.about_image5 = asset_loader('v4-e.png')
        self.about_image6 = asset_loader('v4-f.png')
        self.title_image = asset_loader("title.png", True)

        # self.tab_width = round(115 * gui.scale)
        self.w = 100
        self.h = 100

        self.box_x = 100
        self.box_y = 100
        self.item_x_offset = round(25 * gui.scale)

        self.current_path = os.path.expanduser('~')
        self.view_offset = 0
        self.ext_ratio = {}
        self.last_db_size = -1

        self.enabled = False
        self.click = False
        self.right_click = False
        self.scroll = 0
        self.lock = False

        self.drives = []

        self.temp_lastfm_user = ""
        self.temp_lastfm_pass = ""
        self.lastfm_input_box = 0

        self.func_page = 0
        self.tab_active = 0
        self.tabs = [
            [_("Function"), self.funcs],
            [_("Audio"), self.audio],
            [_("Tracklist"), self.config_v],
            [_("Theme"), self.theme],
            [_("Window"), self.config_b],
            [_("View"), self.view2],
            [_("Transcode"), self.codec_config],
            [_("Lyrics"), self.lyrics],
            [_("Accounts"), self.last_fm_box],
            [_("Stats"), self.stats],
            [_("About"), self.about]
        ]

        self.stats_timer = Timer()
        self.stats_timer.force_set(1000)
        self.stats_pl_timer = Timer()
        self.stats_pl_timer.force_set(1000)
        self.total_albums = 0
        self.stats_pl = 0
        self.stats_pl_albums = 0
        self.stats_pl_length = 0

        self.ani_cred = 0
        self.cred_page = 0
        self.ani_fade_on_timer = Timer(force=10)
        self.ani_fade_off_timer = Timer(force=10)

        self.device_scroll_bar_position = 0

        self.lyrics_panel = False
        self.account_view = 0
        self.view_view = 0
        self.chart_view = 0
        self.eq_view = False
        self.rg_view = False
        self.sync_view = False

        self.account_text_field = -1

        self.themes = []
        self.view_supporters = False
        self.key_box = TextBox2()
        self.key_box_focused = False

    def theme(self, x0, y0, w0, h0):

        global album_mode_art_size
        global update_layout

        y = y0 + 13 * gui.scale
        x = x0 + 25 * gui.scale

        ddt.text_background_colour = colours.box_background
        ddt.text((x, y), _("Theme"), colours.box_text_label, 12)

        y += 25 * gui.scale

        self.toggle_square(x, y, toggle_auto_bg, _("Use album art as background"))

        y += 23 * gui.scale

        self.toggle_square(x + 10 * gui.scale, y, toggle_auto_bg_strong, _("Stronger"))
        # self.toggle_square(x + 10 * gui.scale, y, toggle_auto_bg_strong1, _("Lo"))
        # self.toggle_square(x + 54 * gui.scale, y, toggle_auto_bg_strong2, _("Md"))
        # self.toggle_square(x + 105 * gui.scale, y, toggle_auto_bg_strong3, _("Hi"))

        y += 23 * gui.scale
        self.toggle_square(x + 10 * gui.scale, y, toggle_auto_bg_blur, _("Blur"))

        y += 23 * gui.scale
        self.toggle_square(x + 10 * gui.scale, y, toggle_auto_bg_showcase, _("Showcase only"))

        y += 23 * gui.scale
        #prefs.center_bg = self.toggle_square(x + 10 * gui.scale, y, prefs.center_bg, _("Always center"))
        prefs.showcase_overlay_texture = self.toggle_square(x + 20 * gui.scale, y, prefs.showcase_overlay_texture, _("Pattern style"))

        y += 25 * gui.scale

        self.toggle_square(x, y, toggle_auto_theme, _("Auto-theme from album art"))

        y += 55 * gui.scale

        square = round(8 * gui.scale)
        border = round(4 * gui.scale)
        outer_border = round(2 * gui.scale)

        #theme_files = get_themes()
        xx = x
        yy = y
        hover_name = None
        for c, theme_name, theme_number in self.themes:

            if theme_name == gui.theme_name:
                rect = [xx - outer_border, yy - outer_border, border * 2 + square * 2 + outer_border * 2, border * 2 + square * 2 + outer_border * 2]
                ddt.rect(rect, colours.box_text_label)

            rect = [xx, yy, border * 2 + square * 2, border * 2 + square * 2]
            ddt.rect(rect, [5, 5, 5, 255])

            rect = grow_rect(rect, 3)
            fields.add(rect)
            if coll(rect):
                hover_name = theme_name
                if self.click:
                    global theme
                    theme = theme_number
                    gui.reload_theme = True

            c1 = c.playlist_panel_background
            c2 = c.artist_playing
            c3 = c.title_playing
            c4 = c.bottom_panel_colour

            if theme_name == "Carbon":
                c1 = c.title_playing
                c2 = c.playlist_panel_background
                c3 = c.top_panel_background

            if theme_name == "Lavender Light":
                c1 = c.tab_background_active

            if theme_name == "Neon Love":
                c2 = c.artist_text
                c4 = [118, 85, 194, 255]
                c1 = c4

            if theme_name == "Sky":
                c2 = c.artist_text

            if theme_name == "Sunken":
                c2 = c.title_text
                c3 = c.artist_text
                c4 = [59, 115, 109, 255]
                c1 = c4

            if c2 == c3 and colour_value(c1) < 200:
                rect = [(xx + border + square) - (square // 2), (yy + border + square) - (square // 2), square, square]
                ddt.rect(rect, c2)
            else:

                # tl
                rect = [xx + border, yy + border, square, square]
                ddt.rect(rect, c1)

                # tr
                rect = [xx + border + square, yy + border, square, square]
                ddt.rect(rect, c2)

                # bl
                rect = [xx + border, yy + border + square, square, square]
                ddt.rect(rect, c3)

                # br
                rect = [xx + border + square, yy + border + square, square, square]
                ddt.rect(rect, c4)

            yy += round(27 * gui.scale)
            if yy > y + 40 * gui.scale:
                yy = y
                xx += round(27 * gui.scale)

        name = gui.theme_name
        if hover_name:
            name = hover_name
        ddt.text((x, y - 23 * gui.scale), name, colours.box_text_label, 214)
        #
        if gui.theme_name == "Neon Love" and not hover_name:
            x += 95 * gui.scale
            y -= 23 * gui.scale
            # x += 165 * gui.scale
            # y += -19 * gui.scale

            link_pa = draw_linked_text((x, y), _("Based on") + " " + "https://love.holllo.cc/", colours.box_text_label, 312, replace="love.holllo.cc")
            link_activate(x, y, link_pa, click=self.click)

    def rg(self, x0, y0, w0, h0):
        y = y0 + 55 * gui.scale
        x = x0 + 130 * gui.scale

        if self.button(x - 110 * gui.scale, y + 180 * gui.scale, _("Return"), width=75 * gui.scale):
            self.rg_view = False

        y = y0 + round(15 * gui.scale)
        x = x0 + round(50 * gui.scale)

        ddt.text((x, y), _("ReplayGain"), colours.box_text_label, 14)
        y += round(25 * gui.scale)

        self.toggle_square(x, y, switch_rg_off, _("Off"))
        y += round(22 * gui.scale)
        self.toggle_square(x, y, switch_rg_album, _("Preserve album dynamics"))
        y += round(22 * gui.scale)
        self.toggle_square(x, y, switch_rg_track, _("Tracks equal loudness"))

        y += round(25 * gui.scale)
        ddt.text((x, y), _("Will only have effect if ReplayGain metadata is present."), colours.box_text_label, 12)
        y += round(26 * gui.scale)

        ddt.text((x, y), _("Pre-amp"), colours.box_text_label, 14)
        y += round(26 * gui.scale)

        sw = round(170 * gui.scale)
        sh = round(2 * gui.scale)

        slider = (x, y, sw, sh)

        gh = round(14 * gui.scale)
        gw = round(8 * gui.scale)
        grip = [0, y - (gh // 2), gw, gh]

        grip[0] = x

        bp = prefs.replay_preamp + 15

        grip[0] += (bp / 30 * sw)

        m1 = (x, y, sh, sh * 2)
        m2 = ((x + sw // 2), y, sh, sh * 2)
        m3 = ((x + sw), y, sh, sh * 2)

        if coll(grow_rect(slider, 15)) and mouse_down:
            bp = (mouse_position[0] - x) / sw * 30
            gui.update += 1

        bp = round(bp)
        bp = max(bp, 0)
        bp = min(bp, 30)
        prefs.replay_preamp = bp - 15

        # grip[0] += (bp / 30 * sw)

        ddt.rect(slider, colours.box_text_border)
        ddt.rect(m1, colours.box_text_border)
        ddt.rect(m2, colours.box_text_border)
        ddt.rect(m3, colours.box_text_border)
        ddt.rect(grip, colours.box_text_label)

        text = f"{prefs.replay_preamp} dB"
        if prefs.replay_preamp > 0:
            text = "+" + text

        colour = colours.box_sub_text
        if prefs.replay_preamp == 0:
            colour = colours.box_text_label
        ddt.text((x + sw + round(14 * gui.scale), y - round(8 * gui.scale)), text, colour, 11)
        # print(prefs.replay_preamp)

        y += round(18 * gui.scale)
        ddt.text((x, y, 4, 310 * gui.scale, 300 * gui.scale), _("Lower pre-amp values improve normalisation but will require a higher system volume."), colours.box_text_label, 12)

    def eq(self, x0, y0, w0, h0):

        y = y0 + 55 * gui.scale
        x = x0 + 130 * gui.scale

        if self.button(x - 110 * gui.scale, y + 180 * gui.scale, _("Return"), width=75 * gui.scale):
            self.eq_view = False

        base_dis = 160 * gui.scale
        center = base_dis // 2
        width = 25 * gui.scale

        range = 12

        self.toggle_square(x - 90 * gui.scale, y - 35 * gui.scale, toggle_eq, _("Enable"))

        ddt.text((x - 17 * gui.scale, y + 2 * gui.scale), "+", colours.grey(130), 16)
        ddt.text((x - 17 * gui.scale, y + base_dis - 15 * gui.scale), "-", colours.grey(130), 16)

        for i, q in enumerate(prefs.eq):

            bar = [x, y, width, base_dis]

            ddt.rect(bar, [255, 255, 255, 20])

            bar[0] -= 2 * gui.scale
            bar[1] -= 10 * gui.scale
            bar[2] += 4 * gui.scale
            bar[3] += 20 * gui.scale

            if coll(bar):

                if mouse_down:
                    target = mouse_position[1] - y - center
                    target = (target / center) * range
                    if target > range:
                        target = range
                    if target < range * -1:
                        target = range * -1
                    if -0.1 < target < 0.1:
                        target = 0

                    prefs.eq[i] = target

                    pctl.playerCommand = 'seteq'
                    pctl.playerCommandReady = True

                if self.right_click:
                    prefs.eq[i] = 0
                    pctl.playerCommand = 'seteq'
                    pctl.playerCommandReady = True


            start = (q / range) * center

            bar = [x, y + center, width, start]

            ddt.rect(bar, [100, 200, 100, 255])

            x += round(29 * gui.scale)

    def audio(self, x0, y0, w0, h0):

        global mouse_down

        ddt.text_background_colour = colours.box_background
        y = y0 + 40 * gui.scale
        x = x0 + 20 * gui.scale

        if self.eq_view:
            self.eq(x0, y0, w0, h0)
            return

        if self.rg_view:
            self.rg(x0, y0, w0, h0)
            return

        colour = colours.box_sub_text

        #if system == "linux":
        pha_detected = os.path.isfile(os.path.join(pctl.install_directory, "lib/libphazor.so"))
        if pha_detected:
            x += round(20 * gui.scale)
            #y += round(19 * gui.scale)

            ddt.text((x, y - 25 * gui.scale), "PHAzOR", colour, 213)
            self.toggle_square(x - 20 * gui.scale, y - 24 * gui.scale, set_player_phazor, "                          ")

            x += round(100 * gui.scale)
            if not macos:
                ddt.text((x, y - 25 * gui.scale), "GStreamer", colour, 213)
                self.toggle_square(x - 20 * gui.scale, y - 24 * gui.scale, set_player_gstreamer, "                          ")



        else:
            prefs.backend = 2
            ddt.text((x, y - 25 * gui.scale), "GStreamer", colour, 213)
            #self.toggle_square(x - 20 * gui.scale, y - 24 * gui.scale, set_player_gstreamer, "                          ")

        if prefs.backend == 4:

            y = y0 + 44 * gui.scale
            x = x0 + 20 * gui.scale

            x += round(2 * gui.scale)
            #ddt.text((x, y), "Phazor is an alternative backend", colours.box_text_label, 12)
            y += round(17 * gui.scale)
            #ddt.text((x, y), "in beta testing stage. Best suited for", colours.box_text_label, 12)
            y += round(17 * gui.scale)
            #ddt.text((x, y), "playing FLAC, MP3, Vorbis and Opus.", colours.box_text_label, 12)

            y += round(35 * gui.scale)
            # ddt.text((x, y), "Seek mode", colours.box_text_label, 12)
            # y += round(22 * gui.scale)
            #
            # prefs.pa_fast_seek = self.toggle_square(x, y, prefs.pa_fast_seek ^ True, "Smooth") ^ True
            # prefs.pa_fast_seek = self.toggle_square(x + 90 * gui.scale, y, prefs.pa_fast_seek, "Fast")

            self.toggle_square(x, y, toggle_pause_fade, _("Use fade on pause/stop"))
            y += round(23 * gui.scale)
            self.toggle_square(x, y, toggle_jump_crossfade, _("Use fade on track jump"))

            y += round(35 * gui.scale)
            if self.button(x, y, _("ReplayGain")):
                mouse_down = False
                self.rg_view = True


            y += round(57 * gui.scale)
            ddt.text((x, y), "If you experience cracking audio, try ", colours.box_text_label, 12)
            y += round(17 * gui.scale)
            ddt.text((x, y), "increase output buffer.", colours.box_text_label, 12)
            y += round(0 * gui.scale)
            prefs.device_buffer = self.slide_control(x + round(270 * gui.scale), y, _("Output buffer"), 'ms', prefs.device_buffer, 10,
                                                     500, 10, self.reload_device)

            if prefs.device_buffer > 100:
                prefs.pa_fast_seek = True
            else:
                prefs.pa_fast_seek = False

            y = y0 + 37 * gui.scale
            x = x0 + 270 * gui.scale
            ddt.text_background_colour = colours.box_background
            ddt.text((x, y - 22 * gui.scale), _("Set audio output device"), colours.box_text_label, 212)

            self.device_scroll_bar_position -= pref_box.scroll
            if self.device_scroll_bar_position < 0:
                self.device_scroll_bar_position = 0
            if self.device_scroll_bar_position > len(prefs.phazor_devices) - 11 > 11:
                self.device_scroll_bar_position = len(prefs.phazor_devices) - 11

            if len(prefs.phazor_devices) > 13:
                self.device_scroll_bar_position = device_scroll.draw(x + 250 * gui.scale, y, 11, 180,
                                                                     self.device_scroll_bar_position,
                                                                     len(prefs.phazor_devices) - 11, click=self.click)

            i = 0
            reload = False
            for f_name, s_name in prefs.phazor_devices.items():

                if i < self.device_scroll_bar_position:
                    continue
                if y > self.box_y + self.h - 40 * gui.scale:
                    break

                rect = (x, y + 4 * gui.scale, 245 * gui.scale, 13)

                if self.click and coll(rect):
                    prefs.phazor_device_selected = s_name
                    reload = True

                line = trunc_line(f_name, 10, 245 * gui.scale)

                fields.add(rect)

                if prefs.phazor_device_selected == s_name:
                    ddt.text((x, y), line, colours.box_sub_text, 10)
                    ddt.text((x - 12 * gui.scale, y + 1 * gui.scale), ">", colours.box_sub_text, 213)
                else:
                    if coll(rect):
                        ddt.text((x, y), line, colours.box_sub_text, 10)
                    else:
                        ddt.text((x, y), line, colours.box_text_label, 10)
                y += 14 * gui.scale
                i += 1

            if reload:
                reload_backend()

        # Gstreamer
        if prefs.backend == 2:

            y = y0 + 45 * gui.scale
            x = x0 + 29 * gui.scale


            reload = False
            bk_gain = prefs.replay_gain

            y += round(15 * gui.scale)
            if not prefs.gst_use_custom_output:
                if self.button(x, y, _("ReplayGain")):
                    mouse_down = False
                    self.rg_view = True

            else:
                y += round(66 * gui.scale)


            y += round(35 * gui.scale)

            if self.button(x, y, "EQ", width=50*gui.scale):
                self.eq_view = True

            y += 35 * gui.scale
            self.toggle_square(x, y, toggle_pause_fade, _("Use fade on pause/stop"))

            if bk_gain != prefs.replay_gain:
                reload = True

            y += 30 * gui.scale
            prefs.gst_use_custom_output = self.toggle_square(x, y, prefs.gst_use_custom_output, _("Customise GStreamer output"))
            y += 22 * gui.scale

            if prefs.gst_use_custom_output:
                rect = (x, y, 400 * gui.scale, 22 * gui.scale)
                ddt.rect(rect, colours.grey(8))
                bk = ddt.text_background_colour
                ddt.text_background_colour = colours.grey(8)
                gst_output_field.text = prefs.gst_output
                gst_output_field.draw(x + 5 * gui.scale, y, colours.grey(180), width=rect[2] - 8 * gui.scale, click=self.click)
                ddt.text_background_colour = bk

                self.button(x + rect[2] + 15 * gui.scale, y, _("Reload"), reload_backend)

            y = y0 + 37 * gui.scale
            x = x0 + 270 * gui.scale

            ddt.text_background_colour = colours.box_background

            if not prefs.gst_use_custom_output:
                ddt.text((x, y - 22 * gui.scale), _("Set audio output device"), colours.box_text_label, 212)

                self.device_scroll_bar_position -= pref_box.scroll
                if self.device_scroll_bar_position < 0:
                    self.device_scroll_bar_position = 0
                if self.device_scroll_bar_position > len(pctl.gst_devices) - 11 > 11:
                    self.device_scroll_bar_position = len(pctl.gst_devices) - 11

                if len(pctl.gst_devices) > 13:
                    self.device_scroll_bar_position = device_scroll.draw(x + 250 * gui.scale, y, 11, 180, self.device_scroll_bar_position, len(pctl.gst_devices) - 11, click=self.click)


                for i, name in enumerate(pctl.gst_devices):

                    if i < self.device_scroll_bar_position:
                        continue
                    if y > self.box_y + self.h - 40 * gui.scale:
                        break

                    rect = (x, y + 4 * gui.scale, 245 * gui.scale, 13)

                    if self.click and coll(rect):
                        prefs.gst_device = name
                        reload = True
                    #     prefs.last_device = item[0]
                    #     pctl.playerCommandReady = True
                    #     pctl.playerCommand = "setdev"

                    line = trunc_line(name, 10, 245 * gui.scale)

                    fields.add(rect)

                    if prefs.gst_device == name:
                        ddt.text((x, y), line, colours.box_sub_text, 10)
                        ddt.text((x - 12 * gui.scale, y + 1 * gui.scale), ">", colours.box_sub_text, 213)
                    else:
                        if coll(rect):
                            ddt.text((x, y), line, colours.box_sub_text, 10)
                        else:
                            ddt.text((x, y), line, colours.box_text_label, 10)
                    y += 14 * gui.scale

            if not prefs.gst_use_custom_output:
                prefs.gst_output = prefs.gen_gst_out()
                gst_output_field.text = prefs.gst_output
            else:
                prefs.gst_output = gst_output_field.text

            if reload:
                reload_backend()


    def reload_device(self, _):

        pctl.playerCommand = 'reload'
        pctl.playerCommandReady = True

    def toggle_lyrics_view(self):
        self.lyrics_panel ^= True

    def lyrics(self, x0, y0, w0, h0):

        x = x0 + 25 * gui.scale
        y = y0 - 10 * gui.scale
        y += 30 * gui.scale

        ddt.text_background_colour = colours.box_background

        #self.toggle_square(x, y, toggle_auto_lyrics, _("Auto search lyrics"))
        if prefs.auto_lyrics:
            if prefs.auto_lyrics_checked:
                if self.button(x, y, "Reset failed list"):
                    prefs.auto_lyrics_checked.clear()
            y += 30 * gui.scale
        self.toggle_square(x, y, toggle_guitar_chords, _("Enable chord lyrics"))

        y += 40 * gui.scale
        ddt.text((x, y), _("Sources:"), colours.box_text_label, 11)
        y += 23 * gui.scale

        for name in lyric_sources.keys():
            enabled = name in prefs.lyrics_enables
            title = _(name)
            if name in uses_scraping:
                title += "*"
            new = self.toggle_square(x, y, enabled, title)
            y += round(23 * gui.scale)
            if new != enabled:
                if enabled:
                    prefs.lyrics_enables.clear()
                else:
                    prefs.lyrics_enables.append(name)

        y += round(6 * gui.scale)
        ddt.text((x + 12 * gui.scale, y), _("*Uses scraping. Enable at your own discretion."),
                 colours.box_text_label, 11)
        y += 20 * gui.scale
        ddt.text((x + 12 * gui.scale, y), _("Tip: The order enabled will be the order searched."),
                 colours.box_text_label, 11)
        y += 20 * gui.scale


    def view2(self, x0, y0, w0, h0):

        x = x0 + 25 * gui.scale
        y = y0 + 20 * gui.scale

        ddt.text_background_colour = colours.box_background

        ddt.text((x, y), _("Metadata side panel"), colours.box_text_label, 12)

        y += 25 * gui.scale
        self.toggle_square(x, y, toggle_side_panel_layout, _("Use centered style"))
        y += 25 * gui.scale
        old = prefs.zoom_art
        prefs.zoom_art = self.toggle_square(x, y, prefs.zoom_art, _("Zoom album art to fit"))
        if prefs.zoom_art != old:
            album_art_gen.clear_cache()

        global album_mode_art_size
        global update_layout
        y += 35 * gui.scale
        ddt.text((x, y), _("Gallery"), colours.box_text_label, 12)

        y += 25 * gui.scale
        # self.toggle_square(x, y, toggle_dim_albums, "Dim gallery when playing")
        self.toggle_square(x, y, toggle_gallery_click, _("Single click to play"))
        y += 25 * gui.scale
        self.toggle_square(x, y, toggle_galler_text, _("Show titles"))
        y += 25 * gui.scale
        # self.toggle_square(x, y, toggle_gallery_row_space, _("Increase row spacing"))
        # y += 25 * gui.scale
        prefs.center_gallery_text = self.toggle_square(x + round(10 * gui.scale), y, prefs.center_gallery_text, _("Center alignment"))


        y += 30 * gui.scale

        #y += 25 * gui.scale

        x -= 80 * gui.scale
        x += ddt.get_text_w( _("Thumbnail size"), 312)
        #x += 20 * gui.scale

        if album_mode_art_size < 160:
            self.toggle_square(x + 235 * gui.scale, y + 2 * gui.scale, toggle_gallery_thin, _("Prefer thinner padding"))

        #ddt.text((x, y), _("Gallery art size"), colours.grey(220), 11)

        album_mode_art_size = self.slide_control(x + 25 * gui.scale, y, _("Thumbnail size"), "px", album_mode_art_size, 70, 400, 10, img_slide_update_gall)



    def funcs(self, x0, y0, w0, h0):

        x = x0 + 25 * gui.scale
        y = y0 - 10 * gui.scale

        ddt.text_background_colour = colours.box_background

        if self.func_page == 0:

            y += 23 * gui.scale

            self.toggle_square(x, y, toggle_enable_web,
                               _("Ready broadcaster"), subtitle=_("Start web server for broadcasting"))

            if toggle_enable_web(1):

                link_pa2 = draw_linked_text((x + 320 * gui.scale, y - 1 * gui.scale), f"http://localhost:{str(prefs.metadata_page_port)}/radio", colours.grey_blend_bg(190), 13)
                link_rect2 = [x + 320 * gui.scale, y - 1 * gui.scale, link_pa2[1], 20 * gui.scale]
                fields.add(link_rect2)

                if coll(link_rect2):
                    if not self.click:
                        gui.cursor_want = 3

                    if self.click:
                        webbrowser.open(link_pa2[2], new=2, autoraise=True)

            y += 38 * gui.scale

            self.toggle_square(x, y, toggle_auto_artist_dl,
                               _("Auto fetch artist data"), subtitle=_("Downloads data in background when artist panel is open"))

            y += 38 * gui.scale
            prefs.always_auto_update_playlists = self.toggle_square(x, y, prefs.always_auto_update_playlists, _("Auto regenerate playlists"),
                                                                    subtitle=_("Generated playlists reload when re-entering"))


            y += 38 * gui.scale
            self.toggle_square(x, y, toggle_top_tabs, _("Tabs in top panel"), subtitle=_("Uncheck to disable the tab pin function"))


            y += 45 * gui.scale

            wa = ddt.get_text_w(_("Open config file"), 211) + 10 * gui.scale
            #wb = ddt.get_text_w(_("Open keymap file"), 211) + 10 * gui.scale
            wc = ddt.get_text_w(_("Open data folder"), 211) + 10 * gui.scale

            ww = max(wa, wc)

            self.button(x, y, _("Open config file"), open_config_file, width=ww)
            bg = None
            if gui.opened_config_file:
                bg = [90, 50, 130, 255]
                self.button(x + ww + 10 * gui.scale, y, _("Reload"), reload_config_file, bg=bg)

            y += 30 * gui.scale

            self.button(x, y, _("Open data folder"), open_data_directory, ww)

        elif self.func_page == 1:
            y += 23 * gui.scale
            ddt.text((x, y), _("Enable/Disable track context menu functions:"), colours.box_text_label, 11)
            y += 25 * gui.scale

            self.toggle_square(x, y, toggle_wiki, _("Wikipedia artist search"))
            y += 23 * gui.scale
            self.toggle_square(x, y, toggle_rym, _("Sonemic artist search"))
            y += 23 * gui.scale
            self.toggle_square(x, y, toggle_band, _("Bandcamp artist page search"))
            # y += 23 * gui.scale
            # self.toggle_square(x, y, toggle_gimage, _("Google image search"))
            y += 23 * gui.scale
            self.toggle_square(x, y, toggle_gen, _("Genius track search"))
            y += 23 * gui.scale
            self.toggle_square(x, y, toggle_transcode, _("Transcode folder"))
            y += 23 * gui.scale
            self.toggle_square(x, y, toggle_chromecast, _("Chromecast this"))


        elif self.func_page == 2:
            y += 23 * gui.scale
            #ddt.text((x, y), _("Auto download monitor and archive extractor"), colours.box_text_label, 11)
            #y += 25 * gui.scale
            self.toggle_square(x, y, toggle_extract, _("Extract archives"),
                               subtitle=_("Extracts zip archives on drag and drop"))
            y += 38 * gui.scale
            self.toggle_square(x + 10 * gui.scale, y, toggle_dl_mon, _("Enable download monitor"),
                               subtitle=_("One click import new archives and folders from downloads folder"))
            y += 38 * gui.scale
            self.toggle_square(x + 10 * gui.scale, y, toggle_ex_del, _("Trash archive after extraction"))
            y += 23 * gui.scale
            self.toggle_square(x + 10 * gui.scale, y, toggle_music_ex, _("Always extract to Music folder"))

            y += 38 * gui.scale
            self.toggle_square(x, y, toggle_use_tray, _("Show icon in system tray"))

            y += 25 * gui.scale
            self.toggle_square(x + round(10 * gui.scale), y, toggle_min_tray, _("Close to tray"))

            y += 25 * gui.scale
            self.toggle_square(x + round(10 * gui.scale), y, toggle_text_tray, _("Show title text"))


        elif self.func_page == 3:
            y += 23 * gui.scale
            old = prefs.enable_remote
            prefs.enable_remote = self.toggle_square(x, y, prefs.enable_remote, _("Enable remote control"),
                               subtitle=_("Change requires restart"))
            y += 35 * gui.scale

            if prefs.enable_remote and prefs.enable_remote != old:
                show_message("Notice: This API is not security hardened.",
                             "Only enable in a trusted LAN and do not expose port (7814) to the internet", mode="warning")

            y += 12 * gui.scale
            old = prefs.block_suspend
            prefs.block_suspend = self.toggle_square(x, y, prefs.block_suspend, _("Block suspend"),
                                                     subtitle=_("Prevent system suspend during playback"))

            y += 46 * gui.scale
            old = prefs.auto_rec
            prefs.auto_rec = self.toggle_square(x, y, prefs.auto_rec, _("Record Radio"),
                                                    subtitle=_("Record and split songs when playing internet radio"))
            if prefs.auto_rec != old and prefs.auto_rec:
                show_message(_("Tracks will now be recorded. Restart any playback for change to take effect."), _("Tracks will be saved to \"Saved Radio Tracks\" playlist."),
                             mode="info")

            if tauon.update_play_lock is None:
                prefs.block_suspend = False
                # if flatpak_mode:
                #     show_message("Sandbox support not implemented")
            elif old != prefs.block_suspend:
                tauon.update_play_lock()

            y += 50 * gui.scale
            ddt.text((x, y), "Discord", colours.box_text_label, 11)
            y += 25 * gui.scale
            old = prefs.discord_enable
            prefs.discord_enable = self.toggle_square(x, y, prefs.discord_enable, _("Enable Discord Rich Presence"))

            if flatpak_mode:
                if self.button(x + 215 * gui.scale, y, _("?")):
                    show_message(_("For troubleshooting Discord RP"),
                                 "https://github.com/Taiko2k/TauonMusicBox/wiki/Discord-RP", mode="link")

            if prefs.discord_enable and not old:
                if snap_mode:
                    show_message("Sorry, this feature is unavailable with snap", mode="error")
                    prefs.discord_enable = False
                elif not discord_allow:
                    show_message("Missing dependency python-pypresence")
                    prefs.discord_enable = False
                else:
                    hit_discord()

            if old and not prefs.discord_enable:
                if prefs.discord_active:
                    prefs.disconnect_discord = True

            y += 22 * gui.scale
            text = "Disabled"
            if prefs.discord_enable:
                text = gui.discord_status
            ddt.text((x, y), f"Status: {text}", colours.box_text, 11)

        # Switcher
        pages = 4
        x = x0 + round(23 * gui.scale)
        y = (y0 + h0) - round(31 * gui.scale)
        ww = round(31 * gui.scale)

        for p in range(pages):
            if self.button2(x, y, str(p + 1), width=ww, center_text=True, force_on=self.func_page == p):
                self.func_page = p
            x += ww

        # self.button(x, y, _("Open keymap file"), open_keymap_file, width=wc)

    def button(self, x, y, text, plug=None, width=0, bg=None):


        w = width
        if w == 0:
            w = ddt.get_text_w(text, 211) + round(10 * gui.scale)

        h = round(20 * gui.scale)
        border_size = round(2 * gui.scale)

        rect = (round(x), round(y), round(w), round(h))
        rect2 = (rect[0] - border_size, rect[1] - border_size, rect[2] + border_size * 2, rect[3] + border_size * 2)

        if bg is None:
            bg = colours.box_background

        real_bg = bg
        hit = False

        ddt.rect(rect2, colours.box_check_border)
        ddt.rect(rect, bg)

        fields.add(rect)
        if coll(rect):
            ddt.rect(rect, [255, 255, 255, 15])
            real_bg = alpha_blend([255, 255, 255, 15], bg)
            ddt.text((x + int(w / 2), rect[1] + 1 * gui.scale, 2), text, colours.box_title_text, 211, bg=real_bg)
            if self.click:
                hit = True
                if plug is not None:
                    plug()
        else:
            ddt.text((x + int(w / 2), rect[1] + 1 * gui.scale, 2), text, colours.box_sub_text, 211, bg=real_bg)

        return hit

    def button2(self, x, y, text, width=0, center_text=False, force_on=False):
        w = width
        if w == 0:
            w = ddt.get_text_w(text, 211) + 10 * gui.scale
        rect = (x, y, w, 20 * gui.scale)

        bg_colour = colours.box_button_background
        real_bg = bg_colour

        ddt.rect(rect, bg_colour)
        fields.add(rect)
        hit = False

        text_position = (x + int(7 * gui.scale), rect[1] + 1 * gui.scale)
        if center_text:
            text_position = (x + rect[2] // 2, rect[1] + 1 * gui.scale, 2)

        if coll(rect) or force_on:
            ddt.rect(rect, colours.box_button_background_highlight)
            bg_colour = colours.box_button_background
            real_bg = alpha_blend( colours.box_button_background_highlight, bg_colour)
            ddt.text(text_position, text, colours.box_button_text_highlight, 211, bg=real_bg)
            if self.click and not force_on:
                hit = True
        else:
            ddt.text(text_position, text, colours.box_button_text, 211, bg=real_bg)
        return hit

    def toggle_square(self, x, y, function, text, click=False, subtitle=""):

        x = round(x)
        y = round(y)

        border = round(2 * gui.scale)
        gap = round(2 * gui.scale)
        inner_square = round(6 * gui.scale)

        full_w = border * 2 + gap * 2 + inner_square

        if subtitle:
            le = ddt.text((x + 20 * gui.scale, y - 1 * gui.scale), text, colours.box_text, 13)
            se = ddt.text((x + 20 * gui.scale, y + 14 * gui.scale), subtitle, colours.box_text_label, 13)
            hit_rect = (x - 10 * gui.scale, y - 3 * gui.scale, max(le, se) + 30 * gui.scale, 34 * gui.scale)
            y += round(8 * gui.scale)

        else:
            le = ddt.text((x + 20 * gui.scale, y - 1 * gui.scale), text, colours.box_text, 13)
            hit_rect = (x - 10 * gui.scale, y - 3 * gui.scale, le + 30 * gui.scale, 22 * gui.scale)

        # Border outline
        ddt.rect_a((x, y), (full_w, full_w), colours.box_check_border)
        # Inner background
        ddt.rect_a((x + border, y + border), (gap * 2 + inner_square, gap * 2 + inner_square),
                   alpha_blend([255, 255, 255, 14], colours.box_background))

        # Check if box clicked
        clicked = False
        if (self.click or click) and coll(hit_rect):
            clicked = True

        # There are two mode, function type, and passthrough bool type
        active = False
        if type(function) is bool:
            active = function
        else:
            active = function(1)

        if clicked:
            if type(function) is bool:
                active ^= True
            else:
                function()
                active = function(1)

        # Draw inner check mark if enabled
        if active:
            ddt.rect_a((x + border + gap, y + border + gap), (inner_square, inner_square), colours.toggle_box_on)

        return active

    def last_fm_box(self, x0, y0, w0, h0):

        x = x0 + round(20 * gui.scale)
        y = y0 + round(15 * gui.scale)

        ddt.text_background_colour = colours.box_background
        if last_fm_enable:
            text = "Last.fm"
            if prefs.use_libre_fm:
                text = "Libre.fm"
            if self.button2(x, y, text, width=84*gui.scale):
                self.account_view = 1
            self.toggle_square(x + 105 * gui.scale, y + 2 * gui.scale, toggle_lfm_auto, _("Enable"))

        y += 28 * gui.scale

        if self.button2(x, y, "ListenBrainz", width=84*gui.scale):
            self.account_view = 2
        self.toggle_square(x + 105 * gui.scale, y + 2 * gui.scale, toggle_lb, _("Enable"))

        y += 28 * gui.scale

        if self.button2(x, y, "Maloja", width=84*gui.scale):
            self.account_view = 9
        self.toggle_square(x + 105 * gui.scale, y + 2 * gui.scale, toggle_maloja, _("Enable"))

        y += 28 * gui.scale

        if self.button2(x, y, "Discogs", width=84*gui.scale):
            self.account_view = 3

        y += 28 * gui.scale

        if self.button2(x, y, "fanart.tv", width=84*gui.scale):
            self.account_view = 4

        y += 28 * gui.scale



        y += 15 * gui.scale

        if self.button2(x, y, "koel", width=84*gui.scale):
            self.account_view = 6


        if self.button2(x + round(95 * gui.scale), y, "Jellyfin", width=84*gui.scale):
            self.account_view = 10

        y += 28 * gui.scale

        if self.button2(x, y, "Airsonic", width=84*gui.scale):
            self.account_view = 7

        if self.button2(x + round(95 * gui.scale), y, "PLEX", width=84*gui.scale):
            self.account_view = 5

        y += 41 * gui.scale

        if prefs.spot_mode or key_shift_down:
            if self.button2(x, y, "Spotify", width=84*gui.scale):
                self.account_view = 8

            prefs.spot_mode = self.toggle_square(x + 105 * gui.scale, y + 2 * gui.scale, prefs.spot_mode, _("Enable"))

        if self.account_view in (9, 2, 1):
            self.toggle_square(x0 + 230 * gui.scale, y + 2 * gui.scale, toggle_scrobble_mark, _("Show threshold marker"))

        x = x0 + 230 * gui.scale
        y = y0 + round(20 * gui.scale)

        if self.account_view == 9:

            ddt.text((x, y), _('Maloja Server'), colours.box_sub_text, 213)
            if self.button(x + 260 * gui.scale, y, _("?")):
                show_message(_("Maloja is a self-hosted scrobble server."), _("See here to lean more: %s") % "https://github.com/krateng/maloja", mode="link")

            if inp.key_tab_press:
                self.account_text_field += 1
                if self.account_text_field > 2:
                    self.account_text_field = 0

            field_width = round(245 * gui.scale)

            y += round(25 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Server URL"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 0
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_maloja_url.text = prefs.maloja_url
            text_maloja_url.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 0,
                              width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.maloja_url = text_maloja_url.text.strip()

            y += round(23 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("API Key"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 1
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_maloja_key.text = prefs.maloja_key
            text_maloja_key.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 1,
                              width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.maloja_key = text_maloja_key.text.strip()

            y += round(35 * gui.scale)
 
            if self.button(x, y, _("Test connectivity")):

                if not prefs.maloja_url or not prefs.maloja_key:
                    show_message(_("One or more fields is missing."))
                else:
                    url = prefs.maloja_url
                    if not url.endswith("/mlj_1"):
                        if not url.endswith("/"):
                            url += "/"
                        url += "apis/mlj_1"
                    url += "/test"

                    try:
                        r = requests.get(url, params={'key': prefs.maloja_key})
                        if r.status_code == 403:
                            show_message("Connection appeared successful but the API key was invalid", mode='warning')
                        elif r.status_code == 200:
                            show_message("Connection to Maloja server was successful.", mode='done')
                        else:
                            show_message("The Maloja server returned an error", r.text, mode='warning')
                    except:
                        show_message("Could not communicate with the Maloja server", mode='warning')

            y += round(30 * gui.scale)

            ws = ddt.get_text_w(_("Get scrobble counts"),211) + 10 * gui.scale
            wcc = ddt.get_text_w(_("Clear"),211) + 15 * gui.scale
            if self.button(x, y, _("Get scrobble counts")):
                shooter(maloja_get_scrobble_counts)
            self.button(x + ws + round(12 * gui.scale), y, _("Clear"), self.clear_scrobble_counts, width=wcc)

        if self.account_view == 8:

            ddt.text((x, y), _('Spotify Premium account'), colours.box_sub_text, 213)

            y += round(30 * gui.scale)


            if self.button(x, y, _("View setup instructions")):
                webbrowser.open("https://github.com/Taiko2k/TauonMusicBox/wiki/Spotify", new=2, autoraise=True)

            field_width = round(245 * gui.scale)

            y += round(26 * gui.scale)

            ddt.text((x + 0 * gui.scale, y), _("Client ID"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 0
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_spot_client.text = prefs.spot_client
            text_spot_client.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 0,
                              width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.spot_client = text_spot_client.text.strip()
            #
            # y += round(23 * gui.scale)
            # ddt.text((x + 0 * gui.scale, y), _("Client Secret"),
            #          colours.box_text_label, 11)
            # y += round(19 * gui.scale)
            # rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            # fields.add(rect1)
            # if coll(rect1) and (self.click or level_2_right_click):
            #     self.account_text_field = 1
            # ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            # text_spot_secret.text = prefs.spot_secret
            # text_spot_secret.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 1,
            #                   width=rect1[2] - 8 * gui.scale, click=self.click)
            # prefs.spot_secret = text_spot_secret.text.strip()

            y += round(27 * gui.scale)

            if prefs.spotify_token:
                if self.button(x, y, _("Forget Account")):
                    spot_ctl.delete_token()
                    spot_ctl.cache_saved_albums.clear()
            else:
                if self.button(x, y, _("Authorise")):

                    webThread = threading.Thread(target=authserve, args=[tauon])
                    webThread.daemon = True
                    webThread.start()
                    time.sleep(0.1)

                    spot_ctl.auth()

            # y += round(30 * gui.scale)
            # if self.button(x, y, _("Paste code param")):
            #     text = copy_from_clipboard().strip()
            #     if "=" in text:
            #         text = text.split("=", 1)[1].strip()
            #     if len(text) > 50:
            #         spot_ctl.paste_code(text)
            y += round(31 * gui.scale)
            prefs.launch_spotify_web = self.toggle_square(x,y, prefs.launch_spotify_web, _("Prefer launching web player"))

            y += round(30 * gui.scale)

            ww = ddt.get_text_w(_("Import Albums"), 211)
            if self.button(x, y, _("Import Albums")):
                if not spot_ctl.spotify_com:
                    spot_ctl.spotify_com = True
                    shoot = threading.Thread(target=spot_ctl.get_library_albums)
                    shoot.daemon = True
                    shoot.start()
                else:
                    show_message(_("Please wait until current job is finished"))



            if self.button(x + ww + round(20 * gui.scale), y, _("Import Liked Songs")):
                if not spot_ctl.spotify_com:
                    spot_ctl.spotify_com = True
                    shoot = threading.Thread(target=spot_ctl.get_library_likes)
                    shoot.daemon = True
                    shoot.start()
                else:
                    show_message(_("Please wait until current job is finished"))


            y += round(30 * gui.scale)


            if self.button(x, y, _("Import all user playlists")):
                if not spot_ctl.spotify_com:
                    show_message(_("Importing Spotify playlists..."))
                    shoot_dl = threading.Thread(target=spot_ctl.import_all_playlists)
                    shoot_dl.daemon = True
                    shoot_dl.start()
                else:
                    show_message(_("Please wait until current job is finished"))

            y += round(30 * gui.scale)
            if self.button(x, y, _("Import single user playlist...")):
                if not spot_ctl.spotify_com:
                    playlists = spot_ctl.get_playlist_list()
                    spotify_playlist_menu.items.clear()
                    if playlists:
                        for item in playlists:
                            spotify_playlist_menu.add(item[0], spot_ctl.playlist, pass_ref=True, set_ref=item[1])
                            spotify_playlist_menu.activate(position=(x, y))
                else:
                    show_message(_("Please wait until current job is finished"))



        if self.account_view == 7:

            ddt.text((x, y), _('Airsonic/Subsonic network streaming'), colours.box_sub_text, 213)

            if inp.key_tab_press:
                self.account_text_field += 1
                if self.account_text_field > 2:
                    self.account_text_field = 0

            field_width = round(245 * gui.scale)

            y += round(25 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Username / Email"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 0
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_air_usr.text = prefs.subsonic_user
            text_air_usr.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 0,
                              width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.subsonic_user = text_air_usr.text

            y += round(23 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Password"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 1
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_air_pas.text = prefs.subsonic_password
            text_air_pas.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 1,
                              width=rect1[2] - 8 * gui.scale, click=self.click, secret=True)
            prefs.subsonic_password = text_air_pas.text

            y += round(23 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Server URL"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 2
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_air_ser.text = prefs.subsonic_server
            text_air_ser.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 2,
                              width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.subsonic_server = text_air_ser.text

            y += round(40 * gui.scale)
            self.button(x, y, _("Import music to playlist"), sub_get_album_thread)

            y += round(35 * gui.scale)
            prefs.subsonic_password_plain = self.toggle_square(x,y, prefs.subsonic_password_plain, _("Use plain text authentication"), subtitle=_("Needed for Nextcloud Music"))

        if self.account_view == 10:

            ddt.text((x, y), _('Jellyfin network streaming'), colours.box_sub_text, 213)

            if inp.key_tab_press:
                self.account_text_field += 1
                if self.account_text_field > 2:
                    self.account_text_field = 0

            field_width = round(245 * gui.scale)

            y += round(25 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Username"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 0
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_jelly_usr.text = prefs.jelly_username
            text_jelly_usr.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 0,
                               width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.jelly_username = text_jelly_usr.text

            y += round(23 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Password"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 1
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_jelly_pas.text = prefs.jelly_password
            text_jelly_pas.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 1,
                               width=rect1[2] - 8 * gui.scale, click=self.click, secret=True)
            prefs.jelly_password = text_jelly_pas.text

            y += round(23 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Server URL"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 2
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_jelly_ser.text = prefs.jelly_server_url
            text_jelly_ser.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 2,
                               width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.jelly_server_url = text_jelly_ser.text

            y += round(40 * gui.scale)

            self.button(x, y, _("Import music to playlist"), jellyfin_get_library_thread)

            y += round(35 * gui.scale)
            if self.button(x, y, _("Test connectivity")):
                jellyfin.test()

        if self.account_view == 6:

            ddt.text((x, y), _('koel network streaming'), colours.box_sub_text, 213)

            if inp.key_tab_press:
                self.account_text_field += 1
                if self.account_text_field > 2:
                    self.account_text_field = 0

            field_width = round(245 * gui.scale)

            y += round(25 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Username / Email"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 0
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_koel_usr.text = prefs.koel_username
            text_koel_usr.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 0,
                              width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.koel_username = text_koel_usr.text

            y += round(23 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Password"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 1
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_koel_pas.text = prefs.koel_password
            text_koel_pas.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 1,
                              width=rect1[2] - 8 * gui.scale, click=self.click, secret=True)
            prefs.koel_password = text_koel_pas.text

            y += round(23 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Server URL"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 2
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_koel_ser.text = prefs.koel_server_url
            text_koel_ser.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 2,
                              width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.koel_server_url = text_koel_ser.text

            y += round(40 * gui.scale)
            
            self.button(x, y, _("Import music to playlist"), koel_get_album_thread)


        if self.account_view == 5:

            ddt.text((x, y), _('PLEX network streaming'), colours.box_sub_text, 213)

            if inp.key_tab_press:
                self.account_text_field += 1
                if self.account_text_field > 2:
                    self.account_text_field = 0

            field_width = round(245 * gui.scale)

            y += round(25 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Username / Email"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 0
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_plex_usr.text = prefs.plex_username
            text_plex_usr.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 0,
                              width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.plex_username = text_plex_usr.text

            y += round(23 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Password"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 1
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_plex_pas.text = prefs.plex_password
            text_plex_pas.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 1,
                              width=rect1[2] - 8 * gui.scale, click=self.click, secret=True)
            prefs.plex_password = text_plex_pas.text

            y += round(23 * gui.scale)
            ddt.text((x + 0 * gui.scale, y), _("Server name"),
                     colours.box_text_label, 11)
            y += round(19 * gui.scale)
            rect1 = (x + 0 * gui.scale, y, field_width, round(17 * gui.scale))
            fields.add(rect1)
            if coll(rect1) and (self.click or level_2_right_click):
                self.account_text_field = 2
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            text_plex_ser.text = prefs.plex_servername
            text_plex_ser.draw(x + round(4 * gui.scale), y, colours.box_input_text, self.account_text_field == 2,
                              width=rect1[2] - 8 * gui.scale, click=self.click)
            prefs.plex_servername = text_plex_ser.text

            y += round(40 * gui.scale)
            self.button(x, y, _("Import music to playlist"), plex_get_album_thread)



        if self.account_view == 4:

            ddt.text((x, y), 'fanart.tv', colours.box_sub_text, 213)

            y += 25 * gui.scale
            ddt.text((x + 0 * gui.scale, y, 4, 270 * gui.scale, 600), _("Fanart.tv can be used for sourcing of artist images and cover art."),
                     colours.box_text_label, 11)
            y += 17 * gui.scale

            y += 22 * gui.scale
            #. Limited space available. Limit 55 chars.
            link_pa2 = draw_linked_text((x + 0 * gui.scale, y), _("They encourage you to contribute at https://fanart.tv"),
                     colours.box_text_label, 11)
            link_activate(x, y, link_pa2)

            y += 35 * gui.scale
            prefs.enable_fanart_cover = self.toggle_square(x, y, prefs.enable_fanart_cover, _("Cover art (Manual only)"))
            y += 25 * gui.scale
            prefs.enable_fanart_artist = self.toggle_square(x, y, prefs.enable_fanart_artist, _("Artist images (Automatic)"))
            y += 25 * gui.scale
            prefs.enable_fanart_bg = self.toggle_square(x, y, prefs.enable_fanart_bg, _("Artist backgrounds (Automatic)"))
            y += 25 * gui.scale
            x += 23 * gui.scale
            if self.button(x, y, _("Flip current")):
                if key_shift_down:
                    prefs.bg_flips.clear()
                    show_message("Reset flips", mode="done")
                else:
                    tr = pctl.playing_object()
                    artist = get_artist_safe(tr)
                    if artist:
                        if artist not in prefs.bg_flips:
                            prefs.bg_flips.add(artist)
                        else:
                            prefs.bg_flips.remove(artist)
                    style_overlay.flush()
                    show_message("OK", mode="done")

        if self.account_view == 3:

            ddt.text((x, y), 'Discogs', colours.box_sub_text, 213)

            y += 25 * gui.scale
            hh = ddt.text((x + 0 * gui.scale, y, 4, 260 * gui.scale, 300 * gui.scale), _("Discogs can be used for sourcing artist images. For this you will need a \"Personal Access Token\".\n\nYou can generate one with a Discogs account here:"),
                     colours.box_text_label, 11)


            y += hh
            #y += 15 * gui.scale
            link_pa2 = draw_linked_text((x + 0 * gui.scale, y), "https://www.discogs.com/settings/developers",colours.box_text_label, 12)
            link_rect2 = [x + 0 * gui.scale, y, link_pa2[1], 20 * gui.scale]
            fields.add(link_rect2)
            if coll(link_rect2):
                if not self.click:
                    gui.cursor_want = 3
                if self.click:
                    webbrowser.open(link_pa2[2], new=2, autoraise=True)

            y += 40 * gui.scale
            if self.button(x, y, _("Paste Token")):

                text = copy_from_clipboard()
                if text == "":
                    show_message("There is no text in the clipboard", mode='error')
                elif len(text) == 40:
                    prefs.discogs_pat = text

                    # Reset caches -------------------
                    prefs.failed_artists.clear()
                    artist_list_box.to_fetch = ""
                    for key, value in artist_list_box.thumb_cache.items():
                        if value:
                            SDL_DestroyTexture(value[0])
                    artist_list_box.thumb_cache.clear()
                    artist_list_box.to_fetch = ""

                    direc = os.path.join(a_cache_dir)
                    if os.path.isdir(direc):
                        for item in os.listdir(direc):
                            if "-lfm.txt" in item:
                                os.remove(os.path.join(direc, item))
                    # -----------------------------------

                else:
                    show_message("That is not a valid token", mode='error')
            y += 30 * gui.scale
            if self.button(x, y, _("Clear")):
                if not prefs.discogs_pat:
                    show_message("There wasn't any token saved.")
                prefs.discogs_pat = ""
                save_prefs()

            y += 30 * gui.scale
            if prefs.discogs_pat:
                ddt.text((x + 0 * gui.scale, y - 0 * gui.scale), prefs.discogs_pat, colours.box_input_text, 211)



        if self.account_view == 1:

            text = "Last.fm"
            if prefs.use_libre_fm:
                text = "Libre.fm"

            ddt.text((x, y), text, colours.box_sub_text, 213)

            ww = ddt.get_text_w(_("Username:"), 212)
            ddt.text((x + 65 * gui.scale, y - 0 * gui.scale), _("Username:"), colours.box_text_label, 212)
            ddt.text((x + ww + 65 * gui.scale + 7 * gui.scale, y - 0 * gui.scale), prefs.last_fm_username, colours.box_sub_text, 213)

            y += 25 * gui.scale

            if prefs.last_fm_token is None:
                ww = ddt.get_text_w(_("Login"), 211) + 10 * gui.scale
                ww2 = ddt.get_text_w(_("Done"), 211) + 40 * gui.scale
                self.button(x, y, _("Login"), lastfm.auth1)
                self.button(x + ww + 10 * gui.scale, y, _("Done"), lastfm.auth2)

                if prefs.last_fm_token is None and lastfm.url is None:
                    prefs.use_libre_fm = self.toggle_square(x + ww + ww2, y + round(1 * gui.scale), prefs.use_libre_fm, _("Use LibreFM"))

                y += 25 * gui.scale
                ddt.text((x + 2 * gui.scale, y, 4, 270 * gui.scale, 300 * gui.scale), _("Click login to open the last.fm web authorisation page and follow prompt. Then return here and click \"Done\"."),
                         colours.box_text_label, 11, max_w=270 * gui.scale)

            else:
                self.button(x, y, _("Forget account"), lastfm.auth3)



            x = x0 + 230 * gui.scale
            y = y0 + round(130 * gui.scale)

            # self.toggle_square(x, y, toggle_scrobble_mark, "Show scrobble marker")

            wa = ddt.get_text_w(_("Get user loves"),211) + 10 * gui.scale
            wb = ddt.get_text_w(_("Clear local loves"),211) + 10 * gui.scale
            wc = ddt.get_text_w(_("Get friend loves"),211) + 10 * gui.scale
            ws = ddt.get_text_w(_("Get scrobble counts"),211) + 10 * gui.scale
            wcc = ddt.get_text_w(_("Clear"),211) + 15 * gui.scale
            #wd = ddt.get_text_w(_("Clear friend loves"),211) + 10 * gui.scale
            ww = max(wa, wb, wc, ws)

            self.button(x, y, _("Get user loves"), self.get_user_love, width=ww)
            self.button(x + ww + round(12 * gui.scale), y, _("Clear"), self.clear_local_loves, width=wcc)

            #y += 26 * gui.scale
            #self.button(x, y, _("Clear local loves"), self.clear_local_loves, width=ww)

            y += 26 * gui.scale

            self.button(x, y, _("Get friend loves"), self.get_friend_love, width=ww)
            self.button(x + ww + round(12 * gui.scale), y, _("Clear"), lastfm.clear_friends_love, width=wcc)

            y += 26 * gui.scale
            self.button(x, y, _("Get scrobble counts"), self.get_scrobble_counts, width=ww)
            self.button(x + ww + round(12 * gui.scale), y, _("Clear"), self.clear_scrobble_counts, width=wcc)


        if self.account_view == 2:

            ddt.text((x, y), 'ListenBrainz', colours.box_sub_text, 213)

            y += 30 * gui.scale
            self.button(x, y, _("Paste Token"), lb.paste_key)

            self.button(x + ddt.get_text_w(_("Paste Token"), 211) + 21 * gui.scale, y, _("Clear"), lb.clear_key)

            y += 35 * gui.scale

            if prefs.lb_token:
                line = prefs.lb_token
                ddt.text((x + 0 * gui.scale, y - 0 * gui.scale), line, colours.box_input_text, 212)

            y += 25 * gui.scale
            link_pa2 = draw_linked_text((x + 0 * gui.scale, y), "https://listenbrainz.org/profile/", colours.box_sub_text, 12)
            link_rect2 = [x + 0 * gui.scale, y, link_pa2[1], 20 * gui.scale]
            fields.add(link_rect2)

            if coll(link_rect2):
                if not self.click:
                    gui.cursor_want = 3

                if self.click:
                    webbrowser.open(link_pa2[2], new=2, autoraise=True)


    def clear_local_loves(self):

        if not key_shift_down:
            show_message(_("This will mark all tracks in local database as unloved!"),
                         _("Press button again while holding shift key if you're sure you want to do that."), mode='warning')
            return

        for key, star in star_store.db.items():
            star = [star[0], star[1].strip("L"), star[2]]
            star_store.db[key] = star

        gui.pl_update += 1
        show_message(_("Cleared all loves"), mode="done")

    def get_scrobble_counts(self):

        if not key_shift_down:
            t = lastfm.get_all_scrobbles_estimate_time()
            show_message(_("Warning: This process will take approximately %d minutes to complete." % (t // 60)),
                           _("Press again while holding Shift if you understand"), mode="warning")
            return

        if not lastfm.scanning_friends and not lastfm.scanning_scrobbles and not lastfm.scanning_loves:
            shoot_dl = threading.Thread(target=lastfm.get_all_scrobbles)
            shoot_dl.daemon = True
            shoot_dl.start()
        else:
            show_message("A process is already running. Wait for it to finish.")

    def clear_scrobble_counts(self):

        for track in pctl.master_library.values():
            track.lfm_scrobbles = 0

        show_message(_("Cleared all scrobble counts"), mode="done")

    def get_friend_love(self):

        if not key_shift_down:
            show_message(_("Warning: This process can take a long time to complete! (up to an hour or more)"),
                         _("This feature is not recommended for accounts that have many friends."),
                           _("Press again while holding Shift if you understand"), mode="warning")
            return

        if not lastfm.scanning_friends and not lastfm.scanning_scrobbles and not lastfm.scanning_loves:
            shoot_dl = threading.Thread(target=lastfm.get_friends_love)
            shoot_dl.daemon = True
            shoot_dl.start()
        else:
            show_message("A process is already running. Wait for it to finish.")

    def get_user_love(self):

        if not lastfm.scanning_friends and not lastfm.scanning_scrobbles and not lastfm.scanning_loves:
            shoot_dl = threading.Thread(target=lastfm.dl_love)
            shoot_dl.daemon = True
            shoot_dl.start()
        else:
            show_message("A process is already running. Wait for it to finish.")


    def codec_config(self, x0, y0, w0, h0):

        x = x0 + round(25 * gui.scale)
        y = y0

        y += 20 * gui.scale
        ddt.text_background_colour = colours.box_background

        if self.sync_view:

            pl = None
            if prefs.sync_playlist:
                pl = id_to_pl(prefs.sync_playlist)
            if pl is None:
                prefs.sync_playlist = None

            y += 5 * gui.scale
            if prefs.sync_playlist:
                ww = ddt.text((x, y), _("Selected playlist:") + "    ", colours.box_text_label, 11)
                ddt.text((x + ww, y), pctl.multi_playlist[pl][0], colours.box_sub_text, 12,
                         400 * gui.scale)
            else:
                ddt.text((x, y), _("No sync playlist selected!"), colours.box_text_label, 11)

            y += 25 * gui.scale
            ww = ddt.text((x, y), _("Path to device music folder:   "), colours.box_text_label, 11)
            y += 20 * gui.scale

            rect1 = (x + 0 * gui.scale, y, round(450 * gui.scale), round(17 * gui.scale))
            fields.add(rect1)
            ddt.bordered_rect(rect1, colours.box_background, colours.box_text_border, round(1 * gui.scale))
            sync_target.draw(x + round(4 * gui.scale), y, colours.box_input_text, not gui.sync_progress,
                              width=rect1[2] - 8 * gui.scale, click=self.click)

            rect = [x + rect1[2] + 11 * gui.scale, y - 2 * gui.scale, 15 * gui.scale, 19 * gui.scale]
            fields.add(rect)
            colour = colours.box_text_label
            if coll(rect):
                colour = [225, 160, 0, 255]
                if self.click:
                    paths = auto_get_sync_targets()
                    if paths:
                        sync_target.text = paths[0]
                        show_message(_("A mounted music folder was found!"), mode="done")
                    else:
                        show_message(_("Could not auto-detect mounted device path."), _("Make sure the device is mounted and path is accessible."))

            power_bar_icon.render(rect[0], rect[1], colour)
            y += 30 * gui.scale

            prefs.sync_deletes = self.toggle_square(x, y, prefs.sync_deletes, _("Delete all other folders in target"))
            y += 25 * gui.scale
            prefs.bypass_transcode = self.toggle_square(x, y, prefs.bypass_transcode ^ True, _("Transcode files")) ^ True
            y += 25 * gui.scale
            prefs.smart_bypass = self.toggle_square(x + round(10 * gui.scale), y, prefs.smart_bypass ^ True, _("Bypass low bitrate")) ^ True
            y += 30 * gui.scale

            text = _("Start Transcode and Sync")
            ww = ddt.get_text_w(text, 211) + 25 * gui.scale
            if prefs.bypass_transcode:
                text = _("Start Sync")

            xx = (rect1[0] + (rect1[2] // 2)) - (ww // 2)
            if gui.stop_sync:
                self.button(xx, y, _("Stopping..."), width=ww)
            elif not gui.sync_progress:
                if self.button(xx, y, text, width=ww):
                    if pl is not None:
                        auto_sync(pl)
                    else:
                        show_message(_("Select a source playlist"), _("Right click tab > Misc... > Set as sync playlist"))
            else:
                if self.button(xx, y, _("Stop"), width=ww):
                    gui.stop_sync = True
                    gui.sync_progress = _("Aborting Sync")

            y += 60 * gui.scale

            if self.button(x, y, _("Return"), width=round(75*gui.scale)):
                self.sync_view = False

            if self.button(x + 485 * gui.scale, y, _("?")):
                show_message("See here for detailed instructions", "https://github.com/Taiko2k/TauonMusicBox/wiki/Transcode-and-Sync", mode="link")

            return

        # ----------

        ddt.text((x, y + 13 * gui.scale), _("Output codec setting:"), colours.box_text_label, 11)

        ww = ddt.get_text_w(_("Open output folder"), 211) + 25 * gui.scale
        self.button(x0 + w0 - ww, y - 4 * gui.scale, _("Open output folder"), open_encode_out)

        ww = ddt.get_text_w(_("Sync..."), 211) + 25 * gui.scale
        if self.button(x0 + w0 - ww, y + 25 * gui.scale, _("Sync...")):
            self.sync_view = True

        y += 40 * gui.scale
        self.toggle_square(x, y, switch_flac, "FLAC")
        y += 25 * gui.scale
        self.toggle_square(x, y, switch_opus, "OPUS")
        if prefs.transcode_codec == 'opus':
            self.toggle_square(x + 120 * gui.scale, y, switch_opus_ogg, _("Save opus as .ogg extension"))
        y += 25 * gui.scale
        self.toggle_square(x, y, switch_ogg, "OGG Vorbis")
        y += 25 * gui.scale

        #if not flatpak_mode:
        self.toggle_square(x, y, switch_mp3, "MP3")
            # if prefs.transcode_codec == 'mp3' and not shutil.which("lame"):
            #     ddt.draw_text((x + 90 * gui.scale, y - 3 * gui.scale), "LAME not detected!", [220, 110, 110, 255], 12)

        if prefs.transcode_codec != 'flac':
            y += 35 * gui.scale

            prefs.transcode_bitrate = self.slide_control(x, y, _("Bitrate"), "kbs", prefs.transcode_bitrate, 32, 320, 8)

            y -= 1 * gui.scale
            x += 280 * gui.scale
            if (msys and not os.path.isfile(user_directory + '/encoder/ffmpeg.exe')) or (
                    not msys and shutil.which(ffmpeg) is None):
                ddt.text((x, y), "FFMPEG not detected!", [220, 110, 110, 255], 12)

        x = x0 + round(20 * gui.scale)
        y = y0 + 215 * gui.scale

        self.toggle_square(x, y, toggle_transcode_output, _("Save to output folder"))
        y += 25 * gui.scale
        self.toggle_square(x, y, toggle_transcode_inplace, _("Save and overwrite files inplace"))

    def devance_theme(self):
        global theme

        theme -= 1
        gui.reload_theme = True
        if theme < 0:
            theme = len(get_themes())

    def config_b(self, x0, y0, w0, h0):

        global album_mode_art_size
        global update_layout

        ddt.text_background_colour = colours.box_background
        x = x0 + round(25 * gui.scale)
        y = y0 + round(20 * gui.scale)

        #ddt.text((x, y), _("Window"),colours.box_text_label, 12)

        if system == "linux":
            self.toggle_square(x, y, toggle_notifications, _("Emit track change notifications"))

        y += 25 * gui.scale
        self.toggle_square(x, y, toggle_borderless, _("Draw own window decorations"))

        y += 25 * gui.scale
        prefs.save_window_position = self.toggle_square(x, y, prefs.save_window_position, _("Restore window position on restart"))

        y += 25 * gui.scale
        if not draw_border:
            self.toggle_square(x, y, toggle_titlebar_line, _("Show playing in titlebar"))

        y += 25 * gui.scale
        # if system != 'windows' and (flatpak_mode or snap_mode):
        #     self.toggle_square(x, y, toggle_force_subpixel, _("Enable RGB text antialiasing"))

        y += 15 * gui.scale
        self.toggle_square(x, y, toggle_level_meter, _("Top-panel level meter"))

        y += 25 * gui.scale
        if prefs.backend == 4:
            self.toggle_square(x, y, toggle_showcase_vis, _("Showcase visualisation"))

        y += round(35 * gui.scale)
        #if not msys:
        #y += round(15 * gui.scale)

        ddt.text((x, y), _("UI scale for HiDPI displays"), colours.box_text_label, 12)

        y += round(25 * gui.scale)

        sw = round(200 * gui.scale)
        sh = round(2 * gui.scale)

        slider = (x,y,sw,sh)

        gh = round(14 * gui.scale)
        gw = round(8 * gui.scale)
        grip = [0, y - (gh // 2), gw, gh]

        grip[0] = x
        grip[0] += ((prefs.scale_want - 0.5) / 3 * sw)

        m1 = (x + ((1.0 - 0.5) / 3 * sw), y, sh, sh * 2)
        m2 = (x + ((2.0 - 0.5) / 3 * sw), y, sh, sh * 2)
        m3 = (x + ((3.0 - 0.5) / 3 * sw), y, sh, sh * 2)

        if coll(grow_rect(slider, round(16 * gui.scale))) and mouse_down:
            prefs.scale_want = ((mouse_position[0] - x) / sw * 3) + 0.5
            prefs.x_scale = False
            gui.update_on_drag = True
        if prefs.scale_want < 0.5:
            prefs.scale_want = 0.5
        if prefs.scale_want > 3.5:
            prefs.scale_want = 3.5
        prefs.scale_want = round(round(prefs.scale_want / 0.05) * 0.05, 2)
        if prefs.scale_want == 0.95 or prefs.scale_want == 1.05:
            prefs.scale_want = 1.0
        if prefs.scale_want == 1.95 or prefs.scale_want == 2.05:
            prefs.scale_want = 2.0
        if prefs.scale_want == 2.95 or prefs.scale_want == 3.05:
            prefs.scale_want = 3.0

        text = str(prefs.scale_want)
        if len(text) == 3:
            text += "0"
        text += "x"

        if prefs.x_scale:
            text = "auto"

        font = 13
        if not prefs.x_scale and (prefs.scale_want == 1.0 or prefs.scale_want == 2.0 or prefs.scale_want == 3.0):
            font = 313

        ddt.text((x + sw + round(14 * gui.scale), y - round(8 * gui.scale)), text, colours.box_sub_text, font)
        #ddt.text((x + sw + round(14 * gui.scale), y + round(10 * gui.scale)), _("Restart app to apply any changes"), colours.box_text_label, 11)

        ddt.rect(slider, colours.box_text_border)
        ddt.rect(m1, colours.box_text_border)
        ddt.rect(m2, colours.box_text_border)
        ddt.rect(m3, colours.box_text_border)
        ddt.rect(grip, colours.box_text_label)

        y += round(25 * gui.scale)
        self.toggle_square(x, y, self.toggle_x_scale, _("Auto scale"))

        if prefs.scale_want != gui.scale:
            gui.update += 1
            if not mouse_down:
                gui.update_layout()

    def toggle_x_scale(self, mode=0):
        if mode == 1:
            return prefs.x_scale
        prefs.x_scale ^= True
        auto_scale()
        gui.update_layout()

    def about(self, x0, y0, w0, h0):

        x = x0 + int(w0 * 0.3) - 10 * gui.scale
        y = y0 + 85 * gui.scale

        ddt.text_background_colour = colours.box_background

        icon_rect = (x - 110 * gui.scale, y - 15 * gui.scale, self.about_image.w, self.about_image.h)

        genre = ""
        if pctl.playing_object() is not None:
            genre = pctl.playing_object().genre.lower()

            if any(s in genre for s in ['ock', 'lt']):
                self.about_image2.render(icon_rect[0], icon_rect[1])
            elif any(s in genre for s in ['kpop', 'k-pop', 'anime']):
                self.about_image6.render(icon_rect[0], icon_rect[1])
            elif any(s in genre for s in ['syn', 'pop']):
                self.about_image3.render(icon_rect[0], icon_rect[1])
            elif any(s in genre for s in ['tro', 'cid']):
                self.about_image4.render(icon_rect[0], icon_rect[1])
            elif any(s in genre for s in ['uture']):
                self.about_image5.render(icon_rect[0], icon_rect[1])
            else:
                genre = ""

        if not genre:
            self.about_image.render(icon_rect[0], icon_rect[1])

        x += 20 * gui.scale
        y -= 10 * gui.scale

        self.title_image.render(x - 1, y, alpha_mod(colours.box_sub_text, 240))

        credit_pages = 5

        if self.click and coll(icon_rect) and self.ani_cred == 0:
            self.ani_cred = 1
            self.ani_fade_on_timer.set()

        fade = 0

        if self.ani_cred == 1:
            t = self.ani_fade_on_timer.get()
            fade = round(t / 0.7 * 255)
            if fade > 255:
                fade = 255

            if t > 0.7:
                self.ani_cred = 2
                self.cred_page += 1
                if self.cred_page > credit_pages:
                    self.cred_page = 0
                self.ani_fade_on_timer.set()

            gui.update = 2

        if self.ani_cred == 2:

            t = self.ani_fade_on_timer.get()
            fade = 255 - round(t / 0.7 * 255)
            if fade < 0:
                fade = 0
            if t > 0.7:
                self.ani_cred = 0

            gui.update = 2

        y += 32 * gui.scale

        block_y = y - 10 * gui.scale

        if self.cred_page == 0:

            ddt.text((x, y - 6 * gui.scale), t_version, colours.box_text_label, 313)
            y += 19 * gui.scale
            ddt.text((x, y), "Copyright © 2015-2022 Taiko2k captain.gxj@gmail.com", colours.box_sub_text, 13)

            y += 19 * gui.scale
            link_pa = draw_linked_text((x, y), "https://tauonmusicbox.rocks", colours.box_sub_text, 12, replace="tauonmusicbox.rocks")
            link_rect = [x, y, link_pa[1], 18 * gui.scale]
            if coll(link_rect):
                if not self.click:
                    gui.cursor_want = 3
                if self.click:
                    webbrowser.open(link_pa[2], new=2, autoraise=True)

            fields.add(link_rect)


            y += 27 * gui.scale
            ddt.text((x, y), "This program comes with absolutely no warranty.", colours.box_text_label, 12)
            y += 16 * gui.scale
            link_pa = draw_linked_text((x, y), "See the https://www.gnu.org/licenses/gpl-3.0.html license for details.", colours.box_text_label, 12, replace="GNU GPLv3+")
            link_rect = [x + link_pa[0], y, link_pa[1], 18 * gui.scale]
            if coll(link_rect):
                if not self.click:
                    gui.cursor_want = 3
                if self.click:
                    webbrowser.open(link_pa[2], new=2, autoraise=True)
            fields.add(link_rect)

        elif self.cred_page == 1:

            y += 15 * gui.scale

            ddt.text((x, y + 1 * gui.scale), "Created by", colours.box_text_label, 13)
            ddt.text((x + 120 * gui.scale, y + 1 * gui.scale), "Taiko2k", colours.box_sub_text, 13)

            y += 35 * gui.scale
            ddt.text((x, y + 1 * gui.scale), "Contributors", colours.box_text_label, 13)
            ddt.text((x + 120 * gui.scale, y + 1 * gui.scale), "RSKYS", colours.box_sub_text, 13)


            y += 20 * gui.scale
            link_pa = draw_linked_text((x, y), "https://github.com/Taiko2k/TauonMusicBox/graphs/contributors", colours.box_sub_text, 12, replace="Other contributors")
            link_rect = [x, y, link_pa[1], 18 * gui.scale]
            if coll(link_rect):
                if not self.click:
                    gui.cursor_want = 3
                if self.click:
                    webbrowser.open(link_pa[2], new=2, autoraise=True)
            fields.add(link_rect)


        elif self.cred_page == 2:
            xx = x + round(160 * gui.scale)
            xxx = x + round(240 * gui.scale)
            ddt.text((x, y), "Open source software used", colours.box_text_label, 13)
            font = 12
            spacing = round(18 * gui.scale)
            y += spacing
            ddt.text((x, y), "Simple DirectMedia Layer", colours.box_sub_text, font)
            ddt.text((xx, y), "zlib", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://www.libsdl.org/", colours.box_sub_text, font, click=self.click, replace="libsdl.org")

            y += spacing
            ddt.text((x, y), "Cairo Graphics", colours.box_sub_text, font)
            ddt.text((xx, y), "MPL", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://www.cairographics.org/", colours.box_sub_text, font, click=self.click, replace="cairographics.org")

            y += spacing
            ddt.text((x, y), "Pango", colours.box_sub_text, font)
            ddt.text((xx, y), "LGPL", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://pango.gnome.org/", colours.box_sub_text, font, click=self.click, replace="pango.gnome.org")

            y += spacing
            ddt.text((x, y), "FFmpeg", colours.box_sub_text, font)
            ddt.text((xx, y), "GPL", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://ffmpeg.org/", colours.box_sub_text, font, click=self.click, replace="ffmpeg.org")

            y += spacing
            ddt.text((x, y), "Pillow", colours.box_sub_text, font)
            ddt.text((xx, y), "PIL License", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://python-pillow.org/", colours.box_sub_text, font, click=self.click, replace="python-pillow.org")


        elif self.cred_page == 4:
            xx = x + round(140 * gui.scale)
            xxx = x + round(240 * gui.scale)
            ddt.text((x, y), "Open source software used (cont'd)", colours.box_text_label, 13)
            font = 12
            spacing = round(18 * gui.scale)
            y += spacing
            ddt.text((x, y), "PySDL2", colours.box_sub_text, font)
            ddt.text((xx, y), "Public Domain", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://github.com/marcusva/py-sdl2", colours.box_sub_text, font, click=self.click, replace="github")

            y += spacing
            ddt.text((x, y), "Tekore", colours.box_sub_text, font)
            ddt.text((xx, y), "MIT", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://github.com/felix-hilden/tekore", colours.box_sub_text, font, click=self.click, replace="github")

            y += spacing
            ddt.text((x, y), "pyLast", colours.box_sub_text, font)
            ddt.text((xx, y), "Apache 2.0", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://github.com/pylast/pylast", colours.box_sub_text, font, click=self.click, replace="github")

            y += spacing
            ddt.text((x, y), "Noto Sans font", colours.box_sub_text, font)
            ddt.text((xx, y), "Apache 2.0", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://fonts.google.com/specimen/Noto+Sans", colours.box_sub_text, font, click=self.click, replace="fonts.google.com")

            # y += spacing
            # ddt.text((x, y), "Stagger", colours.box_sub_text, font)
            # ddt.text((xx, y), "BSD 2-Clause", colours.box_text_label, font)
            # d"raw_linked_text2(xxx, y, "https://github.com/staggerpkg/stagger", colours.box_sub_text, font, click=self.click, replace="github")

            y += spacing
            ddt.text((x, y), "KISS FFT", colours.box_sub_text, font)
            ddt.text((xx, y), "New BSD License", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://github.com/mborgerding/kissfft", colours.box_sub_text, font, click=self.click, replace="github")

        elif self.cred_page == 3:
            xx = x + round(130 * gui.scale)
            xxx = x + round(240 * gui.scale)
            ddt.text((x, y), "Open source software used (cont'd)", colours.box_text_label, 13)
            font = 12
            spacing = round(18 * gui.scale)
            y += spacing
            ddt.text((x, y), "libFLAC", colours.box_sub_text, font)
            ddt.text((xx, y), "New BSD License", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://xiph.org/flac/", colours.box_sub_text, font, click=self.click, replace="xiph.org")

            y += spacing
            ddt.text((x, y), "libvorbis", colours.box_sub_text, font)
            ddt.text((xx, y), "BSD License", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://xiph.org/vorbis/", colours.box_sub_text, font, click=self.click, replace="xiph.org")

            y += spacing
            ddt.text((x, y), "opusfile", colours.box_sub_text, font)
            ddt.text((xx, y), "New BSD license", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://opus-codec.org/", colours.box_sub_text, font, click=self.click, replace="opus-codec.org")

            y += spacing
            ddt.text((x, y), "mpg123", colours.box_sub_text, font)
            ddt.text((xx, y), "LGPL 2.1", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://www.mpg123.de/", colours.box_sub_text, font, click=self.click, replace="mpg123.de")

            y += spacing
            ddt.text((x, y), "Secret Rabbit Code", colours.box_sub_text, font)
            ddt.text((xx, y), "BSD 2-Clause", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "http://www.mega-nerd.com/SRC/index.html", colours.box_sub_text, font, click=self.click, replace="mega-nerd.com")

            y += spacing
            ddt.text((x, y), "libopenmpt", colours.box_sub_text, font)
            ddt.text((xx, y), "New BSD License", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://lib.openmpt.org/libopenmpt", colours.box_sub_text, font, click=self.click, replace="lib.openmpt.org")

        elif self.cred_page == 5:
            xx = x + round(130 * gui.scale)
            xxx = x + round(240 * gui.scale)
            ddt.text((x, y), "Open source software used (cont'd)", colours.box_text_label, 13)
            font = 12
            spacing = round(18 * gui.scale)
            y += spacing
            ddt.text((x, y), "Mutagen", colours.box_sub_text, font)
            ddt.text((xx, y), "GPLv2+", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://github.com/quodlibet/mutagen", colours.box_sub_text, font, click=self.click, replace="github")

            y += spacing
            ddt.text((x, y), "isounidecode", colours.box_sub_text, font)
            ddt.text((xx, y), "New BSD License", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://github.com/redvasily/isounidecode", colours.box_sub_text, font, click=self.click, replace="github")

            y += spacing
            ddt.text((x, y), "pypresence", colours.box_sub_text, font)
            ddt.text((xx, y), "MIT", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://github.com/qwertyquerty/pypresence", colours.box_sub_text, font, click=self.click, replace="github")

            y += spacing
            ddt.text((x, y), "musicbrainzngs", colours.box_sub_text, font)
            ddt.text((xx, y), "Simplified BSD", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://github.com/alastair/python-musicbrainzngs", colours.box_sub_text, font, click=self.click, replace="github")

            y += spacing
            ddt.text((x, y), "Send2Trash", colours.box_sub_text, font)
            ddt.text((xx, y), "New BSD License", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://github.com/arsenetar/send2trash", colours.box_sub_text, font, click=self.click, replace="github")

            y += spacing
            ddt.text((x, y), "GTK/PyGObject", colours.box_sub_text, font)
            ddt.text((xx, y), "LGPLv2.1+", colours.box_text_label, font)
            draw_linked_text2(xxx, y, "https://gitlab.gnome.org/GNOME/pygobject", colours.box_sub_text, font, click=self.click, replace="gitlab.gnome.org")

        ddt.rect((x, block_y, 369 * gui.scale, 140 * gui.scale), alpha_mod(colours.box_background, fade))

        y = y0 + h0 - round(33 * gui.scale)
        x = x0 + w0 - 0 * gui.scale

        w = max(ddt.get_text_w(_("Credits"), 211), ddt.get_text_w(_("Next"), 211))
        x -= w + round(40 * gui.scale)

        text = _("Credits")
        if self.cred_page != 0:
            text = _("Next")
        if self.button(x, y, text, width = w + round(25 * gui.scale)):
            self.ani_cred = 1
            self.ani_fade_on_timer.set()

    def topchart(self, x0, y0, w0, h0):

        x = x0 + round(25 * gui.scale)
        y = y0 + 20 * gui.scale

        ddt.text_background_colour = colours.box_background

        ddt.text((x, y), _("Chart Grid Generator"), colours.box_text, 214)

        y += 25 * gui.scale
        ww = ddt.text((x, y), _("Target playlist:   "), colours.box_sub_text, 312)
        ddt.text((x + ww, y), pctl.multi_playlist[pctl.active_playlist_viewing][0], colours.box_text_label, 12, 400 * gui.scale)
        #x -= 210 * gui.scale


        y += 30 * gui.scale

        if prefs.chart_cascade:
            if prefs.chart_d1:
                prefs.chart_c1 = self.slide_control(x, y, "Level 1", '', prefs.chart_c1, 2, 20, 1, width=35)
            y += 22 * gui.scale
            if prefs.chart_d2:
                prefs.chart_c2 = self.slide_control(x, y, "Level 2", '', prefs.chart_c2, 2, 20, 1, width=35)
            y += 22 * gui.scale
            if prefs.chart_d3:
                prefs.chart_c3 = self.slide_control(x, y, "Level 3", '', prefs.chart_c3, 2, 20, 1, width=35)

            y -= 44 * gui.scale
            x += 133 * gui.scale
            prefs.chart_d1 = self.slide_control(x, y, "by", '', prefs.chart_d1, 0, 10, 1, width=35)
            y += 22 * gui.scale
            prefs.chart_d2 = self.slide_control(x, y, "by", '', prefs.chart_d2, 0, 10, 1, width=35)
            y += 22 * gui.scale
            prefs.chart_d3 = self.slide_control(x, y, "by", '', prefs.chart_d3, 0, 10, 1, width=35)
            x -= 133 * gui.scale

        else:

            prefs.chart_rows = self.slide_control(x, y, _("Rows"), '', prefs.chart_rows, 1, 100, 1, width=35)
            y += 22 * gui.scale
            prefs.chart_columns = self.slide_control(x, y, _("Columns"), '', prefs.chart_columns, 1, 100, 1, width=35)
            y += 22 * gui.scale


        y += 35 * gui.scale
        x += 5 * gui.scale

        prefs.chart_cascade = self.toggle_square(x, y, prefs.chart_cascade, _("Cascade style"))
        y += 25 * gui.scale
        prefs.chart_tile = self.toggle_square(x, y, prefs.chart_tile ^ True, _("Use padding")) ^ True

        y -= 25 * gui.scale
        x += 170 * gui.scale

        prefs.chart_text = self.toggle_square(x, y, prefs.chart_text, _("Include album titles"))
        y += 25 * gui.scale
        prefs.topchart_sorts_played = self.toggle_square(x, y, prefs.topchart_sorts_played, _("Sort by top played"))


        x = x0 + 15 * gui.scale + 320 * gui.scale
        y = y0 + 100 * gui.scale

        #. Limited width. Max 13 chars.
        if self.button(x, y, _("Randomise BG")):

            r = round(random.random() * 40)
            g = round(random.random() * 40)
            b = round(random.random() * 40)

            prefs.chart_bg = [r, g, b]

            d = random.randrange(0, 4)

            if d == 1:
                c = 5 + round(random.random() * 20)
                prefs.chart_bg = [c, c, c]

        x += 100 * gui.scale
        y -= 20 * gui.scale

        display_colour = (prefs.chart_bg[0], prefs.chart_bg[1], prefs.chart_bg[2], 255)

        rect = (x, y, 70 * gui.scale, 70 * gui.scale)
        ddt.rect(rect, display_colour)

        ddt.rect_s(rect, (50, 50, 50, 255), round(1 * gui.scale))

        # x = self.box_x + self.item_x_offset + 200 * gui.scale
        # y = self.box_y + 180 * gui.scale

        x = x0 + 260 * gui.scale
        y = y0 + 180 * gui.scale

        dex = reload_albums(quiet=True, return_playlist=pctl.active_playlist_viewing)

        x = x0 + round(110 * gui.scale)
        y = y0 + 240 * gui.scale

        #. Limited width. Max 9 chars.
        if self.button(x, y, _("Generate"), width=80*gui.scale):
            if gui.generating_chart:
                show_message("Be patient!")
            else:
                if not prefs.chart_font:
                    show_message("No font set in config", mode='error')
                else:
                    shoot = threading.Thread(target=gen_chart)
                    shoot.daemon = True
                    shoot.start()
                    gui.generating_chart = True

        x += round(95 * gui.scale)
        if gui.generating_chart:
            ddt.text((x , y + round(1 * gui.scale)), _("Generating..."),
                     colours.box_text_label, 12)
        else:

            count = prefs.chart_rows * prefs.chart_columns
            if prefs.chart_cascade:
                count = prefs.chart_c1 * prefs.chart_d1 + prefs.chart_c2 * prefs.chart_d2 + prefs.chart_c3 * prefs.chart_d3

            line = str(count) + " " + _("Album chart")

            ww = ddt.text((x, y + round(1 * gui.scale)), line, colours.box_text_label, 12)

            if len(dex) < count:
                ddt.text((x + ww + round(10 * gui.scale), y + 1 * gui.scale), _("Not enough albums in the playlist!"), [255, 120, 125, 255], 12)

        x = x0 + round(20 * gui.scale)
        y = y0 + 240 * gui.scale

        #. Limited width. Max 8 chars.
        if self.button(x, y, _("Return"), width=75 * gui.scale):
            self.chart_view = 0


    def stats(self, x0, y0, w0, h0):


        x = x0 + 10 * gui.scale
        y = y0

        if self.chart_view == 1:
            self.topchart(x0, y0, w0, h0)
            return

        ww = ddt.get_text_w(_("Chart generator..."), 211) + 30 * gui.scale
        if system == 'linux' and self.button(x0 + w0 - ww, y + 15 * gui.scale, _("Chart generator...")):
            self.chart_view = 1


        ddt.text_background_colour = colours.box_background
        lt_font = 312
        lt_colour = colours.box_text_label

        w1 = ddt.get_text_w(_("Tracks in playlist"), 12)
        w2 = ddt.get_text_w(_("Albums in playlist"), 12)
        w3 = ddt.get_text_w(_("Playlist duration"), 12)
        w4 = ddt.get_text_w(_("Tracks in database"), 12)
        w5 = ddt.get_text_w(_("Total albums"), 12)
        w6 = ddt.get_text_w(_("Total playtime"), 12)


        x1 = x + (8 + 10 + 10) * gui.scale
        x2 = x1 + max(w1, w2, w3, w4, w5, w6) + 20 * gui.scale
        y1 = y + 50 * gui.scale

        if self.stats_pl != pctl.multi_playlist[pctl.active_playlist_viewing][6] or self.stats_pl_timer.get() > 5:
            self.stats_pl = pctl.multi_playlist[pctl.active_playlist_viewing][6]
            self.stats_pl_timer.set()

            album_names = set()
            folder_names = set()
            count = 0

            for track_id in default_playlist:
                tr = pctl.g(track_id)

                if not tr.album:
                    if tr.parent_folder_path not in folder_names:
                        count += 1
                    folder_names.add(tr.parent_folder_path)
                else:
                    if tr.parent_folder_path not in folder_names and tr.album not in album_names:
                        count += 1
                    folder_names.add(tr.parent_folder_path)
                    album_names.add(tr.album)

            self.stats_pl_albums = count

            self.stats_pl_length = 0
            for item in default_playlist:
                self.stats_pl_length += pctl.master_library[item].length

        line = seconds_to_day_hms(self.stats_pl_length, strings.day, strings.days)

        ddt.text((x1, y1), _("Tracks in playlist"), lt_colour, lt_font)
        ddt.text((x2, y1), py_locale.format_string('%d', len(default_playlist), True), colours.box_sub_text, 12)
        y1 += 20 * gui.scale
        ddt.text((x1, y1), _("Albums in playlist"), lt_colour, lt_font)
        ddt.text((x2, y1), str(self.stats_pl_albums), colours.box_sub_text, 12)
        y1 += 20 * gui.scale
        ddt.text((x1, y1), _("Playlist duration"), lt_colour, lt_font)

        ddt.text((x2, y1), line, colours.box_sub_text, 12)

        if self.stats_timer.get() > 5:
            album_names = set()
            folder_names = set()
            count = 0

            for pl in pctl.multi_playlist:
                for track_id in pl[2]:
                    tr = pctl.g(track_id)

                    if not tr.album:
                        if tr.parent_folder_path not in folder_names:
                            count += 1
                        folder_names.add(tr.parent_folder_path)
                    else:
                        if tr.parent_folder_path not in folder_names and tr.album not in album_names:
                            count += 1
                        folder_names.add(tr.parent_folder_path)
                        album_names.add(tr.album)

            self.total_albums = count

            self.stats_timer.set()


        y1 += 40 * gui.scale
        ddt.text((x1, y1), _("Tracks in database"), lt_colour, lt_font)
        ddt.text((x2, y1), py_locale.format_string('%d', len(pctl.master_library), True), colours.box_sub_text, 12)
        y1 += 20 * gui.scale
        ddt.text((x1, y1), _("Total albums"), lt_colour, lt_font)
        ddt.text((x2, y1), str(self.total_albums), colours.box_sub_text, 12)

        y1 += 20 * gui.scale
        ddt.text((x1, y1), _("Total playtime"), lt_colour, lt_font)
        ddt.text((x2, y1), seconds_to_day_hms(pctl.total_playtime, strings.day, strings.days),
                 colours.box_sub_text, 15)


        # Ratio bar
        if len(pctl.master_library) > 115 * gui.scale:
            x = x0
            y = y0 + h0 - 7 * gui.scale

            full_rect = [x, y, w0, 7 * gui.scale]
            d = 0

            # Stats
            try:
                if self.last_db_size != len(pctl.master_library):
                    self.last_db_size = len(pctl.master_library)
                    self.ext_ratio = {}
                    for key, value in pctl.master_library.items():
                        if value.file_ext in self.ext_ratio:
                            self.ext_ratio[value.file_ext] += 1
                        else:
                            self.ext_ratio[value.file_ext] = 1

                for key, value in self.ext_ratio.items():

                    colour = [200, 200, 200 ,255]
                    if key in format_colours:
                        colour = format_colours[key]

                    colour = colorsys.rgb_to_hls(colour[0] / 255, colour[1] / 255, colour[2] / 255)
                    colour = colorsys.hls_to_rgb(1 - colour[0], colour[1] * 0.8, colour[2] * 0.8)
                    colour = [int(colour[0] * 255), int(colour[1] * 255), int(colour[2] * 255), 255]

                    h = int(round(value / len(pctl.master_library) * full_rect[2]))
                    block_rect = [full_rect[0] + d, full_rect[1], h, full_rect[3]]

                    ddt.rect(block_rect, colour)
                    d += h

                    block_rect = (block_rect[0], block_rect[1], block_rect[2] - 1, block_rect[3])
                    fields.add(block_rect)
                    if coll(block_rect):
                        xx = block_rect[0] + int(block_rect[2] / 2)
                        if xx < x + 30 * gui.scale:
                            xx = x + 30 * gui.scale
                        if xx > x0 + w0 - 30 * gui.scale:
                            xx = x0 + w0 - 30 * gui.scale
                        ddt.text((xx, y0 + h0 - 35 * gui.scale, 2), key, colours.grey_blend_bg(220), 13)

                        if self.click:
                            gen_codec_pl(key)
            except:
                print("Error draw ext bar")

    def config_v(self, x0, y0, w0, h0):

        ddt.text_background_colour = colours.box_background

        x = x0 + self.item_x_offset
        y = y0 + 17 * gui.scale

        self.toggle_square(x, y, rating_toggle, _('Track ratings'))
        y += round(25 * gui.scale)
        self.toggle_square(x, y, album_rating_toggle, _('Album ratings'))
        y += round(35 * gui.scale)

        self.toggle_square(x, y, heart_toggle, "     ")
        heart_row_icon.render(x + round(23 * gui.scale), y + round(2 * gui.scale), colours.box_text)
        rect = (x, y + round(2 * gui.scale), 40 * gui.scale, 15 * gui.scale)
        fields.add(rect)
        if coll(rect):
            ex_tool_tip(x + round(45 * gui.scale), y - 20 * gui.scale, 0, _("Show track loves"), 12)

        x += (55 * gui.scale)
        self.toggle_square(x, y, star_toggle, "     ")
        star_row_icon.render(x + round(22 * gui.scale), y + round(0 * gui.scale), colours.box_text)
        rect = (x, y + round(2 * gui.scale), 40 * gui.scale, 15 * gui.scale)
        fields.add(rect)
        if coll(rect):
            ex_tool_tip(x + round(35 * gui.scale), y - 20 * gui.scale, 0, _("Represent playtime as stars"), 12)

        x += (55 * gui.scale)
        self.toggle_square(x, y, star_line_toggle, "     ")
        ddt.rect((x + round(21 * gui.scale), y + round(6 * gui.scale), round(15 * gui.scale), round(1 * gui.scale)),
                 colours.box_text)
        rect = (x, y + round(2 * gui.scale), 40 * gui.scale, 15 * gui.scale)
        fields.add(rect)
        if coll(rect):
            ex_tool_tip(x + round(35 * gui.scale), y - 20 * gui.scale, 0, _("Represent playcount as lines"), 12)

        x = x0 + self.item_x_offset

        #y += round(25 * gui.scale)


        #self.toggle_square(x, y, star_line_toggle, _('Show playtime lines'))
        y += round(15 * gui.scale)



        # if gui.show_ratings:
        #     x += round(10 * gui.scale)
        # #self.toggle_square(x, y, star_toggle, _('Show playtime stars'))
        # if gui.show_ratings:
        #     x -= round(10 * gui.scale)

        y += round(25 * gui.scale)

        self.toggle_square(x, y, toggle_append_date, _('Show album release year'))
        y += round(25 * gui.scale)

        self.toggle_square(x, y, toggle_append_total_time, _('Show album duration'))
        y += round(25 * gui.scale)

        x = x0 + 330 * gui.scale
        y = y0 + 25 * gui.scale


        prefs.playlist_font_size = self.slide_control(x, y, _("Font Size"), "", prefs.playlist_font_size, 12, 17)
        y += 25 * gui.scale
        prefs.playlist_row_height = self.slide_control(x, y, _("Row Size"), "px", prefs.playlist_row_height, 15, 45)
        y += 25 * gui.scale
        prefs.tracklist_y_text_offset = self.slide_control(x, y, _("Baseline offset"), "px", prefs.tracklist_y_text_offset, -10, 10)
        y += 25 * gui.scale

        x += 65 * gui.scale
        self.button(x, y, _("Thin default"), self.small_preset, 124 * gui.scale)
        y += 27 * gui.scale
        self.button(x, y, _("Thick default"), self.large_preset, 124 * gui.scale)

        y += 65 * gui.scale
        # x -= 90 * gui.scale
        x = x0 + self.item_x_offset

        ddt.text((x, y), _("End of playlist action"), colours.box_text_label, 12)

        y += 25 * gui.scale
        wa = ddt.get_text_w(_("Stop playback"), 13) + 10 * gui.scale
        wb = ddt.get_text_w(_("Repeat playlist"), 13) + 10 * gui.scale
        wc = max(wa, wb) + 20 * gui.scale

        self.toggle_square(x, y, self.set_playlist_stop, _("Stop playback"))
        y += 25 * gui.scale
        self.toggle_square(x, y, self.set_playlist_repeat, _("Repeat playlist"))
        #y += 25
        y -= 25 * gui.scale
        x += wc
        self.toggle_square(x, y, self.set_playlist_advance, _("Play next playlist"))
        y += 25 * gui.scale
        self.toggle_square(x, y, self.set_playlist_cycle, _("Cycle all playlists"))

    def set_playlist_cycle(self, mode=0):
        if mode == 1:
            return True if prefs.end_setting == "cycle" else False
        prefs.end_setting = 'cycle'
        # global pl_follow
        # pl_follow = False

    def set_playlist_advance(self, mode=0):
        if mode == 1:
            return True if prefs.end_setting == "advance" else False
        prefs.end_setting = 'advance'
        # global pl_follow
        # pl_follow = False

    def set_playlist_stop(self, mode=0):
        if mode == 1:
            return True if prefs.end_setting == "stop" else False
        prefs.end_setting = 'stop'

    def set_playlist_repeat(self, mode=0):
        if mode == 1:
            return True if prefs.end_setting == "repeat" else False
        prefs.end_setting = 'repeat'


    def small_preset(self):

        prefs.playlist_row_height = round(22 * prefs.ui_scale)
        prefs.playlist_font_size = 15
        prefs.tracklist_y_text_offset = 0
        gui.update_layout()

    def large_preset(self):

        prefs.playlist_row_height = round(27 * prefs.ui_scale)
        prefs.playlist_font_size = 15
        gui.update_layout()

    def slide_control(self, x, y, label, units, value, lower_limit, upper_limit, step=1, callback=None, width=58):

        width = width * gui.scale

        if label is not None:
            ddt.text((x + 55 * gui.scale, y, 1), label, colours.box_text, 312)
            x += 65 * gui.scale
        y += 1 * gui.scale
        rect = (x, y, 33 * gui.scale, 15 * gui.scale)
        fields.add(rect)
        ddt.rect(rect, colours.box_button_background)
        abg = [255, 255, 255, 40]
        if coll(rect):

            if self.click:
                if value > lower_limit:
                    value -= step
                    gui.update_layout()
                    if callback is not None:
                        callback(value)

            if mouse_down:
                abg = [230, 120, 20, 255]
            else:
                abg = [220, 150, 20, 255]

        if colour_value(colours.box_background) > 300:
            abg = colours.box_sub_text

        dec_arrow.render(x + 1 * gui.scale, y, abg)

        x += 33 * gui.scale

        ddt.rect((x, y, width, 15 * gui.scale), alpha_mod(colours.box_button_background, 120))
        ddt.text((x + width / 2, y, 2), str(value) + units, colours.box_sub_text, 312)

        x += width

        rect = (x, y, 33 * gui.scale, 15 * gui.scale)
        fields.add(rect)
        ddt.rect(rect, colours.box_button_background)
        abg = [255, 255, 255, 40]
        if coll(rect):

            if self.click:
                if value < upper_limit:
                    value += step
                    gui.update_layout()
                    if callback is not None:
                        callback(value)
            if mouse_down:
                abg = [230, 120, 20, 255]
            else:
                abg = [220, 150, 20, 255]

        if colour_value(colours.box_background) > 300:
            abg = colours.box_sub_text

        inc_arrow.render(x + 1 * gui.scale, y, abg)

        return value

    # def style_up(self):
    #     prefs.line_style += 1
    #     if prefs.line_style > 5:
    #         prefs.line_style = 1

    def inside(self):

        return coll((self.box_x, self.box_y, self.w, self.h))

    def init2(self):

        self.init2done = True

    def close(self):
        self.enabled = False
        fader.fall()
        if gui.opened_config_file:
            reload_config_file()

    def render(self):

        if self.init2done is False:
            self.init2()

        if key_esc_press:
            self.close()

        tab_width = 115 * gui.scale

        side_width = 115 * gui.scale
        header_width = 0

        top_mode = False
        if window_size[0] < 700 * gui.scale:
            top_mode = True
            side_width = 0 * gui.scale
            header_width = round(48 * gui.scale) #48

        content_width = round(545 * gui.scale)
        content_height = round(275 * gui.scale)  # 275
        full_width = content_width
        full_height = content_height

        full_width += side_width
        full_height += header_width


        x = int(window_size[0] / 2) - int(full_width / 2)
        y = int(window_size[1] / 2) - int(full_height / 2)

        self.box_x = x
        self.box_y = y
        self.w = full_width
        self.h = full_height

        border_colour = colours.box_border

        ddt.rect((x - 5 * gui.scale, y - 5 * gui.scale, full_width + 10 * gui.scale, full_height + 10 * gui.scale),
                 border_colour)
        ddt.rect_a((x, y), (full_width, full_height), colours.box_background)

        current_tab = 0
        tab_height = round(24 * gui.scale) #30

        tab_bg = colours.sys_tab_bg
        tab_hl = colours.sys_tab_hl
        tab_text = rgb_add_hls(tab_bg, 0, 0.3, -0.15)
        tab_over = alpha_mod(rgb_add_hls(tab_bg, 0, 0.5, 0), 13)

        if top_mode:

            xx = x
            yy = y
            tab_width = 90 * gui.scale

            ddt.rect_a((x, y), (full_width, header_width), tab_bg)

            for item in self.tabs:

                if self.click and gui.message_box:
                    gui.message_box = False

                box = [xx, yy, tab_width, tab_height]
                box2 = [xx, yy, tab_width, tab_height - 1]
                fields.add(box2)

                if self.click and coll(box2):
                    self.tab_active = current_tab
                    self.lyrics_panel = False

                if current_tab == self.tab_active:
                    colour = copy.deepcopy(colours.sys_tab_hl)
                    ddt.text_background_colour = colour
                    ddt.rect(box, colour)
                else:
                    ddt.text_background_colour = tab_bg
                    ddt.rect(box, tab_bg)

                if coll(box2):
                    ddt.rect(box, tab_over)

                alpha = 100
                if current_tab == self.tab_active:
                    alpha = 240

                ddt.text((xx + (tab_width // 2), yy + 4 * gui.scale, 2), item[0], tab_text, 212)

                current_tab += 1
                xx += tab_width
                if current_tab == 6:
                    yy += round(24 * gui.scale) #30
                    xx = x

        else:

            ddt.rect_a((x, y), (tab_width, full_height), tab_bg)

            for item in self.tabs:

                if self.click and gui.message_box:
                    if not coll(message_box.get_rect()):
                        gui.message_box = False
                    else:
                        inp.mouse_click = True
                        self.click = False

                box = [x, y + (current_tab * tab_height), tab_width, tab_height]
                box2 = [x, y + (current_tab * tab_height), tab_width, tab_height - 1]
                fields.add(box2)

                if self.click and coll(box2):
                    self.tab_active = current_tab
                    self.lyrics_panel = False

                if current_tab == self.tab_active:
                    bg_colour = copy.deepcopy(colours.sys_tab_hl)
                    ddt.text_background_colour = bg_colour
                    ddt.rect(box, bg_colour)
                else:
                    ddt.text_background_colour = tab_bg
                    ddt.rect(box, tab_bg)


                if coll(box2):
                    ddt.rect(box, tab_over)
                    
                yy = box[1] + 4 * gui.scale

                if current_tab == self.tab_active:
                    ddt.text((box[0] + (tab_width // 2), yy, 2), item[0],
                             alpha_blend(colours.tab_text_active, ddt.text_background_colour), 213)
                else:
                    ddt.text((box[0] + (tab_width // 2), yy, 2), item[0],
                             tab_text, 213)

                current_tab += 1

        #ddt.line(x + 110, self.box_y + 1, self.box_x + 110, self.box_y + self.h, colours.grey(50))

        self.tabs[self.tab_active][1](x + side_width, y + header_width, content_width, content_height)

        self.click = False
        self.right_click = False

        ddt.text_background_colour = colours.box_background




class Fields:
    def __init__(self):

        self.id = []
        self.last_id = []

        self.field_array = []
        self.force = False

    def add(self, rect, callback=None):

        self.field_array.append((rect, callback))

    def test(self):

        if self.force:
            self.force = False
            return True

        self.last_id = self.id
        # print(len(self.id))
        self.id = []

        for f in self.field_array:
            if coll(f[0]):
                self.id.append(1)  # += "1"
                if f[1] is not None:  # Call callback if present
                    f[1]()
            else:
                self.id.append(0)  # += "0"

        if self.last_id == self.id:
            return False

        else:
            return True

    def clear(self):

        self.field_array = []


fields = Fields()

def update_playlist_call():
    gui.update + 2
    gui.pl_update = 2


pref_box = Over()

inc_arrow = asset_loader("inc.png", True)
dec_arrow = asset_loader("dec.png", True)
corner_icon = asset_loader("corner.png", True)

# ----------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------
def pl_is_mut(pl):
    id = pl_to_id(pl)
    if id is None:
        return False
    return not (pctl.gen_codes.get(id) and "self" not in pctl.gen_codes[id])

class TopPanel:
    def __init__(self):

        self.height = gui.panelY
        self.ty = 0

        self.start_space_left = round(46 * gui.scale)
        self.start_space_compact_left = 46 * gui.scale

        self.tab_text_font = fonts.tabs
        self.tab_extra_width = round(17 * gui.scale)
        self.tab_text_start_space = 8 * gui.scale
        self.tab_text_y_offset = 7 * gui.scale
        self.tab_spacing = 0

        self.ini_menu_space = 17 * gui.scale  # 17
        self.menu_space = 17 * gui.scale
        self.click_buffer = 4 * gui.scale

        self.tabs_right_x = 0  # computed for drag and drop code elsewhere (hacky)
        self.tabs_left_x = 1

        self.prime_tab = gui.saved_prime_tab
        self.prime_side = gui.saved_prime_direction  # 0=left, 1=right
        self.shown_tabs = []

        # ---
        self.space_left = 0
        self.tab_text_spaces = []
        self.index_playing = -1
        self.drag_zone_start_x = 300 * gui.scale

        self.exit_button = asset_loader('ex.png', True)
        self.maximize_button = asset_loader('max.png', True)
        self.restore_button = asset_loader('restore.png', True)
        self.restore_button = asset_loader('restore.png', True)
        self.playlist_icon = asset_loader('playlist.png', True)
        self.return_icon = asset_loader('return.png', True)
        self.artist_list_icon = asset_loader('artist-list.png', True)
        self.folder_list_icon = asset_loader('folder-list.png', True)
        self.dl_button = asset_loader('dl.png', True)
        self.overflow_icon = asset_loader('overflow.png', True)

        self.drag_slide_timer = Timer(100)
        self.tab_d_click_timer = Timer(10)
        self.tab_d_click_ref = None

        self.adds = []

    def left_overflow_switch_playlist(self, pl):
        self.prime_side = 0
        self.prime_tab = pl
        switch_playlist(pl)

    def right_overflow_switch_playlist(self, pl):
        self.prime_side = 1
        self.prime_tab = pl
        switch_playlist(pl)

    def render(self):

        # C-TD
        global quick_drag
        global update_layout

        hh = gui.panelY2
        yy = gui.panelY - hh
        self.height = hh

        if quick_drag is True:
            #gui.pl_update = 1
            gui.update_on_drag = True

        # Draw the background
        ddt.rect((0, 0, window_size[0], gui.panelY), colours.top_panel_background)

        if prefs.shuffle_lock and not gui.compact_bar:
            colour = [250, 250, 250, 255]
            if colours.lm:
                colour = [10, 10, 10, 255]
            if not pctl.broadcast_active:
                text = "Tauon Music Box SHUFFLE!"
                if pctl.album_shuffle_lock_mode:
                    text = "Tauon Music Box ALBUM SHUFFLE!"
                ddt.text((window_size[0] // 2, 8 * gui.scale, 2), text, colour,
                                          212, bg=colours.top_panel_background)
        if gui.top_bar_mode2:
            tr = pctl.playing_object()
            if tr:
                album_art_gen.display(tr, (window_size[0] - gui.panelY - 1, 0), (gui.panelY, gui.panelY),)
                if loading_in_progress or\
                        to_scan or \
                        cm_clean_db or \
                        lastfm.scanning_friends or \
                        pctl.broadcast_active or \
                        after_scan or \
                        move_in_progress or \
                        plex.scanning or \
                        transcode_list or spot_ctl.launching_spotify or spot_ctl.spotify_com or subsonic.scanning or \
                        koel.scanning or gui.sync_progress or lastfm.scanning_scrobbles:
                    ddt.rect(
                        (window_size[0] - (gui.panelY + 20), gui.panelY - gui.panelY2, gui.panelY + 25, gui.panelY2),
                        colours.top_panel_background)

                maxx = window_size[0] - (gui.panelY + 30 * gui.scale)
                title_colour = colours.grey(249)
                if colours.lm:
                    title_colour = colours.grey(30)
                title = tr.title
                if not title:
                    title = tr.filename
                artist = tr.artist

                if pctl.playing_state == 3 and not radiobox.dummy_track.title:
                    title = pctl.tag_meta
                    artist = radiobox.loaded_url #pctl.url

                ddt.text_background_colour = colours.top_panel_background

                ddt.text((round(14 * gui.scale), round(15 * gui.scale)), title, title_colour, 215, max_w=maxx)
                ddt.text((round(14 * gui.scale), round(40 * gui.scale)), artist, colours.grey(120), 315, max_w=maxx)

        wwx = 0
        if prefs.left_window_control and not gui.compact_bar:
            if gui.macstyle:
                wwx = 24
                #wwx = round(64 * gui.scale)
                if draw_min_button:
                    wwx += 20
                if draw_max_button:
                    wwx += 20
                wwx = round(wwx * gui.scale)
            else:
                wwx = 26
                #wwx = round(90 * gui.scale)
                if draw_min_button:
                    wwx += 35
                if draw_max_button:
                    wwx += 33
                wwx = round(wwx * gui.scale)


        rect = (wwx + 9 * gui.scale, yy + 4 * gui.scale, 34 * gui.scale, 25 * gui.scale)
        fields.add(rect)

        if coll(rect) and not prefs.shuffle_lock:
            if inp.mouse_click:

                if gui.combo_mode:
                    gui.switch_showcase_off = True
                else:
                    gui.lsp ^= True

                update_layout = True
                gui.update += 1
            if mouse_down and quick_drag:
                gui.lsp = True
                update_layout = True
                gui.update += 1

            if middle_click:
                toggle_left_last()
                update_layout = True
                gui.update += 1

            if right_click:
                #prefs.artist_list ^= True
                lsp_menu.activate(position=(5 * gui.scale, gui.panelY))
                update_layout_do()

        colour = colours.corner_button #[230, 230, 230, 255]

        if gui.lsp:
            colour = colours.corner_button_active
        if gui.combo_mode:
            colour = colours.corner_button
            if coll(rect):
                colour = colours.corner_button_active

        if not prefs.shuffle_lock:
            if gui.combo_mode:
                self.return_icon.render(wwx + 14 * gui.scale, yy + 8 * gui.scale, colour)
            elif prefs.left_panel_mode == "artist list":
                self.artist_list_icon.render(wwx + 13 * gui.scale, yy + 8 * gui.scale, colour)
            elif prefs.left_panel_mode == "folder view":
                self.folder_list_icon.render(wwx + 14 * gui.scale, yy + 8 * gui.scale, colour)
            else:
                self.playlist_icon.render(wwx + 13 * gui.scale, yy + 8 * gui.scale, colour)

        # if prefs.artist_list:
        #     self.artist_list_icon.render(13 * gui.scale, yy + 8 * gui.scale, colour)
        # else:
        #     self.playlist_icon.render(13 * gui.scale, yy + 8 * gui.scale, colour)

        if playlist_box.drag:
            drag_mode = False

        # Need to test length
        self.tab_text_spaces = []

        if gui.radio_view:
            for item in pctl.radio_playlists:
                le = ddt.get_text_w(item["name"], self.tab_text_font)
                self.tab_text_spaces.append(le)
        else:
            for i, item in enumerate(pctl.multi_playlist):
                le = ddt.get_text_w(pctl.multi_playlist[i][0], self.tab_text_font)
                self.tab_text_spaces.append(le)

        x = self.start_space_left + wwx
        y = yy #self.ty

        # Calculate position for playing text and text
        offset = 15 * gui.scale
        if draw_border and not prefs.left_window_control:
            offset += 61 * gui.scale
            if draw_max_button:
                offset += 61 * gui.scale
        if gui.turbo:
            offset += 90 * gui.scale
            if gui.vis == 3:
                offset += 57 * gui.scale
        if gui.top_bar_mode2:
            offset = 0

        if pctl.broadcast_active:

            p_text_len = ddt.get_text_w(pctl.master_library[pctl.broadcast_index].artist + " - " + pctl.master_library[
                pctl.broadcast_index].title, 11)

            p_text_len += ddt.get_text_w("Now Streaming:", 11)
            p_text_len += 20 * gui.scale
            p_text_len += 180 * gui.scale

        # elif gui.show_top_title:
        #
        #     p_text = trunc_line(pctl.title_text(), 12, window_size[0] - offset - 120 * gui.scale)
        #     p_text_len = ddt.get_text_w(p_text, 12) + 70 * gui.scale

        else:
            p_text_len = 180 * gui.scale

        right_space_es = p_text_len + offset

        x_start = x

        if playlist_box.drag and not gui.radio_view:
            if mouse_up:
                if mouse_up_position[0] > (gui.lspw if gui.lsp else 0) and mouse_up_position[1] > gui.panelY:
                    playlist_box.drag = False
                    if prefs.drag_to_unpin:
                        if playlist_box.drag_source == 0:
                            pctl.multi_playlist[playlist_box.drag_on][8] = True
                        else:
                            pctl.multi_playlist[playlist_box.drag_on][8] = False
                    gui.update += 1
            gui.update_on_drag = True


        # List all tabs eligible to be shown
        #print("-------------")
        ready_tabs = []
        show_tabs = []

        if prefs.tabs_on_top or gui.radio_view:
            if gui.radio_view:
                for i, tab in enumerate(pctl.radio_playlists):
                    ready_tabs.append(i)
                if self.prime_tab > len(pctl.radio_playlists) - 1:
                    self.prime_tab = len(pctl.radio_playlists) - 1
            else:
                for i, tab in enumerate(pctl.multi_playlist):
                    # Skip if hide flag is set
                    if tab[8] is True:
                        continue
                    ready_tabs.append(i)
                if self.prime_tab > len(pctl.multi_playlist) - 1:
                    self.prime_tab = len(pctl.multi_playlist) - 1
            max_w = window_size[0] - (x + right_space_es + round(34 * gui.scale))

            left_tabs = []
            right_tabs = []
            if prefs.shuffle_lock:
                for p in ready_tabs:
                    left_tabs.append(p)

            else:
                for p in ready_tabs:
                    if p < self.prime_tab:
                        left_tabs.append(p)

                for p in ready_tabs:
                    if p > self.prime_tab:
                        right_tabs.append(p)
                left_tabs.reverse()

            run = max_w

            if self.prime_tab in ready_tabs:
                size = self.tab_text_spaces[self.prime_tab] + self.tab_extra_width
                if size < run:
                    show_tabs.append(self.prime_tab)
                    run -= size

            if self.prime_side == 0:
                for tab in right_tabs:
                    size = self.tab_text_spaces[tab] + self.tab_extra_width
                    if size < run:
                        show_tabs.append(tab)
                        run -= size
                    else:
                        break
                for tab in left_tabs:
                    size = self.tab_text_spaces[tab] + self.tab_extra_width
                    if size < run:
                        show_tabs.insert(0, tab)
                        run -= size
                    else:
                        break
            else:
                for tab in left_tabs:
                    size = self.tab_text_spaces[tab] + self.tab_extra_width
                    if size < run:
                        show_tabs.insert(0, tab)
                        run -= size
                    else:
                        break
                for tab in right_tabs:
                    size = self.tab_text_spaces[tab] + self.tab_extra_width
                    if size < run:
                        show_tabs.append(tab)
                        run -= size
                    else:
                        break

            # for tab in show_tabs:
            #     print(pctl.multi_playlist[tab][0])
            # print("---")
            left_overflow = [x for x in left_tabs if x not in show_tabs]
            right_overflow = [x for x in right_tabs if x not in show_tabs]
            self.shown_tabs = show_tabs

            if left_overflow:
                hh = round(20 * gui.scale)
                rect = [x, y + (self.height - hh), 17 * gui.scale, hh]
                ddt.rect(rect, colours.tab_background)
                self.overflow_icon.render(rect[0] + round(3 * gui.scale), rect[1] + round(4 * gui.scale), colours.tab_text)

                x += 17 * gui.scale
                x_start = x

                if inp.mouse_click and coll(rect):
                    overflow_menu.items.clear()
                    for tab in reversed(left_overflow):
                        if gui.radio_view:
                            overflow_menu.add(pctl.radio_playlists[tab]["name"], self.left_overflow_switch_playlist, pass_ref=True, set_ref=tab)
                        else:
                            overflow_menu.add(pctl.multi_playlist[tab][0], self.left_overflow_switch_playlist, pass_ref=True, set_ref=tab)
                    overflow_menu.activate(0, (rect[0], rect[1] + rect[3]))

            xx = x + (max_w - run) #+ round(6 * gui.scale)
            self.tabs_left_x = x_start

            if right_overflow:
                hh = round(20 * gui.scale)
                rect = [xx, y + (self.height - hh), 17 * gui.scale, hh]
                ddt.rect(rect, colours.tab_background)
                self.overflow_icon.render(rect[0] + round(3 * gui.scale), rect[1] + round(4 * gui.scale), colours.tab_text)
                if inp.mouse_click and coll(rect):
                    overflow_menu.items.clear()
                    for tab in right_overflow:
                        if gui.radio_view:
                            overflow_menu.add(pctl.radio_playlists[tab]["name"], self.left_overflow_switch_playlist,
                                              pass_ref=True, set_ref=tab)
                        else:
                            overflow_menu.add(pctl.multi_playlist[tab][0], self.left_overflow_switch_playlist,
                                              pass_ref=True, set_ref=tab)
                    overflow_menu.activate(0, (rect[0], rect[1] + rect[3]))

            if gui.radio_view:
                if not mouse_down and pctl.radio_playlist_viewing not in show_tabs and pctl.radio_playlist_viewing in ready_tabs:
                    if pctl.radio_playlist_viewing < self.prime_tab:
                        self.prime_side = 0
                    elif pctl.radio_playlist_viewing > self.prime_tab:
                        self.prime_side = 1
                    self.prime_tab = pctl.radio_playlist_viewing
                    gui.update += 1
            else:
                if not mouse_down and pctl.active_playlist_viewing not in show_tabs and pctl.active_playlist_viewing in ready_tabs:
                    if pctl.active_playlist_viewing < self.prime_tab:
                        self.prime_side = 0
                    elif pctl.active_playlist_viewing > self.prime_tab:
                        self.prime_side = 1
                    self.prime_tab = pctl.active_playlist_viewing
                    gui.update += 1

            if playlist_box.drag and mouse_position[0] > xx and mouse_position[1] < gui.panelY:
                gui.update += 1
                if 0.5 < self.drag_slide_timer.get() < 1 and show_tabs and right_overflow:
                    self.drag_slide_timer.set()
                    self.prime_side = 1
                    self.prime_tab = right_overflow[0]
                if self.drag_slide_timer.get() > 1:
                    self.drag_slide_timer.set()
            if playlist_box.drag and mouse_position[0] < x and mouse_position[1] < gui.panelY:
                gui.update += 1
                if 0.5 < self.drag_slide_timer.get() < 1 and show_tabs and left_overflow:
                    self.drag_slide_timer.set()
                    self.prime_side = 0
                    self.prime_tab = left_overflow[0]
                if self.drag_slide_timer.get() > 1:
                    self.drag_slide_timer.set()



        # TAB INPUT PROCESSING
        target = pctl.multi_playlist
        if gui.radio_view:
            target = pctl.radio_playlists
        for i, tab in enumerate(target):

            if not gui.radio_view:
                if not prefs.tabs_on_top or prefs.shuffle_lock:
                    break

                if len(pctl.multi_playlist) != len(self.tab_text_spaces):
                    break

            if i not in show_tabs:
                continue

            # Determine the tab width
            tab_width = self.tab_text_spaces[i] + self.tab_extra_width

            # Save the far right boundary of the tabs (hacky)
            self.tabs_right_x = x + tab_width

            # Detect mouse over and add tab to mouse over detection
            f_rect = [x, y + 1, tab_width - 1, self.height - 1]
            tab_hit = coll(f_rect)

            # Tab functions
            if tab_hit:
                if not gui.radio_view:
                    # Double click to play
                    if mouse_up and pl_to_id(i) == self.tab_d_click_ref == pl_to_id(pctl.active_playlist_viewing) and \
                        self.tab_d_click_timer.get() < 0.25 and point_distance(last_click_location, mouse_up_position) < 5 * gui.scale:

                        if pctl.playing_state == 2 and pctl.active_playlist_playing == i:
                            pctl.play()
                        elif pctl.selected_ready() and (pctl.playing_state != 1 or pctl.active_playlist_playing != i):
                            pctl.jump(default_playlist[playlist_selected], pl_position=playlist_selected)
                    if mouse_up:
                        self.tab_d_click_timer.set()
                        self.tab_d_click_ref = pl_to_id(i)

                # Click to change playlist
                if inp.mouse_click:
                    gui.pl_update = 1
                    playlist_box.drag = True
                    playlist_box.drag_source = 0
                    playlist_box.drag_on = i
                    if gui.radio_view:
                        pctl.radio_playlist_viewing = i
                    else:
                        switch_playlist(i)
                    set_drag_source()


                # Drag to move playlist
                if mouse_up and playlist_box.drag and coll_point(mouse_up_position, f_rect):

                    if gui.radio_view:
                        move_radio_playlist(playlist_box.drag_on, i)
                    else:
                        if playlist_box.drag_source == 1:
                            pctl.multi_playlist[playlist_box.drag_on][8] = False

                        if i != playlist_box.drag_on:

                            # # Reveal the tab in case it has been hidden
                            # pctl.multi_playlist[playlist_box.drag_on][8] = False

                            if key_shift_down:
                                pctl.multi_playlist[i][2] += pctl.multi_playlist[playlist_box.drag_on][2]
                                delete_playlist(playlist_box.drag_on, check_lock=True)
                            else:
                                move_playlist(playlist_box.drag_on, i)

                    playlist_box.drag = False
                    gui.update += 1

                # Delete playlist on wheel click
                elif tab_menu.active is False and middle_click:
                    #delete_playlist(i)
                    delete_playlist_ask(i)
                    break

                # Activate menu on right click
                elif right_click:
                    if gui.radio_view:
                        radio_tab_menu.activate(copy.deepcopy(i))
                    else:
                        tab_menu.activate(copy.deepcopy(i))
                    gui.tab_menu_pl = i

                # Quick drop tracks
                elif quick_drag is True and mouse_up:
                    self.tab_d_click_ref = -1
                    self.tab_d_click_timer.force_set(100)
                    if not (pctl.gen_codes.get(pl_to_id(i)) and "self" not in pctl.gen_codes[pl_to_id(i)]):
                        quick_drag = False
                        modified = False
                        gui.pl_update += 1

                        for item in shift_selection:
                            pctl.multi_playlist[i][2].append(default_playlist[item])
                            modified = True
                        if len(shift_selection) > 0:
                            modified = True
                            self.adds.append([pctl.multi_playlist[i][6], len(shift_selection), Timer()]) # ID, num, timer

                        if modified:
                            pctl.after_import_flag = True
                            pctl.notify_change()
                            pctl.update_shuffle_pool(pctl.multi_playlist[i][6], shift_selection)
                            tree_view_box.clear_target_pl(i)
                            tm.ready("worker")

                if mouse_up and radio_view.drag:
                    pctl.radio_playlists[i]["items"].append(radio_view.drag)
                    toast(_("Added station to: ") + pctl.radio_playlists[i]["name"])

                    radio_view.drag = None

            x += tab_width + self.tab_spacing

        # Test dupelicate tab function
        if playlist_box.drag:
            rect = (0, x, self.height, window_size[0])
            fields.add(rect)

        if mouse_up and playlist_box.drag and mouse_position[0] > x and mouse_position[1] < self.height:
            if gui.radio_view:
                pass
            else:
                if key_ctrl_down:
                    gen_dupe(playlist_box.drag_on)

                else:
                    if playlist_box.drag_source == 1:
                        pctl.multi_playlist[playlist_box.drag_on][8] = False

                    move_playlist(playlist_box.drag_on, i)
            playlist_box.drag = False

        # Need to test length again
        # Need to test length
        self.tab_text_spaces = []

        if gui.radio_view:
            for item in pctl.radio_playlists:
                le = ddt.get_text_w(item["name"], self.tab_text_font)
                self.tab_text_spaces.append(le)
        else:
            for i, item in enumerate(pctl.multi_playlist):
                le = ddt.get_text_w(pctl.multi_playlist[i][0], self.tab_text_font)
                self.tab_text_spaces.append(le)

        # Reset X draw position
        x = x_start
        bar_highlight_size = round(2 * gui.scale)

        # TAB DRAWING
        shown = []
        for i, tab in enumerate(target):

            if not gui.radio_view:
                if not prefs.tabs_on_top or prefs.shuffle_lock:
                    break

                if len(pctl.multi_playlist) != len(self.tab_text_spaces):
                    break

            # if tab[8] is True:
            #     continue

            if i not in show_tabs:
                continue

            # if window_size[0] - x - (self.tab_text_spaces[i] + self.tab_extra_width) < right_space_es:
            #     break

            shown.append(i)

            tab_width = self.tab_text_spaces[i] + self.tab_extra_width
            rect = [x, y, tab_width, self.height]

            # Detect mouse over and add tab to mouse over detection
            f_rect = [x, y + 1, tab_width - 1, self.height - 1]
            fields.add(f_rect)
            tab_hit = coll(f_rect)
            playing_hint = False
            active = False

            # Determine tab background colour
            if not gui.radio_view:
                if i == pctl.active_playlist_viewing:
                    bg = colours.tab_background_active
                    active = True
                elif (tab_menu.active is True and tab_menu.reference == i) or tab_menu.active is False and tab_hit and not playlist_box.drag:
                    bg = colours.tab_highlight
                elif i == pctl.active_playlist_playing:
                    bg = colours.tab_background
                    playing_hint = True
                else:
                    bg = colours.tab_background
            else:
                if pctl.radio_playlist_viewing == i:
                    bg = colours.tab_background_active
                    active = True
                else:
                    bg = colours.tab_background

            # Draw tab background
            ddt.rect(rect, bg)
            if playing_hint:
                ddt.rect(rect, [255, 255, 255, 7])


            # Determine text colour
            if active:
                fg = colours.tab_text_active
            else:
                fg = colours.tab_text

            # Draw tab text
            if gui.radio_view:
                text = tab["name"]
            else:
                text = tab[0]
            ddt.text((x + self.tab_text_start_space, y + self.tab_text_y_offset), text, fg, self.tab_text_font, bg=bg)

            # Drop pulse
            if gui.pl_pulse and gui.drop_playlist_target == i:
                    if tab_pulse.render(x, y + self.height - bar_highlight_size, tab_width, bar_highlight_size, r=200, g=130) is False:
                        gui.pl_pulse = False

            # Drag to move playlist
            if tab_hit:
                if mouse_down and i != playlist_box.drag_on and playlist_box.drag is True:

                    if key_shift_down:
                        ddt.rect((x, y + self.height - bar_highlight_size, tab_width, bar_highlight_size), [80, 160, 200, 255])
                    else:
                        if playlist_box.drag_on < i:
                            ddt.rect((x + tab_width - bar_highlight_size, y, bar_highlight_size, gui.panelY2), [80, 160, 200, 255])
                        else:
                            ddt.rect((x, y, bar_highlight_size, gui.panelY2), [80, 160, 200, 255])

                elif quick_drag is True and pl_is_mut(i):
                    ddt.rect((x, y + self.height - bar_highlight_size, tab_width, bar_highlight_size), [80, 200, 180, 255])
            # Drag yellow line highlight if single track already in playlist
            elif quick_drag and not point_proximity_test(gui.drag_source_position, mouse_position, 15 * gui.scale):
                for item in shift_selection:
                    if default_playlist[item] in tab[2]:
                        ddt.rect((x, y + self.height - bar_highlight_size, tab_width, bar_highlight_size), [190, 160, 20, 255])
                        break
            # Drag red line highlight if playlist is generator playlist
            if quick_drag and not point_proximity_test(gui.drag_source_position, mouse_position, 15 * gui.scale):
                if not pl_is_mut(i):
                    ddt.rect((x, y + self.height - bar_highlight_size, tab_width, bar_highlight_size), [200, 70, 50, 255])

            if not gui.radio_view:
                if len(self.adds) > 0:
                    for k in reversed(range(len(self.adds))):
                        if pctl.multi_playlist[i][6] == self.adds[k][0]:
                            if self.adds[k][2].get() > 0.3:
                                del self.adds[k]
                            else:
                                ay = y + 4
                                ay -= 6 * self.adds[k][2].get() / 0.3

                                ddt.text((x + tab_width - 3, int(round(ay)), 1), '+' + str(self.adds[k][1]), colours.pluse_colour, 212, bg=bg)
                                gui.update += 1


            x += tab_width + self.tab_spacing

        # Quick drag single track onto bar to create new playlist function and indicator
        if prefs.tabs_on_top:
            if quick_drag and mouse_position[0] > x and mouse_position[1] < gui.panelY and quick_d_timer.get() > 1:
                ddt.rect((x, y, 2 * gui.scale, gui.panelY2), [80, 200, 180, 255])

                if mouse_up:
                    drop_tracks_to_new_playlist(shift_selection)

            # Draw end drag tab indicator
            if playlist_box.drag and mouse_position[0] > x and mouse_position[1] < gui.panelY:
                if key_ctrl_down:
                    ddt.rect((x, y, 2 * gui.scale, gui.panelY2), [255, 190, 0, 255])
                else:
                    ddt.rect((x, y, 2 * gui.scale, gui.panelY2), [80, 160, 200, 255])

        if prefs.tabs_on_top and right_overflow:
            x += 24 * gui.scale
            self.tabs_right_x += 24 * gui.scale

        # -------------
        # Other input
        if mouse_up:
            quick_drag = False
            playlist_box.drag = False
            radio_view.drag = None

        # Scroll anywhere on panel to cycle playlist
        # (This is a bit complicated because we need to skip over hidden playlists)
        if mouse_wheel != 0 and 1 < mouse_position[1] < gui.panelY + 1 and len(pctl.multi_playlist) > 1 and 5 < mouse_position[0]:

            cycle_playlist_pinned(mouse_wheel)

            gui.pl_update = 1
            if not prefs.tabs_on_top:
                if pctl.active_playlist_viewing not in shown: # and not gui.lsp:
                    gui.mode_toast_text = _(pctl.multi_playlist[pctl.active_playlist_viewing][0])
                    toast_mode_timer.set()
                    gui.frame_callback_list.append(TestTimer(1))
                else:
                    toast_mode_timer.force_set(10)
                    gui.mode_toast_text = ""
        # ---------
        # Menu Bar

        x += self.ini_menu_space
        y += 7 * gui.scale
        ddt.text_background_colour = colours.top_panel_background

        # MENU -----------------------------

        word = "MENU"
        word_length = ddt.get_text_w(word, 212)
        rect = [x - self.click_buffer, yy + self.ty + 1, word_length + self.click_buffer * 2, self.height - 1]
        hit = coll(rect)
        fields.add(rect)

        if (x_menu.active or hit) and not tab_menu.active:
            bg = colours.status_text_over
        else:
            bg = colours.status_text_normal
        ddt.text((x, y), word, bg, 212)

        if hit and inp.mouse_click:
            if x_menu.active:
                x_menu.active = False
            else:
                xx = x
                if x > window_size[0] - (210 * gui.scale):
                    xx = window_size[0] - round(210 * gui.scale)
                x_menu.activate(position=(xx + round(12 * gui.scale), gui.panelY))
                view_box.activate(xx)


        # if True:
        #     border = round(3 * gui.scale)
        #     border_colour = colours.grey(30)
        #     rect = (5 * gui.scale, gui.panelY, round(90 * gui.scale), round(25 * gui.scale))
        #

        dl = len(dl_mon.ready)
        watching = len(dl_mon.watching)

        if (dl > 0 or watching > 0) and core_timer.get() > 2 and prefs.auto_extract and prefs.monitor_downloads:
            x += 52 * gui.scale
            rect = (x - 5 * gui.scale, y - 2 * gui.scale, 30 * gui.scale, 23 * gui.scale)
            fields.add(rect)

            if coll(rect):
                colour = colours.corner_button_active
                # if colours.lm:
                #     colour = [40, 40, 40, 255]
                if dl > 0 or watching > 0:
                    if right_click:
                        dl_menu.activate(position=(mouse_position[0], gui.panelY))
                if dl > 0:
                    if inp.mouse_click:
                        pln = 0
                        for item in dl_mon.ready:
                            load_order = LoadClass()
                            load_order.target = item
                            pln = pctl.active_playlist_viewing
                            load_order.playlist = pctl.multi_playlist[pln][6]

                            for i, pl in enumerate(pctl.multi_playlist):
                                if prefs.download_playlist is not None:
                                    if pl[6] == prefs.download_playlist:
                                        load_order.playlist = pl[6]
                                        pln = i
                                        break
                            else:
                                for i, pl in enumerate(pctl.multi_playlist):
                                    if pl[0].lower() == "downloads":
                                        load_order.playlist = pl[6]
                                        pln = i
                                        break

                            load_orders.append(copy.deepcopy(load_order))

                        if len(dl_mon.ready) > 0:
                            dl_mon.ready.clear()
                            switch_playlist(pln)

                            pctl.playlist_view_position = len(default_playlist)
                            console.print("DEBUG: Position changed by track import")
                            gui.update += 1
                else:
                    colour = colours.corner_button #[60, 60, 60, 255]
                    # if colours.lm:
                    #     colour = [180, 180, 180, 255]
                    if inp.mouse_click:
                        inp.mouse_click = False
                        show_message("It looks like something is being downloaded...", "Let's check back later...",
                                     mode='info')


            else:
                colour = colours.corner_button #[60, 60, 60, 255]
                if colours.lm:
                    #colour = [180, 180, 180, 255]
                    if dl_mon.ready:
                        colour = colours.corner_button_active # [60, 60, 60, 255]

            self.dl_button.render(x, y + 1 * gui.scale, colour)
            if dl > 0:
                ddt.text((x + 18 * gui.scale, y - 4 * gui.scale), str(dl), colours.pluse_colour, 209) #[244, 223, 66, 255]
                # [166, 244, 179, 255]


        # LAYOUT --------------------------------
        x += self.menu_space + word_length

        self.drag_zone_start_x = x - 11 * gui.scale
        status = True

        if loading_in_progress:

            bg = colours.status_info_text
            if to_got == 'xspf':
                text = "Importing XSPF playlist"
            elif to_got == 'xspfl':
                text = "Importing XSPF playlist..."
            elif to_got == 'ex':
                text = "Extracting Archive..."
            else:
                text = "Importing...  " + str(to_got)  # + "/" + str(to_get)
                if right_click and coll([x, y, 180 * gui.scale, 18 * gui.scale]):
                    cancel_menu.activate(position=(x + 20 * gui.scale, y + 23 * gui.scale))
        elif after_scan:
            #bg = colours.status_info_text
            bg = [100, 200, 100, 255]
            text = "Scanning Tags...  " + str(len(after_scan)) + " remaining"

        elif move_in_progress:
            text = "File copy in progress..."
            bg = colours.status_info_text
        elif cm_clean_db and to_get > 0:
            per = str(int(to_got / to_get * 100))
            text = "Cleaning db...  " + per + "%"
            bg = [100, 200, 100, 255]
        elif to_scan:
            text = "Rescanning Tags...  " + str(len(to_scan)) + " Remaining"
            bg = [100, 200, 100, 255]
        elif plex.scanning:
            text = "Accessing PLEX library..."
            if gui.to_got:
                text += f" {gui.to_got}"
            bg = [229, 160, 13, 255]
        elif spot_ctl.launching_spotify:
            text = "Launching Spotify..."
            bg = [30, 215, 96, 255]
        elif spot_ctl.spotify_com:
            text = "Accessing Spotify library..."
            bg = [30, 215, 96, 255]
        elif subsonic.scanning:
            text = "Accessing AIRSONIC library..."
            if gui.to_got:
                text += f" {gui.to_got}"
            bg = [58, 194, 224, 255]
        elif koel.scanning:
            text = "Accessing KOEL library..."
            bg = [111, 98, 190, 255]
        elif jellyfin.scanning:
            text = "Accessing JELLYFIN library..."
            bg = [90, 170, 240, 255]
        elif gui.sync_progress and not transcode_list:
            text = gui.sync_progress
            bg = [100, 200, 100, 255]
            if right_click and coll([x, y, 280 * gui.scale, 18 * gui.scale]):
                cancel_menu.activate(position=(x + 20 * gui.scale, y + 23 * gui.scale))
        elif transcode_list and gui.tc_cancel:
            bg = [150, 150, 150, 255]
            text = "Stopping transcode..."
        elif pctl.encoder_pause == 1 and pctl.broadcast_active:
            text = "Streaming Paused"
            bg = colours.streaming_text
        elif lastfm.scanning_friends or lastfm.scanning_loves:
            text = "Scanning: " + lastfm.scanning_username
            bg = [200, 150, 240, 255]
        elif lastfm.scanning_scrobbles:
            text = "Scanning Scrobbles..."
            bg = [219, 88, 18, 255]
        elif gui.buffering:
            text = _("Buffering... ") + str(gui.buffering) + "%"
            bg = [18, 180, 180, 255]

        elif lfm_scrobbler.queue and scrobble_warning_timer.get() < 260:
            text = "Network error. Will try again later."
            bg = [250, 250, 250, 255]
            last_fm_icon.render(x - 4 * gui.scale, y + 4 * gui.scale, [250, 40, 40, 255])
            x += 21 * gui.scale
        else:
            status = False


        if status:
            x += ddt.text((x, y), text, bg, 311)
            # x += ddt.get_text_w(text, 11)

        elif transcode_list:
            bg = colours.status_info_text
            # if key_ctrl_down and key_c_press:
            #     del transcode_list[1:]
            #     gui.tc_cancel = True
            if right_click and coll([x, y, 280 * gui.scale, 18 * gui.scale]):
                cancel_menu.activate(position=(x + 20 * gui.scale, y + 23 * gui.scale))


            w = 100 * gui.scale
            x += ddt.text((x, y), "Transcoding", bg, 311) + 8 * gui.scale


            if gui.transcoding_batch_total:

                # c1 = [40, 40, 40, 255]
                # c2 = [60, 60, 60, 255]
                # c3 = [130, 130, 130, 255]
                #
                # if colours.lm:
                #     c1 = [100, 100, 100, 255]
                #     c2 = [130, 130, 130, 255]
                #     c3 = [180, 180, 180, 255]

                c1 = [40, 40, 40, 255]
                c2 = [100, 59, 200, 200]
                c3 = [150, 70, 200, 255]

                if colours.lm:
                    c1 = [100, 100, 100, 255]
                    c2 = [170, 140, 255, 255]
                    c3 = [230, 170, 255, 255]

                yy = y + 4 * gui.scale
                h = 9 * gui.scale
                box = [x, yy, w, h]
                #ddt.rect_r(box, [100, 100, 100, 255])
                ddt.rect(box, c1)

                done = round(gui.transcoding_bach_done / gui.transcoding_batch_total * 100)
                doing = round(core_use / gui.transcoding_batch_total * 100)

                ddt.rect([x, yy, done, h], c3)
                ddt.rect([x + done, yy, doing, h], c2)

            x += w + 8 * gui.scale

            if gui.sync_progress:
                text = gui.sync_progress
            else:
                text = str(len(transcode_list)) + " " + _("Folder Remaining") + " " + transcode_state
                if len(transcode_list) > 1:
                    text = str(len(transcode_list)) + " " + _("Folders Remaining") + " " + transcode_state

            x += ddt.text((x, y), text, bg, 311) + 8 * gui.scale

        elif pctl.broadcast_active:

            text = "Now Streaming:"
            rect = [x, y, 0, round(15 * gui.scale)]

            ddt.text((x, y), text, [95, 110, 230, 255], 311) # [70, 85, 230, 255]
            x += ddt.get_text_w(text, 11) + 6 * gui.scale

            text = pctl.master_library[pctl.broadcast_index].artist + " - " + pctl.master_library[
                pctl.broadcast_index].title
            trunc = window_size[0] - x - 150 * gui.scale
            text_w = ddt.text((x, y), text, colours.grey(130), 311, max_w=trunc)

            rect[2] = (x - rect[0]) + text_w
            if coll(rect) and inp.mouse_click:
                pctl.show_current(index=pctl.broadcast_index)

            x += text_w + 13 * gui.scale

            progress = int(pctl.broadcast_time / int(pctl.master_library[pctl.broadcast_index].length) * 100 * gui.scale)
            ddt.rect_a((x, y + 4), (progress, 9 * gui.scale), [65, 80, 220, 255])
            ddt.rect_s((x, y + 4, 100 * gui.scale, 9 * gui.scale), colours.grey(30), 1 * gui.scale)

            if inp.mouse_click and coll((x, y, 100 * gui.scale, 11)):
                newtime = ((mouse_position[0] - x) / (100 * gui.scale)) * pctl.master_library[pctl.broadcast_index].length
                pctl.broadcast_seek_position = newtime
                pctl.broadcastCommand = 'encseek'
                pctl.broadcastCommandReady = True


            x += 110 * gui.scale
            ddt.text((x, y), str(len(tauon.chunker.clients)), [70, 85, 230, 255], 11)

            self.drag_zone_start_x = x + 21 * gui.scale

            if inp.mouse_click and coll((x - 5, y - 5, 20, 24)):
                line = ""
                inp.mouse_click = False
                for ip, timestamp in tauon.chunker.clients.values():
                    print(ip)
                    line += ip + " "

                if len(tauon.chunker.clients) == 0:
                    show_message(_("There are currently no connected clients"))
                elif len(tauon.chunker.clients) == 1:
                    show_message(_("There is 1 inbound connection."), line, mode='info')
                else:
                    show_message(_("There are %d inbound connections.") % len(tauon.chunker.clients), line,
                                 mode='info')

        # if pctl.playing_state > 0 and not pctl.broadcast_active and gui.show_top_title:
        #     ddt.draw_text((window_size[0] - offset, y, 1), p_text, colours.side_bar_line1, 12)

        if colours.lm:
            colours.tb_line = colours.grey(200)
            ddt.rect((0, int(gui.panelY - 1 * gui.scale), window_size[0], int(1 * gui.scale)), colours.tb_line)

top_panel = TopPanel()


class BottomBarType1:
    def __init__(self):

        self.mode = 0

        self.seek_time = 0

        self.seek_down = False
        self.seek_hit = False
        self.volume_hit = False
        self.volume_bar_being_dragged = False
        self.control_line_bottom = 35 * gui.scale
        self.repeat_click_off = False
        self.random_click_off = False

        self.seek_bar_position = [300 * gui.scale, window_size[1] - gui.panelBY]
        self.seek_bar_size = [window_size[0] - (300 * gui.scale), 15 * gui.scale]
        self.volume_bar_size = [135 * gui.scale, 14 * gui.scale]
        self.volume_bar_position = [0, 45 * gui.scale]

        self.play_button = asset_loader('play.png', True)
        self.forward_button = asset_loader('ff.png', True)
        self.back_button = asset_loader('bb.png', True)

        self.buffer_shard = asset_loader("shard.png", True)

        self.scrob_stick = 0


    def update(self):

        if self.mode == 0:
            self.volume_bar_position[0] = window_size[0] - (210 * gui.scale)
            self.volume_bar_position[1] = window_size[1] - (27 * gui.scale)
            self.seek_bar_position[1] = window_size[1] - gui.panelBY

            seek_bar_x = 300 * gui.scale
            if window_size[0] < 600 * gui.scale:
                seek_bar_x = 250 * gui.scale

            self.seek_bar_size[0] = window_size[0] - seek_bar_x
            self.seek_bar_position[0] = seek_bar_x

            # if gui.bb_show_art:
            #     self.seek_bar_position[0] = 300 + gui.panelBY
            #     self.seek_bar_size[0] = window_size[0] - 300 - gui.panelBY

            # self.seek_bar_position[0] = 0
            # self.seek_bar_size[0] = window_size[0]

    def render(self):

        global volume_store
        global clicked
        global right_click

        ddt.rect_a((0, window_size[1] - gui.panelBY), (window_size[0], gui.panelBY), colours.bottom_panel_colour)

        ddt.rect_a(self.seek_bar_position, self.seek_bar_size, colours.seek_bar_background)

        right_offset = 0
        if gui.display_time_mode >= 2:
            right_offset = 22 * gui.scale

        if window_size[0] < 670 * gui.scale:
            right_offset -= 90 * gui.scale
        # Scrobble marker

        if prefs.scrobble_mark and (prefs.auto_lfm or lb.enable or prefs.maloja_enable) and not prefs.scrobble_hold and pctl.playing_length > 0 and 3 > pctl.playing_state > 0:
            if pctl.master_library[pctl.track_queue[pctl.queue_step]].length > 240 * 2:
                l_target = 240
            else:
                l_target = int(pctl.master_library[pctl.track_queue[pctl.queue_step]].length * 0.50)
            l_lead = l_target - pctl.a_time

            if l_lead > 0 and pctl.master_library[pctl.track_queue[pctl.queue_step]].length > 30:
                l_x = self.seek_bar_position[0] + int(math.ceil(
                    pctl.playing_time * self.seek_bar_size[0] / int(pctl.playing_length)))
                l_x += int(math.ceil(self.seek_bar_size[0] / int(pctl.playing_length) * l_lead))

                if abs(self.scrob_stick - l_x) < 2:
                    l_x = self.scrob_stick
                else:
                    self.scrob_stick = l_x
                ddt.rect((self.scrob_stick, self.seek_bar_position[1], 2 * gui.scale, self.seek_bar_size[1]),
                         [240, 10, 10, 80])


        # # MINI ALBUM ART
        # if gui.bb_show_art:
        #     rect = [self.seek_bar_position[0] - gui.panelBY, self.seek_bar_position[1], gui.panelBY, gui.panelBY]
        #     ddt.rect_r(rect, [255, 255, 255, 8], True)
        #     if 3 > pctl.playing_state > 0:
        #         album_art_gen.display(pctl.track_queue[pctl.queue_step], (rect[0], rect[1]), (rect[2], rect[3]))

            #ddt.rect_r(rect, [255, 255, 255, 20])

        # SEEK BAR------------------
        if pctl.playing_time < 1:
            self.seek_time = 0

        if inp.mouse_click and coll_point(mouse_position,
                                      self.seek_bar_position + [self.seek_bar_size[0]] + [self.seek_bar_size[1] + 2]):
            self.seek_down = True
            self.volume_hit = True
        if right_click and coll_point(mouse_position,
                                      self.seek_bar_position + [self.seek_bar_size[0]] + [self.seek_bar_size[1] + 2]):
            pctl.pause()
            if pctl.playing_state == 0:
                pctl.play()

        fields.add(self.seek_bar_position + self.seek_bar_size)
        if coll(self.seek_bar_position + self.seek_bar_size):

            if middle_click and pctl.playing_state > 0:
                gui.seek_cur_show = True


            clicked = True
            if mouse_wheel != 0:
                pctl.seek_time(pctl.playing_time + (mouse_wheel * 3))

        if gui.seek_cur_show:
            gui.update += 1

            # fields.add([mouse_position[0] - 1, mouse_position[1] - 1, 1, 1])
            # ddt.rect_r([mouse_position[0] - 1, mouse_position[1] - 1, 1, 1], [255,0,0,180], True)

            bargetX = mouse_position[0]
            if bargetX > self.seek_bar_position[0] + self.seek_bar_size[0]:
                bargetX = self.seek_bar_position[0] + self.seek_bar_size[0]
            if bargetX < self.seek_bar_position[0]:
                bargetX = self.seek_bar_position[0]
            bargetX -= self.seek_bar_position[0]
            seek = bargetX / self.seek_bar_size[0]
            gui.cur_time = get_display_time(pctl.playing_object().length * seek)

        if self.seek_down is True:
            if mouse_position[0] == 0:
                self.seek_down = False
                self.seek_hit = True


        if (mouse_up and coll(self.seek_bar_position + self.seek_bar_size) and coll_point(last_click_location,self.seek_bar_position + self.seek_bar_size)
            and coll_point(click_location,
                           self.seek_bar_position + self.seek_bar_size)) or mouse_up and self.volume_hit or self.seek_hit:

            self.volume_hit = False
            self.seek_down = False
            self.seek_hit = False

            bargetX = mouse_position[0]
            if bargetX > self.seek_bar_position[0] + self.seek_bar_size[0]:
                bargetX = self.seek_bar_position[0] + self.seek_bar_size[0]
            if bargetX < self.seek_bar_position[0]:
                bargetX = self.seek_bar_position[0]
            bargetX -= self.seek_bar_position[0]
            seek = bargetX / self.seek_bar_size[0]

            pctl.seek_decimal(seek)
            # print(seek)

            self.seek_time = pctl.playing_time

        if radiobox.load_connecting or gui.buffering:
            x = self.seek_bar_position[0] - round(26 - gui.scale)
            y = self.seek_bar_position[1]
            while x < self.seek_bar_position[0] + self.seek_bar_size[0]:

                offset = (math.floor(((core_timer.get() * 1) % 1) * 13) / 13) * self.buffer_shard.w
                gui.delay_frame(0.01)

                # colour = colours.seek_bar_fill
                h, l, s = rgb_to_hls(colours.seek_bar_background[0], colours.seek_bar_background[1], colours.seek_bar_background[2])
                l = min(1, l + 0.05)
                colour = hls_to_rgb(h, l, s)
                colour[3] = colours.seek_bar_background[3]

                self.buffer_shard.render(x + offset, y, colour)
                x += self.buffer_shard.w

            ddt.rect((self.seek_bar_position[0] - self.buffer_shard.w, y, self.buffer_shard.w, self.buffer_shard.h),
                     colours.bottom_panel_colour)

        if pctl.playing_length > 0:

            if pctl.download_time != 0:

                if pctl.download_time == -1:
                    pctl.download_time = pctl.playing_length

                colour = (255, 255, 255, 10)
                if gui.theme_name == "Lavender Light" or gui.theme_name == "Carbon":
                    colour = (255, 255, 255, 40)

                gui.seek_bar_rect = (self.seek_bar_position[0], self.seek_bar_position[1],
                                      int(pctl.download_time * self.seek_bar_size[0] / pctl.playing_length),
                           self.seek_bar_size[1])
                ddt.rect(gui.seek_bar_rect, colour)

            gui.seek_bar_rect = (self.seek_bar_position[0], self.seek_bar_position[1],
                                  int(self.seek_time * self.seek_bar_size[0] / pctl.playing_length),
                       self.seek_bar_size[1])
            ddt.rect(gui.seek_bar_rect, colours.seek_bar_fill)



        if gui.seek_cur_show:

            if coll([self.seek_bar_position[0] - 50, self.seek_bar_position[1] - 50, self.seek_bar_size[0] + 50, self.seek_bar_size[1] + 100]):
                if mouse_position[0] > self.seek_bar_position[0] - 1:
                    cur = [mouse_position[0] - 40, self.seek_bar_position[1] - 25, 42, 19]
                    ddt.rect(cur, colours.grey(15))
                    # ddt.rect_r(cur, colours.grey(80))
                    ddt.text((mouse_position[0] - 40 + 3, self.seek_bar_position[1] - 24), gui.cur_time, colours.grey(180), 213,
                             bg=colours.grey(15))

                    ddt.rect([mouse_position[0], self.seek_bar_position[1], 2, self.seek_bar_size[1]],
                             [100, 100, 20, 255])

            else:
                gui.seek_cur_show = False

        # Volume mouse wheel control -----------------------------------------
        if mouse_wheel != 0 and mouse_position[1] > self.seek_bar_position[1] + 4 and not coll_point(mouse_position,
                                                                                                     self.seek_bar_position + self.seek_bar_size):

            pctl.player_volume += mouse_wheel * prefs.volume_wheel_increment
            if pctl.player_volume < 1:
                pctl.player_volume = 0
            elif pctl.player_volume > 100:
                pctl.player_volume = 100

            pctl.player_volume = int(pctl.player_volume)
            pctl.set_volume()

        # Volume Bar 2 ------------------------------------------------
        if window_size[0] < 670 * gui.scale:
            x = window_size[0] - right_offset - 207 * gui.scale
            y = window_size[1] - round(14 * gui.scale)

            rect = (x - 8 * gui.scale, y - 17 * gui.scale, 55 * gui.scale, 23 * gui.scale)
            #ddt.rect(rect, [255,255,255,25])
            if coll(rect) and mouse_down:
                gui.update_on_drag = True

            h_rect = (x - 6 * gui.scale, y - 17 * gui.scale, 4 * gui.scale, 23 * gui.scale)
            if coll(h_rect) and mouse_down:
                pctl.player_volume = 0

            step = round(1 * gui.scale)
            min_h = round(4 * gui.scale)
            spacing = round(5 * gui.scale)

            if right_click and coll((h_rect[0], h_rect[1], h_rect[2] + 50 * gui.scale, h_rect[3])):
                if right_click:
                    if pctl.player_volume > 0:
                        volume_store = pctl.player_volume
                        pctl.player_volume = 0
                    else:
                        pctl.player_volume = volume_store

                    pctl.set_volume()

            for bar in range(8):

                h = min_h + bar * step
                rect = (x, y - h, 3 * gui.scale, h)
                h_rect = (x - 1 * gui.scale, y - 17 * gui.scale, 4 * gui.scale, 23 * gui.scale)

                if coll(h_rect):
                    if mouse_down or mouse_up:
                        gui.update_on_drag = True

                        if bar == 0:
                            pctl.player_volume = 5
                        if bar == 1:
                            pctl.player_volume = 10
                        if bar == 2:
                            pctl.player_volume = 20
                        if bar == 3:
                            pctl.player_volume = 30
                        if bar == 4:
                            pctl.player_volume = 45
                        if bar == 5:
                            pctl.player_volume = 55
                        if bar == 6:
                            pctl.player_volume = 70
                        if bar == 7:
                            pctl.player_volume = 100

                        pctl.set_volume()

                colour = colours.mode_button_off

                if bar == 0 and pctl.player_volume > 0:
                    colour = colours.mode_button_active
                elif bar == 1 and pctl.player_volume >= 10:
                    colour = colours.mode_button_active
                elif bar == 2 and pctl.player_volume >= 20:
                    colour = colours.mode_button_active
                elif bar == 3 and pctl.player_volume >= 30:
                    colour = colours.mode_button_active
                elif bar == 4 and pctl.player_volume >= 45:
                    colour = colours.mode_button_active
                elif bar == 5 and pctl.player_volume >= 55:
                    colour = colours.mode_button_active
                elif bar == 6 and pctl.player_volume >= 70:
                    colour = colours.mode_button_active
                elif bar == 7 and pctl.player_volume >= 95:
                    colour = colours.mode_button_active

                ddt.rect(rect, colour)
                x += spacing

        # Volume Bar --------------------------------------------------------
        else:
            if inp.mouse_click and coll((
                self.volume_bar_position[0] - right_offset, self.volume_bar_position[1], self.volume_bar_size[0],
                self.volume_bar_size[1] + 4)) or \
                            self.volume_bar_being_dragged is True:
                clicked = True

                if inp.mouse_click is True or self.volume_bar_being_dragged is True:
                    gui.update = 2

                    self.volume_bar_being_dragged = True
                    volgetX = mouse_position[0]
                    if volgetX > self.volume_bar_position[0] + self.volume_bar_size[0] - right_offset:
                        volgetX = self.volume_bar_position[0] + self.volume_bar_size[0] - right_offset
                    if volgetX < self.volume_bar_position[0] - right_offset:
                        volgetX = self.volume_bar_position[0] - right_offset
                    volgetX -= self.volume_bar_position[0] - right_offset
                    pctl.player_volume = volgetX / self.volume_bar_size[0] * 100

                    time.sleep(0.02)

                    if mouse_down is False:
                        self.volume_bar_being_dragged = False
                        pctl.player_volume = int(pctl.player_volume)
                        pctl.set_volume(True)

                if mouse_down:
                    pctl.player_volume = int(pctl.player_volume)
                    pctl.set_volume(False)


            if right_click and coll((
                        self.volume_bar_position[0] - 15 * gui.scale, self.volume_bar_position[1] - 10 * gui.scale, self.volume_bar_size[0] + 30 * gui.scale,
                        self.volume_bar_size[1] + 20 * gui.scale)):

                if pctl.player_volume > 0:
                    volume_store = pctl.player_volume
                    pctl.player_volume = 0
                else:
                    pctl.player_volume = volume_store

                pctl.set_volume()

            ddt.rect_a((self.volume_bar_position[0] - right_offset, self.volume_bar_position[1]), self.volume_bar_size,
                       colours.volume_bar_background)  # 22

            gui.volume_bar_rect = (self.volume_bar_position[0] - right_offset, self.volume_bar_position[1],
                      int(pctl.player_volume * self.volume_bar_size[0] / 100), self.volume_bar_size[1])

            ddt.rect(gui.volume_bar_rect, colours.volume_bar_fill)


            fields.add(self.volume_bar_position + self.volume_bar_size)
            if pctl.active_replaygain != 0 and (coll((
                        self.volume_bar_position[0], self.volume_bar_position[1], self.volume_bar_size[0],
                        self.volume_bar_size[1])) or self.volume_bar_being_dragged):


                if pctl.player_volume > 50:
                    ddt.text((self.volume_bar_position[0] - right_offset + 8 * gui.scale, self.volume_bar_position[1] - 1 * gui.scale), str(pctl.active_replaygain) + " dB", colours.volume_bar_background,
                             11, bg=colours.volume_bar_fill)
                else:
                    ddt.text((self.volume_bar_position[0] - right_offset + 85 * gui.scale, self.volume_bar_position[1] - 1 * gui.scale), str(pctl.active_replaygain) + " dB", colours.volume_bar_fill,
                             11, bg=colours.volume_bar_background)

        gui.show_bottom_title = gui.showed_title ^ True
        if not prefs.hide_bottom_title:
            gui.show_bottom_title = True

        if gui.show_bottom_title and pctl.playing_state > 0 and window_size[0] > 820 * gui.scale:

            line = pctl.title_text()

            x = self.seek_bar_position[0] + 1
            mx = window_size[0] - 710 * gui.scale
            # if gui.bb_show_art:
            #     x += 10 * gui.scale
            #     mx -= gui.panelBY - 10

            #line = trunc_line(line, 213, mx)
            ddt.text((x, self.seek_bar_position[1] + 24 * gui.scale), line, colours.bar_title_text,
                     fonts.panel_title, max_w=mx)

        if (inp.mouse_click or right_click) and coll((
                    self.seek_bar_position[0] - 10 * gui.scale, self.seek_bar_position[1] + 20 * gui.scale, window_size[0] - 710 * gui.scale, 30 * gui.scale)):
            # if pctl.playing_state == 3:
            #     copy_to_clipboard(pctl.tag_meta)
            #     show_message(_("Copied text to clipboard"))
            #     if input.mouse_click or right_click:
            #         input.mouse_click = False
            #         right_click = False
            # else:
            if inp.mouse_click and pctl.playing_state != 3:
                pctl.show_current()

            if pctl.playing_ready() and not fullscreen == 1:

                if right_click:
                    mode_menu.activate()

                if d_click_timer.get() < 0.3 and inp.mouse_click:
                    set_mini_mode()
                    gui.update += 1
                    return
                else:
                    d_click_timer.set()

        # TIME----------------------

        x = window_size[0] - 57 * gui.scale
        y = window_size[1] - 29 * gui.scale

        r_start = x - 10 * gui.scale
        if gui.display_time_mode in (2, 3):
            r_start -= 20 * gui.scale
        rect = (r_start, y - 3 * gui.scale, 80 * gui.scale, 27 * gui.scale)
        # ddt.rect_r(rect, [255, 0, 0, 40], True)
        if inp.mouse_click and coll(rect):
            gui.display_time_mode += 1
            if gui.display_time_mode > 3:
                gui.display_time_mode = 0

        if gui.display_time_mode == 0:
            text_time = get_display_time(pctl.playing_time)
            ddt.text((x + 1 * gui.scale, y), text_time, colours.time_playing,
                     fonts.bottom_panel_time)
        elif gui.display_time_mode == 1:
            if pctl.playing_state == 0:
                text_time = get_display_time(0)
            else:
                text_time = get_display_time(pctl.playing_length - pctl.playing_time)
            ddt.text((x + 1 * gui.scale, y), text_time, colours.time_playing,
                     fonts.bottom_panel_time)
            ddt.text((x - 5 * gui.scale, y), '-', colours.time_playing,
                     fonts.bottom_panel_time)
        elif gui.display_time_mode == 2:

            #colours.time_sub = alpha_blend([255, 255, 255, 80], colours.bottom_panel_colour)

            x -= 4
            text_time = get_display_time(pctl.playing_time)
            ddt.text((x - 25 * gui.scale, y), text_time, colours.time_playing,
                     fonts.bottom_panel_time)

            offset1 = 10 * gui.scale

            if system == "windows":
                offset1 += 2 * gui.scale

            offset2 = offset1 + 7 * gui.scale


            ddt.text((x + offset1, y), "/", colours.time_sub,
                     fonts.bottom_panel_time)
            text_time = get_display_time(pctl.playing_length)
            if pctl.playing_state == 0:
                text_time = get_display_time(0)
            elif pctl.playing_state == 3:
                text_time = "-- : --"
            ddt.text((x + offset2, y), text_time, colours.time_sub,
                     fonts.bottom_panel_time)

        elif gui.display_time_mode == 3:

            #colours.time_sub = alpha_blend([255, 255, 255, 80], colours.bottom_panel_colour)

            track = pctl.playing_object()
            if track and track.index != gui.dtm3_index:

                gui.dtm3_cum = 0
                gui.dtm3_total = 0
                run = True
                collected = []
                for item in default_playlist:
                    if pctl.master_library[item].parent_folder_path == track.parent_folder_path:
                        if item not in collected:
                            collected.append(item)
                            gui.dtm3_total += pctl.master_library[item].length
                            if item == track.index:
                                run = False
                            if run:
                                gui.dtm3_cum += pctl.master_library[item].length
                gui.dtm3_index = track.index

            x -= 4
            text_time = get_display_time(gui.dtm3_cum + pctl.playing_time)

            ddt.text((x - 25 * gui.scale, y), text_time, colours.time_playing,
                     fonts.bottom_panel_time)

            offset1 = 10 * gui.scale
            if system == "windows":
                offset1 += 2 * gui.scale
            offset2 = offset1 + 7 * gui.scale

            ddt.text((x + offset1, y), "/", colours.time_sub,
                     fonts.bottom_panel_time)
            text_time = get_display_time(gui.dtm3_total)
            if pctl.playing_state == 0:
                text_time = get_display_time(0)
            elif pctl.playing_state == 3:
                text_time = "-- : --"
            ddt.text((x + offset2, y), text_time, colours.time_sub,
                     fonts.bottom_panel_time)



        # BUTTONS
        # bottom buttons

        if gui.mode == 1:

            # PLAY---
            buttons_x_offset = 0
            compact = False
            if window_size[0] < 650 * gui.scale:
                compact = True

            play_colour = colours.media_buttons_off
            pause_colour = colours.media_buttons_off
            stop_colour = colours.media_buttons_off
            forward_colour = colours.media_buttons_off
            back_colour = colours.media_buttons_off

            if pctl.playing_state == 1:
                play_colour = colours.media_buttons_active

            if pctl.auto_stop:
                stop_colour = colours.media_buttons_active

            if pctl.playing_state == 2 or (spot_ctl.coasting and spot_ctl.paused):
                pause_colour = colours.media_buttons_active
                play_colour = colours.media_buttons_active
            elif pctl.playing_state == 3:
                play_colour = colours.media_buttons_active
                if tauon.stream_proxy.encode_running:
                    play_colour = [220, 50, 50, 255]

            if not compact or (compact and pctl.playing_state != 1):
                rect = (buttons_x_offset + (10 * gui.scale), window_size[1] - self.control_line_bottom - (13 * gui.scale), 50 * gui.scale , 40 * gui.scale)
                fields.add(rect)
                if coll(rect):
                    play_colour = colours.media_buttons_over
                    if inp.mouse_click:
                        if compact and pctl.playing_state == 1:
                            pctl.pause()
                        elif pctl.playing_state == 1 or spot_ctl.coasting:
                            pctl.show_current(highlight=True)
                        else:
                            pctl.play()
                        inp.mouse_click = False
                    tool_tip2.test(33 * gui.scale, y - 35 * gui.scale, _("Play, RC: Go to playing"))

                    if right_click:
                        pctl.show_current(highlight=True)

                self.play_button.render(29 * gui.scale, window_size[1] - self.control_line_bottom, play_colour)
                # ddt.rect_r(rect,[255,0,0,255], True)


            # PAUSE---
            if compact:
                buttons_x_offset = -46 * gui.scale

            x = (75 * gui.scale) + buttons_x_offset
            y = window_size[1] - self.control_line_bottom


            if not compact or (compact and pctl.playing_state == 1):


                rect = (x - 15 * gui.scale, y - 13 * gui.scale, 50 * gui.scale, 40 * gui.scale)
                fields.add(rect)
                if coll(rect) and not pctl.playing_state == 3:
                    pause_colour = colours.media_buttons_over
                    if inp.mouse_click:
                        pctl.pause()
                    if right_click:
                        pctl.show_current(highlight=True)
                    tool_tip2.test(x, y - 35 * gui.scale, _("Pause"))


                # ddt.rect_r(rect,[255,0,0,255], True)
                ddt.rect_a((x, y + 0), (4 * gui.scale, 13 * gui.scale), pause_colour)
                ddt.rect_a((x + 10 * gui.scale, y + 0), (4 * gui.scale, 13 * gui.scale), pause_colour)

            # STOP---
            x = 125 * gui.scale + buttons_x_offset
            rect = (x - 14 * gui.scale, y - 13 * gui.scale, 50 * gui.scale, 40 * gui.scale)
            fields.add(rect)
            if coll(rect):
                stop_colour = colours.media_buttons_over
                if inp.mouse_click:
                    pctl.stop()
                if right_click:
                    pctl.auto_stop ^= True
                tool_tip2.test(x, y - 35 * gui.scale, _("Stop, RC: Toggle auto-stop"))

            ddt.rect_a((x, y + 0), (13 * gui.scale, 13 * gui.scale), stop_colour)
            # ddt.rect_r(rect,[255,0,0,255], True)

            if compact:
                buttons_x_offset -= 5 * gui.scale

            # FORWARD---
            rect = (buttons_x_offset + 230 * gui.scale, window_size[1] - self.control_line_bottom - 10 * gui.scale, 50 * gui.scale, 35 * gui.scale)
            fields.add(rect)
            if coll(rect) and not pctl.playing_state == 3:
                forward_colour = colours.media_buttons_over
                if inp.mouse_click:
                    pctl.advance()
                    gui.tool_tip_lock_off_f = True
                if right_click:
                    #pctl.random_mode ^= True
                    toggle_random()
                    gui.tool_tip_lock_off_f = True
                    # if window_size[0] < 600 * gui.scale:
                    #. Shuffle set to on
                    gui.mode_toast_text = _("Shuffle On")
                    if not pctl.random_mode:
                        #. Shuffle set to off
                        gui.mode_toast_text = _("Shuffle Off")
                    toast_mode_timer.set()
                    gui.delay_frame(1)
                if middle_click:
                    pctl.advance(rr=True)
                    gui.tool_tip_lock_off_f = True
                #tool_tip.test(buttons_x_offset + 230 * gui.scale + 50 * gui.scale, window_size[1] - self.control_line_bottom - 20 * gui.scale, "Advance")
                # if not gui.tool_tip_lock_off_f:
                #     tool_tip2.test(x + 45 * gui.scale, y - 35 * gui.scale, _("Forward, RC: Toggle shuffle, MC: Radio random"))
            else:
                gui.tool_tip_lock_off_f = False

            self.forward_button.render(buttons_x_offset + 240 * gui.scale, 1 + window_size[1] - self.control_line_bottom, forward_colour)

            # ddt.rect_r(rect,[255,0,0,255], True)

            # BACK---
            rect = (buttons_x_offset + 170 * gui.scale, window_size[1] - self.control_line_bottom - 10 * gui.scale, 50 * gui.scale, 35 * gui.scale)
            fields.add(rect)
            if coll(rect) and not pctl.playing_state == 3:
                back_colour = colours.media_buttons_over
                if inp.mouse_click:
                    pctl.back()
                    gui.tool_tip_lock_off_b = True
                if right_click:
                    toggle_repeat()
                    gui.tool_tip_lock_off_b = True
                    #if window_size[0] < 600 * gui.scale:
                    #. Repeat set to on
                    gui.mode_toast_text = _("Repeat On")
                    if not pctl.repeat_mode:
                        #. Repeat set to off
                        gui.mode_toast_text = _("Repeat Off")
                    toast_mode_timer.set()
                    gui.delay_frame(1)
                if middle_click:
                    pctl.revert()
                    gui.tool_tip_lock_off_b = True
                if not gui.tool_tip_lock_off_b:
                    tool_tip2.test(x, y - 35 * gui.scale, _("Back, RC: Toggle repeat, MC: Revert"))
            else:
                gui.tool_tip_lock_off_b = False

            self.back_button.render(buttons_x_offset + 180 * gui.scale, 1 + window_size[1] - self.control_line_bottom, back_colour)
            # ddt.rect_r(rect,[255,0,0,255], True)


            # menu button

            x = window_size[0] - 252 * gui.scale - right_offset
            y = window_size[1] - round(26 * gui.scale)
            rpbc = colours.mode_button_off
            rect = (x - 9 * gui.scale, y - 5 * gui.scale, 40 * gui.scale, 25 * gui.scale)
            fields.add(rect)
            if coll(rect):
                if not extra_menu.active:
                    tool_tip.test(x, y - 28 * gui.scale, "Playback menu")
                rpbc = colours.mode_button_over
                if inp.mouse_click:
                    extra_menu.activate(position=(x - 115 * gui.scale, y - 6 * gui.scale))
                elif right_click:
                    mode_menu.activate(position=(x - 115 * gui.scale, y - 6 * gui.scale))
            if extra_menu.active:
                rpbc = colours.mode_button_active

            spacing = round(5 * gui.scale)
            ddt.rect_a((x, y), (24 * gui.scale, 2 * gui.scale), rpbc)
            y += spacing
            ddt.rect_a((x, y), (24 * gui.scale, 2 * gui.scale), rpbc)
            y += spacing
            ddt.rect_a((x, y), (24 * gui.scale, 2 * gui.scale), rpbc)

            if self.mode == 0 and window_size[0] > 530 * gui.scale:

                # shuffle button
                x = window_size[0] - 318 * gui.scale - right_offset
                y = window_size[1] - 27 * gui.scale

                rect = (x - 5 * gui.scale, y - 5 * gui.scale, 60 * gui.scale, 25 * gui.scale)
                fields.add(rect)

                rpbc = colours.mode_button_off
                if (inp.mouse_click or right_click) and coll(rect):

                    if inp.mouse_click:
                        #pctl.random_mode ^= True
                        toggle_random()
                        if pctl.random_mode is False:
                            self.random_click_off = True
                    else:
                        shuffle_menu.activate(position=(x + 30 * gui.scale, y - 7 * gui.scale))

                if pctl.random_mode:
                    rpbc = colours.mode_button_active
                    if coll(rect):
                        tool_tip.test(x, y - 28 * gui.scale, _("Shuffle"))
                elif coll(rect):
                    tool_tip.test(x, y - 28 * gui.scale, _("Shuffle"))
                    if self.random_click_off is True:
                        rpbc = colours.mode_button_off
                    elif pctl.random_mode is True:
                        rpbc = colours.mode_button_active
                    else:
                        rpbc = colours.mode_button_over
                else:
                    self.random_click_off = False

                # Keep hover highlight on if menu is open
                if shuffle_menu.active and not pctl.random_mode is True:
                    rpbc = colours.mode_button_over

                y += round(3 * gui.scale)
                ddt.rect_a((x, y), (25 * gui.scale, 3 * gui.scale), rpbc)

                if pctl.album_shuffle_mode:
                    ddt.rect_a((x + 25 * gui.scale, y), (23 * gui.scale, 3 * gui.scale), rpbc)

                y += round(5 * gui.scale)
                ddt.rect_a((x, y), (48 * gui.scale, 3 * gui.scale), rpbc)

                # REPEAT
                x = window_size[0] - round(380 * gui.scale) - right_offset
                y = window_size[1] - round(27 * gui.scale)

                rpbc = colours.mode_button_off

                rect = (x - 6 * gui.scale, y - 5 * gui.scale, 61 * gui.scale, 25 * gui.scale)
                fields.add(rect)
                if (inp.mouse_click or right_click) and coll(rect):

                    if inp.mouse_click:
                        toggle_repeat()
                        if pctl.repeat_mode is False:
                            self.repeat_click_off = True
                    else: # right click
                        repeat_menu.activate(position=(x + 30 * gui.scale, y - 7 * gui.scale))
                        # pctl.album_repeat_mode ^= True
                        # if not pctl.repeat_mode:
                        #     self.repeat_click_off = True

                if pctl.repeat_mode:
                    rpbc = colours.mode_button_active
                    if coll(rect):
                        if pctl.album_repeat_mode:
                            tool_tip.test(x, y - 28 * gui.scale, _("Repeat album"))
                        else:
                            tool_tip.test(x, y - 28 * gui.scale, _("Repeat track"))
                elif coll(rect):

                    # Tooltips. But don't show tooltips if menus open
                    if not repeat_menu.active and not shuffle_menu.active:
                        if pctl.album_repeat_mode:
                            tool_tip.test(x, y - 28 * gui.scale, _("Repeat album"))
                        else:
                            tool_tip.test(x, y - 28 * gui.scale, _("Repeat track"))

                    if self.repeat_click_off is True:
                        rpbc = colours.mode_button_off
                    elif pctl.repeat_mode is True:
                        rpbc = colours.mode_button_active
                    else:
                        rpbc = colours.mode_button_over
                else:
                    self.repeat_click_off = False

                # Keep hover highlight on if menu is open
                if repeat_menu.active and not pctl.repeat_mode is True:
                    rpbc = colours.mode_button_over

                rpbc = alpha_blend(rpbc, colours.bottom_panel_colour)  # bake in alpha in case of overlap

                y += round(3 * gui.scale)
                w = round(3 * gui.scale)
                y = round(y)
                x = round(x)

                ar = x + round(50 * gui.scale)
                h = round(5 * gui.scale)

                if pctl.album_repeat_mode:
                    ddt.rect_a((x + round(4 * gui.scale), y), (round(25 * gui.scale), w), rpbc)

                ddt.rect_a((ar - round(25 * gui.scale), y), (round(25 * gui.scale), w), rpbc)
                ddt.rect_a((ar - w, y), (w, h), rpbc)
                ddt.rect_a((ar - round(50 * gui.scale), y + h), (round(50 * gui.scale), w), rpbc)

                #ddt.rect_a((x + round(25 * gui.scale), y), (round(25 * gui.scale), w), rpbc, True)
                #ddt.rect_a((x + round(4 * gui.scale), y + round(5 * gui.scale)), (math.floor(46 * gui.scale), w), rpbc, True)
                #ddt.rect_a((x + 50 * gui.scale - w, y), (w, 8 * gui.scale), rpbc, True)
                #ddt.rect_a((x + round(50 * gui.scale) - w, y + w), (w, round(4 * gui.scale)), rpbc, True)


bottom_bar1 = BottomBarType1()


class BottomBarType_ao1:
    def __init__(self):

        self.mode = 0

        self.seek_time = 0

        self.seek_down = False
        self.seek_hit = False
        self.volume_hit = False
        self.volume_bar_being_dragged = False
        self.control_line_bottom = 35 * gui.scale
        self.repeat_click_off = False
        self.random_click_off = False

        self.seek_bar_position = [300 * gui.scale, window_size[1] - gui.panelBY]
        self.seek_bar_size = [window_size[0] - (300 * gui.scale), 15 * gui.scale]
        self.volume_bar_size = [135 * gui.scale, 14 * gui.scale]
        self.volume_bar_position = [0, 45 * gui.scale]

        self.play_button = asset_loader('play.png', True)
        self.forward_button = asset_loader('ff.png', True)
        self.back_button = asset_loader('bb.png', True)

        self.scrob_stick = 0


    def update(self):

        if self.mode == 0:
            self.volume_bar_position[0] = window_size[0] - (210 * gui.scale)
            self.volume_bar_position[1] = window_size[1] - (27 * gui.scale)
            self.seek_bar_position[1] = window_size[1] - gui.panelBY

            seek_bar_x = 300 * gui.scale
            if window_size[0] < 600 * gui.scale:
                seek_bar_x = 250 * gui.scale

            self.seek_bar_size[0] = window_size[0] - seek_bar_x
            self.seek_bar_position[0] = seek_bar_x

            # if gui.bb_show_art:
            #     self.seek_bar_position[0] = 300 + gui.panelBY
            #     self.seek_bar_size[0] = window_size[0] - 300 - gui.panelBY

            # self.seek_bar_position[0] = 0
            # self.seek_bar_size[0] = window_size[0]

    def render(self):

        global volume_store
        global clicked
        global right_click

        ddt.rect_a((0, window_size[1] - gui.panelBY), (window_size[0], gui.panelBY), colours.bottom_panel_colour)

        right_offset = 0
        if gui.display_time_mode >= 2:
            right_offset = 22 * gui.scale

        if window_size[0] < 670 * gui.scale:
            right_offset -= 90 * gui.scale


        # # MINI ALBUM ART
        # if gui.bb_show_art:
        #     rect = [self.seek_bar_position[0] - gui.panelBY, self.seek_bar_position[1], gui.panelBY, gui.panelBY]
        #     ddt.rect_r(rect, [255, 255, 255, 8], True)
        #     if 3 > pctl.playing_state > 0:
        #         album_art_gen.display(pctl.track_queue[pctl.queue_step], (rect[0], rect[1]), (rect[2], rect[3]))

            #ddt.rect_r(rect, [255, 255, 255, 20])

        # Volume mouse wheel control -----------------------------------------
        if mouse_wheel != 0 and mouse_position[1] > self.seek_bar_position[1] + 4 and not coll_point(mouse_position,
                                                                                                     self.seek_bar_position + self.seek_bar_size):

            pctl.player_volume += mouse_wheel * prefs.volume_wheel_increment
            if pctl.player_volume < 1:
                pctl.player_volume = 0
            elif pctl.player_volume > 100:
                pctl.player_volume = 100

            pctl.player_volume = int(pctl.player_volume)
            pctl.set_volume()

        # Volume Bar 2 ------------------------------------------------
        if True:
            x = window_size[0] - right_offset - 120 * gui.scale
            y = window_size[1] - round(21 * gui.scale)

            if gui.compact_bar:
                x -= 90 * gui.scale

            rect = (x - 8 * gui.scale, y - 17 * gui.scale, 55 * gui.scale, 23 * gui.scale)
            #ddt.rect(rect, [255,255,255,25])
            if coll(rect) and mouse_down:
                gui.update_on_drag = True

            h_rect = (x - 6 * gui.scale, y - 17 * gui.scale, 4 * gui.scale, 23 * gui.scale)
            if coll(h_rect) and mouse_down:
                pctl.player_volume = 0

            step = round(1 * gui.scale)
            min_h = round(4 * gui.scale)
            spacing = round(5 * gui.scale)

            if right_click and coll((h_rect[0], h_rect[1], h_rect[2] + 50 * gui.scale, h_rect[3])):
                if right_click:
                    if pctl.player_volume > 0:
                        volume_store = pctl.player_volume
                        pctl.player_volume = 0
                    else:
                        pctl.player_volume = volume_store

                    pctl.set_volume()

            for bar in range(8):

                h = min_h + bar * step
                rect = (x, y - h, 3 * gui.scale, h)
                h_rect = (x - 1 * gui.scale, y - 17 * gui.scale, 4 * gui.scale, 23 * gui.scale)

                if coll(h_rect):
                    if mouse_down:
                        gui.update_on_drag = True

                        if bar == 0:
                            pctl.player_volume = 5
                        if bar == 1:
                            pctl.player_volume = 10
                        if bar == 2:
                            pctl.player_volume = 20
                        if bar == 3:
                            pctl.player_volume = 30
                        if bar == 4:
                            pctl.player_volume = 45
                        if bar == 5:
                            pctl.player_volume = 55
                        if bar == 6:
                            pctl.player_volume = 70
                        if bar == 7:
                            pctl.player_volume = 100

                        pctl.set_volume()

                colour = colours.mode_button_off

                if bar == 0 and pctl.player_volume > 0:
                    colour = colours.mode_button_active
                elif bar == 1 and pctl.player_volume >= 10:
                    colour = colours.mode_button_active
                elif bar == 2 and pctl.player_volume >= 20:
                    colour = colours.mode_button_active
                elif bar == 3 and pctl.player_volume >= 30:
                    colour = colours.mode_button_active
                elif bar == 4 and pctl.player_volume >= 45:
                    colour = colours.mode_button_active
                elif bar == 5 and pctl.player_volume >= 55:
                    colour = colours.mode_button_active
                elif bar == 6 and pctl.player_volume >= 70:
                    colour = colours.mode_button_active
                elif bar == 7 and pctl.player_volume >= 95:
                    colour = colours.mode_button_active

                ddt.rect(rect, colour)
                x += spacing


        # TIME----------------------

        x = window_size[0] - 57 * gui.scale
        y = window_size[1] - 35 * gui.scale

        r_start = x - 10 * gui.scale
        if gui.display_time_mode in (2, 3):
            r_start -= 20 * gui.scale
        rect = (r_start, y - 3 * gui.scale, 80 * gui.scale, 27 * gui.scale)
        # ddt.rect_r(rect, [255, 0, 0, 40], True)
        if inp.mouse_click and coll(rect):
            gui.display_time_mode += 1
            if gui.display_time_mode > 3:
                gui.display_time_mode = 0

        if gui.display_time_mode == 0:
            text_time = get_display_time(pctl.playing_time)
            ddt.text((x + 1 * gui.scale, y), text_time, colours.time_playing,
                     fonts.bottom_panel_time)
        elif gui.display_time_mode == 1:
            if pctl.playing_state == 0:
                text_time = get_display_time(0)
            else:
                text_time = get_display_time(pctl.playing_length - pctl.playing_time)
            ddt.text((x + 1 * gui.scale, y), text_time, colours.time_playing,
                     fonts.bottom_panel_time)
            ddt.text((x - 5 * gui.scale, y), '-', colours.time_playing,
                     fonts.bottom_panel_time)
        elif gui.display_time_mode == 2:

            colours.time_sub = alpha_blend([255, 255, 255, 80], colours.bottom_panel_colour)

            x -= 4
            text_time = get_display_time(pctl.playing_time)
            ddt.text((x - 25 * gui.scale, y), text_time, colours.time_playing,
                     fonts.bottom_panel_time)

            offset1 = 10 * gui.scale

            if system == "windows":
                offset1 += 2 * gui.scale

            offset2 = offset1 + 7 * gui.scale


            ddt.text((x + offset1, y), "/", colours.time_sub,
                     fonts.bottom_panel_time)
            text_time = get_display_time(pctl.playing_length)
            if pctl.playing_state == 0:
                text_time = get_display_time(0)
            elif pctl.playing_state == 3:
                text_time = "-- : --"
            ddt.text((x + offset2, y), text_time, colours.time_sub,
                     fonts.bottom_panel_time)

        elif gui.display_time_mode == 3:

            colours.time_sub = alpha_blend([255, 255, 255, 80], colours.bottom_panel_colour)

            track = pctl.playing_object()
            if track and track.index != gui.dtm3_index:

                gui.dtm3_cum = 0
                gui.dtm3_total = 0
                run = True
                collected = []
                for item in default_playlist:
                    if pctl.master_library[item].parent_folder_path == track.parent_folder_path:
                        if item not in collected:
                            collected.append(item)
                            gui.dtm3_total += pctl.master_library[item].length
                            if item == track.index:
                                run = False
                            if run:
                                gui.dtm3_cum += pctl.master_library[item].length
                gui.dtm3_index = track.index

            x -= 4
            text_time = get_display_time(gui.dtm3_cum + pctl.playing_time)

            ddt.text((x - 25 * gui.scale, y), text_time, colours.time_playing,
                     fonts.bottom_panel_time)

            offset1 = 10 * gui.scale
            if system == "windows":
                offset1 += 2 * gui.scale
            offset2 = offset1 + 7 * gui.scale

            ddt.text((x + offset1, y), "/", colours.time_sub,
                     fonts.bottom_panel_time)
            text_time = get_display_time(gui.dtm3_total)
            if pctl.playing_state == 0:
                text_time = get_display_time(0)
            elif pctl.playing_state == 3:
                text_time = "-- : --"
            ddt.text((x + offset2, y), text_time, colours.time_sub,
                     fonts.bottom_panel_time)



        # BUTTONS
        # bottom buttons

        if gui.mode == 1:

            # PLAY---
            buttons_x_offset = 0
            compact = False
            if window_size[0] < 650 * gui.scale:
                compact = True

            play_colour = colours.media_buttons_off
            pause_colour = colours.media_buttons_off
            stop_colour = colours.media_buttons_off
            forward_colour = colours.media_buttons_off
            back_colour = colours.media_buttons_off

            if pctl.playing_state == 1:
                play_colour = colours.media_buttons_active

            if pctl.auto_stop:
                stop_colour = colours.media_buttons_active

            if pctl.playing_state == 2:
                pause_colour = colours.media_buttons_active
                play_colour = colours.media_buttons_active
            elif pctl.playing_state == 3:
                play_colour = colours.media_buttons_active
                if pctl.record_stream:
                    play_colour = [220, 50 ,50 , 255]

            if not compact or (compact and pctl.playing_state != 2):
                rect = (buttons_x_offset + (10 * gui.scale), window_size[1] - self.control_line_bottom - (13 * gui.scale), 50 * gui.scale , 40 * gui.scale)
                fields.add(rect)
                if coll(rect):
                    play_colour = colours.media_buttons_over
                    if inp.mouse_click:
                        if compact and pctl.playing_state == 1:
                            pctl.pause()
                        elif pctl.playing_state == 1:
                            pctl.show_current(highlight=True)
                        else:
                            pctl.play()
                        inp.mouse_click = False
                    tool_tip2.test(33 * gui.scale, y - 35 * gui.scale, _("Play, RC: Go to playing"))

                    if right_click:
                        pctl.show_current(highlight=True)

                self.play_button.render(29 * gui.scale, window_size[1] - self.control_line_bottom, play_colour)
                # ddt.rect_r(rect,[255,0,0,255], True)


            # PAUSE---
            if compact:
                buttons_x_offset = -46 * gui.scale

            x = (75 * gui.scale) + buttons_x_offset
            y = window_size[1] - self.control_line_bottom


            if not compact or (compact and pctl.playing_state == 2):


                rect = (x - 15 * gui.scale, y - 13 * gui.scale, 50 * gui.scale, 40 * gui.scale)
                fields.add(rect)
                if coll(rect) and not pctl.playing_state == 3:
                    pause_colour = colours.media_buttons_over
                    if inp.mouse_click:
                        pctl.pause()
                    if right_click:
                        pctl.show_current(highlight=True)
                    tool_tip2.test(x, y - 35 * gui.scale, _("Pause"))


                # ddt.rect_r(rect,[255,0,0,255], True)
                ddt.rect_a((x, y + 0), (4 * gui.scale, 13 * gui.scale), pause_colour)
                ddt.rect_a((x + 10 * gui.scale, y + 0), (4 * gui.scale, 13 * gui.scale), pause_colour)

            # FORWARD---
            rect = (buttons_x_offset + 125 * gui.scale, window_size[1] - self.control_line_bottom - 10 * gui.scale, 50 * gui.scale, 35 * gui.scale)
            fields.add(rect)
            if coll(rect) and not pctl.playing_state == 3:
                forward_colour = colours.media_buttons_over
                if inp.mouse_click:
                    pctl.advance()
                    gui.tool_tip_lock_off_f = True
                if right_click:
                    #pctl.random_mode ^= True
                    toggle_random()
                    gui.tool_tip_lock_off_f = True
                    # if window_size[0] < 600 * gui.scale:
                    #. Shuffle set to on
                    gui.mode_toast_text = _("Shuffle On")
                    if not pctl.random_mode:
                        #. Shuffle set to off
                        gui.mode_toast_text = _("Shuffle Off")
                    toast_mode_timer.set()
                    gui.delay_frame(1)
                if middle_click:
                    pctl.advance(rr=True)
                    gui.tool_tip_lock_off_f = True
                #tool_tip.test(buttons_x_offset + 230 * gui.scale + 50 * gui.scale, window_size[1] - self.control_line_bottom - 20 * gui.scale, "Advance")
                # if not gui.tool_tip_lock_off_f:
                #     tool_tip2.test(x + 45 * gui.scale, y - 35 * gui.scale, _("Forward, RC: Toggle shuffle, MC: Radio random"))
            else:
                gui.tool_tip_lock_off_f = False

            self.forward_button.render(buttons_x_offset + 125 * gui.scale, 1 + window_size[1] - self.control_line_bottom, forward_colour)



bottom_bar_ao1 = BottomBarType_ao1()


class MiniMode:

    def __init__(self):

        self.save_position = None
        self.was_borderless = True
        self.volume_timer = Timer()
        self.volume_timer.force_set(100)

        self.left_slide = asset_loader("left-slide.png", True)
        self.right_slide = asset_loader("right-slide.png", True)

        self.shuffle_fade_timer = Timer(100)
        self.repeat_fade_timer = Timer(100)

    def render(self):

        w = window_size[0]
        h = window_size[1]

        y1 = w
        if w == h:
            y1 -= 79 * gui.scale

        h1 = h - y1

        # Draw background
        bg = colours.mini_mode_background
        #bg = [250, 250, 250, 255]

        ddt.rect((0, 0, w, h), bg)
        ddt.text_background_colour = bg

        detect_mouse_rect = (3, 3, w - 6, h - 6)
        fields.add(detect_mouse_rect)
        mouse_in = coll(detect_mouse_rect)

        # Play / Pause when right clicking below art
        if right_click: # and mouse_position[1] > y1:
            pctl.play_pause()

        # Volume change on scroll
        if mouse_wheel != 0:
            self.volume_timer.set()

            pctl.player_volume += mouse_wheel * prefs.volume_wheel_increment * 3
            if pctl.player_volume < 1:
                pctl.player_volume = 0
            elif pctl.player_volume > 100:
                pctl.player_volume = 100

            pctl.player_volume = int(pctl.player_volume)
            pctl.set_volume()

        track = pctl.playing_object()

        control_hit_area = (3, y1 - 15 * gui.scale, w - 6, h1 - 3 + 15 * gui.scale)
        mouse_in_area = coll(control_hit_area)
        fields.add(control_hit_area)

        ddt.rect((0, 0, w, w), (0, 0, 0, 45))
        if track is not None:

            # Render album art
            album_art_gen.display(track, (0, 0), (w, w))

            line1c = colours.mini_mode_text_1
            line2c = colours.mini_mode_text_2

            if h == w and mouse_in_area:
                #ddt.pretty_rect = (0, 260 * gui.scale, w, 100 * gui.scale)
                ddt.rect((0, y1, w, h1), [0, 0, 0, 220])
                line1c = [255, 255, 255, 240]
                line2c = [255, 255, 255, 77]

            # Double click bottom text to return to full window
            text_hit_area = (60 * gui.scale, y1 + 4, 230 * gui.scale, 50 * gui.scale)



            if coll(text_hit_area):
                if inp.mouse_click:
                    if d_click_timer.get() < 0.3:
                        restore_full_mode()
                        gui.update += 1
                        return
                    else:
                        d_click_timer.set()


            # Draw title texts
            line1 = track.artist
            line2 = track.title

            # Calculate seek bar position
            seek_w = int(w * 0.70)

            seek_r = [(w - seek_w) // 2, y1 + 58 * gui.scale, seek_w, 6 * gui.scale]
            seek_r_hit = [seek_r[0], seek_r[1] - 4 * gui.scale, seek_r[2], seek_r[3] + 8 * gui.scale]


            if w != h or mouse_in_area:

                if not line1 and not line2:
                    ddt.text((w // 2, y1 + 18 * gui.scale, 2), track.filename, line1c, 214,
                             window_size[0] - 30 * gui.scale)
                else:

                    ddt.text((w // 2, y1 + 10 * gui.scale, 2), line1, line2c, 514,
                             window_size[0] - 30 * gui.scale)

                    ddt.text((w // 2, y1 + 31 * gui.scale, 2), line2, line1c, 414,
                             window_size[0] - 30 * gui.scale)


                # Test click to seek
                if mouse_up and coll(seek_r_hit):

                    click_x = mouse_position[0]
                    if click_x > seek_r[0] + seek_r[2]:
                        click_x = seek_r[0] + seek_r[2]
                    if click_x < seek_r[0]:
                        click_x = seek_r[0]
                    click_x -= seek_r[0]

                    if click_x < 6 * gui.scale:
                        click_x = 0
                    seek = click_x / seek_r[2]

                    pctl.seek_decimal(seek)

                # Draw progress bar background
                ddt.rect(seek_r, [255, 255, 255, 32])

                # Calculate and draw bar foreground
                progress_w = 0
                if pctl.playing_length > 1:
                    progress_w = pctl.playing_time * seek_w / pctl.playing_length
                seek_colour = [210, 210, 210, 255]
                if gui.theme_name == "Carbon":
                    seek_colour = colours.bottom_panel_colour

                if pctl.playing_state != 1:
                    seek_colour = [210, 40, 100, 255]

                seek_r[2] = progress_w


                if self.volume_timer.get() < 0.9:
                    progress_w = pctl.player_volume * (seek_w - (4 * gui.scale)) / 100
                    gui.update += 1
                    seek_colour = [210, 210, 210, 255]
                    seek_r[2] = progress_w
                    seek_r[0] += 2 * gui.scale
                    seek_r[1] += 2 * gui.scale
                    seek_r[3] -= 4 * gui.scale

                ddt.rect(seek_r, seek_colour)


        left_area = (1, y1, seek_r[0] - 1, 45 * gui.scale)
        right_area = (seek_r[0] + seek_w, y1, seek_r[0] - 2, 45 * gui.scale)

        fields.add(left_area)
        fields.add(right_area)

        hint = 0
        if coll(control_hit_area):
            hint = 30
        if coll(left_area):
            hint = 240
        if hint:
            self.left_slide.render(16 * gui.scale, y1 + 17 * gui.scale, [255, 255, 255, hint])

        hint = 0
        if coll(control_hit_area):
            hint = 30
        if coll(right_area):
            hint = 240
        if hint:
            self.right_slide.render(window_size[0] - self.right_slide.w - 16 * gui.scale, y1 + 17 * gui.scale, [255, 255, 255, hint])


        # Shuffle

        shuffle_area = (seek_r[0] + seek_w, seek_r[1] - 10 * gui.scale, 50 * gui.scale, 30 * gui.scale)
        #fields.add(shuffle_area)
        # ddt.rect_r(shuffle_area, [255, 0, 0, 100], True)

        if coll(control_hit_area):
            colour = [255, 255, 255, 20]
            if inp.mouse_click and coll(shuffle_area):
                #pctl.random_mode ^= True
                toggle_random()
            if pctl.random_mode:
                colour = [255, 255, 255, 190]

            sx = seek_r[0] + seek_w + 8 * gui.scale
            sy = seek_r[1] - 1 * gui.scale
            ddt.rect_a((sx, sy), (14 * gui.scale, 2 * gui.scale), colour)
            sy += 4 * gui.scale
            ddt.rect_a((sx, sy), (28 * gui.scale, 2 * gui.scale), colour)


        shuffle_area = (seek_r[0] - 41 * gui.scale, seek_r[1] - 10 * gui.scale, 40 * gui.scale, 30 * gui.scale)
        if coll(control_hit_area):
            colour = [255, 255, 255, 20]
            if inp.mouse_click and coll(shuffle_area):
                toggle_repeat()
            if pctl.repeat_mode:
                colour = [255, 255, 255, 190]

            sx = seek_r[0] - 39 * gui.scale
            sy = seek_r[1] - 1 * gui.scale

            tw = 2 * gui.scale
            ddt.rect_a((sx + 15 * gui.scale, sy), (13 * gui.scale, tw), colour)
            ddt.rect_a((sx + 4 * gui.scale, sy + 4 * gui.scale), (25 * gui.scale, tw), colour)
            ddt.rect_a((sx + 30 * gui.scale - tw, sy), (tw, 6 * gui.scale), colour)


        # Forward and back clicking
        if inp.mouse_click:
            if coll(left_area):
                pctl.back()
            if coll(right_area):
                pctl.advance()


        # Show exit/min buttons when mosue over
        tool_rect = [window_size[0] - 110 * gui.scale, 2, 108 * gui.scale, 45 * gui.scale]
        if prefs.left_window_control:
            tool_rect[0] = 0
        fields.add(tool_rect)
        if coll(tool_rect):
            draw_window_tools()

        if w != h:
            ddt.rect_s((1, 1, w - 2, h - 2), colours.mini_mode_border, 1 * gui.scale)
            if gui.scale == 2:
                ddt.rect_s((2, 2, w - 4, h - 4), colours.mini_mode_border, 1 * gui.scale)

mini_mode = MiniMode()


class MiniMode2:

    def __init__(self):

        self.save_position = None
        self.was_borderless = True
        self.volume_timer = Timer()
        self.volume_timer.force_set(100)

        self.left_slide = asset_loader("left-slide.png", True)
        self.right_slide = asset_loader("right-slide.png", True)

    def render(self):

        w = window_size[0]
        h = window_size[1]

        x1 = h

        # Draw background
        ddt.rect((0, 0, w, h), colours.mini_mode_background)
        ddt.text_background_colour = colours.mini_mode_background

        detect_mouse_rect = (2, 2, w - 4, h - 4)
        fields.add(detect_mouse_rect)
        mouse_in = coll(detect_mouse_rect)

        # Play / Pause when right clicking below art
        if right_click: # and mouse_position[1] > y1:
            pctl.play_pause()

        # Volume change on scroll
        if mouse_wheel != 0:
            self.volume_timer.set()

            pctl.player_volume += mouse_wheel * prefs.volume_wheel_increment * 3
            if pctl.player_volume < 1:
                pctl.player_volume = 0
            elif pctl.player_volume > 100:
                pctl.player_volume = 100

            pctl.player_volume = int(pctl.player_volume)
            pctl.set_volume()

        track = pctl.playing_object()

        if track is not None:

            # Render album art
            album_art_gen.display(track, (0, 0), (h, h))

            text_hit_area = (x1, 0, w, h)

            if coll(text_hit_area):
                if inp.mouse_click:
                    if d_click_timer.get() < 0.3:
                        restore_full_mode()
                        gui.update += 1
                        return
                    else:
                        d_click_timer.set()

            # Draw title texts
            line1 = track.artist
            line2 = track.title

            if not line1 and not line2:

                ddt.text((x1 + 15 * gui.scale, 44 * gui.scale), track.filename, colours.grey(150), 315,
                         window_size[0] - x1 - 30 * gui.scale)
            else:

                # if ddt.get_text_w(line2, 215) > window_size[0] - x1 - 30 * gui.scale:
                #     ddt.text((x1 + 15 * gui.scale, 19 * gui.scale), line2, colours.grey(249), 413,
                #              window_size[0] - x1 - 35 * gui.scale)
                #
                #     ddt.text((x1 + 15 * gui.scale, 43 * gui.scale), line1, colours.grey(110), 513,
                #              window_size[0] - x1 - 35 * gui.scale)
                # else:

                ddt.text((x1 + 15 * gui.scale, 18 * gui.scale), line2, colours.grey(249), 514,
                         window_size[0] - x1 - 30 * gui.scale)

                ddt.text((x1 + 15 * gui.scale, 43 * gui.scale), line1, colours.grey(110), 514,
                         window_size[0] - x1 - 30 * gui.scale)


        # Show exit/min buttons when mosue over
        tool_rect = [window_size[0] - 110 * gui.scale, 2, 108 * gui.scale, 45 * gui.scale]
        if prefs.left_window_control:
            tool_rect[0] = 0
        fields.add(tool_rect)
        if coll(tool_rect):
            draw_window_tools()

        # Seek bar
        bg_rect = (h, h - round(5 * gui.scale), w - h, round(5 * gui.scale))
        ddt.rect(bg_rect, [255, 255, 255, 18])

        if 0 < pctl.playing_state:

            hit_rect = h - 5 * gui.scale, h - 12 * gui.scale, w - h + 5 * gui.scale, 13 * gui.scale

            if coll(hit_rect) and mouse_up:
                p = (mouse_position[0] - h) / (w - h)

                if p < 0 or mouse_position[0] - h < 6 * gui.scale:
                    pctl.seek_time(0)
                elif p > .96:
                    pctl.advance()
                else:
                    pctl.seek_decimal(p)



            if pctl.playing_length:
                seek_rect = (h, h - round(5 * gui.scale), round((w - h) * (pctl.playing_time / pctl.playing_length)), round(5 * gui.scale))
                colour = colours.artist_text
                if gui.theme_name == "Carbon":
                    colour = colours.bottom_panel_colour
                if pctl.playing_state != 1:
                    colour = [210, 40, 100, 255]
                ddt.rect(seek_rect, colour)


mini_mode2 = MiniMode2()

def set_mini_mode():

    if fullscreen == 1:
        return

    global mouse_down
    global mouse_up
    global old_window_position
    mouse_down = False
    mouse_up = False
    inp.mouse_click = False

    if gui.maximized:
        SDL_RestoreWindow(t_window)
        update_layout_do()

    if gui.mode < 3:
        old_window_position = get_window_position()

    gui.mode = 3
    gui.vis = 0
    gui.turbo = False
    gui.draw_vis4_top = False
    gui.level_update = False

    i_y = pointer(c_int(0))
    i_x = pointer(c_int(0))
    SDL_GetWindowPosition(t_window, i_x, i_y)
    gui.save_position = (i_x.contents.value, i_y.contents.value)

    mini_mode.was_borderless = draw_border
    SDL_SetWindowBordered(t_window, False)

    size = (350, 429)
    if prefs.mini_mode_mode == 1:
        size = (330, 330)
    if prefs.mini_mode_mode == 2:
        size = (420, 499)
    if prefs.mini_mode_mode == 3:
        size = (430, 430)
    if prefs.mini_mode_mode == 4:
        size = (330, 80)
    if prefs.mini_mode_mode == 5:
        size = (400, 600)

    if logical_size == window_size:
        size = (int(size[0] * gui.scale), int(size[1] * gui.scale))

    logical_size[0] = size[0]
    logical_size[1] = size[1]

    SDL_SetWindowMinimumSize(t_window, logical_size[0], logical_size[1])
    SDL_SetWindowResizable(t_window, False)
    SDL_SetWindowSize(t_window, logical_size[0], logical_size[1])


    if mini_mode.save_position:
        SDL_SetWindowPosition(t_window, mini_mode.save_position[0], mini_mode.save_position[1])

    i_x = pointer(c_int(0))
    i_y = pointer(c_int(0))
    SDL_GL_GetDrawableSize(t_window, i_x, i_y)
    window_size[0] = i_x.contents.value
    window_size[1] = i_y.contents.value

    gui.update += 3

restore_ignore_timer = Timer()
restore_ignore_timer.force_set(100)

def restore_full_mode():

    i_y = pointer(c_int(0))
    i_x = pointer(c_int(0))
    SDL_GetWindowPosition(t_window, i_x, i_y)
    mini_mode.save_position = [i_x.contents.value, i_y.contents.value]

    if not mini_mode.was_borderless:
        SDL_SetWindowBordered(t_window, True)

    logical_size[0] = gui.save_size[0]
    logical_size[1] = gui.save_size[1]

    SDL_SetWindowPosition(t_window, gui.save_position[0], gui.save_position[1])
    if macos:
        SDL_SetWindowMinimumSize(t_window, 560, 330)
    else:
        SDL_SetWindowMinimumSize(t_window, round(560 * scale), round(330 * scale))

    SDL_SetWindowResizable(t_window, True)
    SDL_SetWindowSize(t_window, logical_size[0], logical_size[1])

    restore_ignore_timer.set()  # Hacky

    gui.mode = 1

    global mouse_down
    global mouse_up
    mouse_down = False
    mouse_up = False
    inp.mouse_click = False

    if gui.maximized:
        SDL_MaximizeWindow(t_window)
        time.sleep(0.05)
        SDL_PumpEvents()
        SDL_GetWindowSize(t_window, i_x, i_y)
        logical_size[0] = i_x.contents.value
        logical_size[1] = i_y.contents.value

        # print(window_size)

    SDL_GL_GetDrawableSize(t_window, i_x, i_y)
    window_size[0] = i_x.contents.value
    window_size[1] = i_y.contents.value

    gui.update_layout()
    if prefs.art_bg:
        tm.ready('style')


def line_render(n_track, p_track, y, this_line_playing, album_fade, start_x, width, style=1, ry=None ):
    timec = colours.bar_time
    titlec = colours.title_text
    indexc = colours.index_text
    artistc = colours.artist_text
    albumc = colours.album_text

    if this_line_playing is True:
        timec = colours.time_text
        titlec = colours.title_playing
        indexc = colours.index_playing
        artistc = colours.artist_playing
        albumc = colours.album_playing

    if n_track.found is False:
        timec = colours.playlist_text_missing
        titlec = colours.playlist_text_missing
        indexc = colours.playlist_text_missing
        artistc = colours.playlist_text_missing
        albumc = colours.playlist_text_missing

    artistoffset = 0
    indexLine = ""

    offset_font_extra = 0
    if gui.row_font_size > 14:
        offset_font_extra = 8

    # In windows (arial?) draws numbers too high (hack fix)
    num_y_offset = 0
    #if system == 'windows':
    #    num_y_offset = 1


    if True or style == 1:


        # if not gui.rsp and not gui.combo_mode:
        #     width -= 10 * gui.scale

        dash = False
        if n_track.artist and colours.artist_text == colours.title_text:
            dash = True

        if n_track.title:

            line = track_number_process(n_track.track_number)

            indexLine = line

            if prefs.use_absolute_track_index and pctl.multi_playlist[pctl.active_playlist_viewing][4] == 1:
                indexLine = str(p_track)
                if len(indexLine) > 3:
                    indexLine += "  "

            line = ""

            if n_track.artist != "" and not dash:
                line0 = n_track.artist

                artistoffset = ddt.text((start_x + 27 * gui.scale,
                                         y),
                                        line0,
                                        alpha_mod(artistc, album_fade),
                                        gui.row_font_size,
                                        int(width / 2),
                                        )

                line = n_track.title
            else:
                line += n_track.title
        else:
            line = \
                os.path.splitext((n_track.filename))[
                    0]

        if p_track >= len(default_playlist):
            gui.pl_update += 1
            return

        index = default_playlist[p_track]
        star_x = 0
        total = star_store.get(index)

        if gui.star_mode == 'line' and total > 0 and pctl.master_library[index].length > 0:

            ratio = total / pctl.master_library[index].length
            if ratio > 0.55:
                star_x = int(ratio * 4 * gui.scale)
                if star_x > 60 * gui.scale:
                    star_x = 60 * gui.scale
                sp = y - 0 - gui.playlist_text_offset + int(gui.playlist_row_height / 2)
                if gui.playlist_row_height > 17 * gui.scale:
                    sp -= 1

                lh = 1
                if gui.scale != 1:
                    lh = 2

                colour = colours.star_line
                if this_line_playing and colours.star_line_playing is not None:
                    colour = colours.star_line_playing

                ddt.rect([width + start_x - star_x - 45 * gui.scale - offset_font_extra,
                          sp,
                          star_x + 3 * gui.scale,
                          lh
                          ], alpha_mod(colour, album_fade))

                star_x += 6 * gui.scale


        if gui.show_ratings:

            sx = round(width + start_x - round(40 * gui.scale) - offset_font_extra)
            sy = round(ry + (gui.playlist_row_height // 2) - round(7 * gui.scale))
            sx -= round(68 * gui.scale)

            draw_rating_widget(sx, sy, n_track)

            star_x += round(70 * gui.scale)


        if gui.star_mode == 'star' and total > 0 and pctl.master_library[
            index].length != 0:

            sx = width + start_x - 40 * gui.scale - offset_font_extra
            sy = ry + (gui.playlist_row_height // 2) - (6 * gui.scale)
            # if gui.scale == 1.25:
            #     sy += 1
            playtime_stars = star_count(total, pctl.master_library[index].length) - 1

            sx2 = sx
            selected_star = -2
            rated_star = -1

            #if key_ctrl_down:



            c = 60
            d = 6

            colour = [70, 70, 70, 255]
            if colours.lm:
                colour = [90, 90, 90, 255]
            #colour = alpha_mod(indexc, album_fade)


            for count in range(8):

                if selected_star < count and playtime_stars < count and rated_star < count:
                    break

                if count == 0:
                    sx -= round(13 * gui.scale)
                    star_x += round(13 * gui.scale)
                else:
                    if playtime_stars > 3:
                        dd = round(((13 - (playtime_stars - 3)) * gui.scale))
                        sx -= dd
                        star_x += dd
                    else:
                        sx -= round(13 * gui.scale)
                        star_x += round(13 * gui.scale)

                # if playtime_stars > 4:
                #     colour = [c + d * count, c + d * count, c + d * count, 255]
                # if playtime_stars > 6: # and count < 1:
                #     colour = [230, 220, 60, 255]
                if gui.tracklist_bg_is_light:
                    colour = alpha_blend([0, 0, 0, 200], ddt.text_background_colour)
                else:
                    colour = alpha_blend([255, 255, 255, 50], ddt.text_background_colour)

                # if selected_star > -2:
                #     if selected_star >= count:
                #         colour = (220, 200, 60, 255)
                # else:
                #     if rated_star >= count:
                #         colour = (220, 200, 60, 255)

                star_pc_icon.render(sx, sy, colour)

        if gui.show_hearts:

            xxx = star_x

            count = 0
            spacing = 6 * gui.scale

            yy = ry + (gui.playlist_row_height // 2) - (5 * gui.scale)
            if gui.scale == 1.25:
                yy += 1
            if xxx > 0:
                xxx += 3 * gui.scale

            if love(False, index):

                count = 1

                x = width + start_x - 52 * gui.scale - offset_font_extra - xxx

                f_store.store(display_you_heart, (x, yy))

                star_x += 18 * gui.scale

            for name in pctl.master_library[index].lfm_friend_likes:

                # Limit to number of hears to display
                if gui.star_mode == 'none':
                    if count > 6:
                        break
                else:
                    if count > 4:
                        break

                x = width + start_x - 52 * gui.scale - offset_font_extra - (heart_row_icon.w + spacing) * count - xxx

                f_store.store(display_friend_heart, (x, yy, name))

                count += 1

                star_x += heart_row_icon.w + spacing + 2

        # Draw track number/index
        display_queue = False

        if pctl.force_queue:

            marks = []
            album_type = False
            for i, item in enumerate(pctl.force_queue):
                if item[0] == n_track.index and item[1] == p_track and item[2] == pl_to_id(pctl.active_playlist_viewing):
                    if item[3] == 0:  # Only show mark if track type
                        marks.append(i)
                    # else:
                    #     album_type = True
                    #     marks.append(i)

            if marks:
                display_queue = True

        if display_queue:

            li = str(marks[0] + 1)
            if li == '1':
                li = "N"
                # if item[0] == n_track.index and item[1] == p_track and item[2] == pctl.active_playlist_viewing
                if pctl.playing_ready() and n_track.index == pctl.track_queue[pctl.queue_step] and p_track == pctl.playlist_playing_position:
                        li = "R"
                # if album_type:
                #     li = "A"

            # rect = (start_x + 3 * gui.scale, y - 1 * gui.scale, 5 * gui.scale, 5 * gui.scale)
            # ddt.rect_r(rect, [100, 200, 100, 255], True)
            if len(marks) > 1:
                li += " " + ("." * (len(marks) - 1))
                li = li[:5]

            # if album_type:
            #     li += "🠗"

            colour = [244, 200, 66, 255]
            if colours.lm:
                colour = [220, 40, 40, 255]

            ddt.text((start_x + 5 * gui.scale,
                      y, 2), li,
                     colour, gui.row_font_size + 200 - 1)

        else:
            if len(indexLine) > 2:

                ddt.text((start_x + 5 * gui.scale,
                          y, 2), indexLine,
                         alpha_mod(indexc, album_fade), gui.row_font_size)
            else:

                ddt.text((start_x,
                          y), indexLine,
                         alpha_mod(indexc, album_fade), gui.row_font_size)


        if dash and n_track.artist and n_track.title:
            line = n_track.artist + " - " + n_track.title

        ddt.text((start_x + 33 * gui.scale + artistoffset,
                  y),
                 line,
                 alpha_mod(titlec, album_fade),
                 gui.row_font_size,
                 width - 71 * gui.scale - artistoffset - star_x - 20 * gui.scale,
                 )

        line = get_display_time(n_track.length)

        ddt.text((width + start_x - 36 * gui.scale - offset_font_extra,
                  y + num_y_offset, 0), line,
                 alpha_mod(timec, album_fade), gui.row_font_size)


        f_store.recall_all()

pl_bg = None
if os.path.exists(user_directory + "/bg.png"):
    pl_bg = LoadImageAsset(user_directory + "/bg.png", True)

class StandardPlaylist:
    def __init__(self):
        pass

    def full_render(self):

        global highlight_left
        global highlight_right

        global playlist_hold
        global playlist_hold_position
        global playlist_selected
        global shift_selection

        global click_time
        global quick_drag
        global mouse_down
        global mouse_up
        global selection_stage

        global r_menu_index
        global r_menu_position

        left = gui.playlist_left
        width = gui.plw

        # center_mode = True
        # if gui.lsp or gui.rsp:  #gui.set_mode
        #     center_mode = False
        #
        # highlight_left = 0
        # highlight_width = width
        #
        # inset_left = highlight_left + 23 * gui.scale
        # inset_width = highlight_width - 32 * gui.scale
        #
        # if gui.lsp and not gui.rsp:
        #     inset_width -= 10 * gui.scale
        #
        # if gui.lsp:
        #     inset_left -= 10 * gui.scale
        #     inset_width += 10 * gui.scale
        #
        # if center_mode:
        #
        #     if gui.set_mode:
        #
        #         highlight_left = int(pow((window_size[0] * 0.004), 2))
        #         if window_size[0] < 600 * gui.scale:
        #             highlight_left = 3 * gui.scale
        #         highlight_width = highlight_width - (highlight_left * 2)
        #
        #         inset_left = highlight_left + 18 * gui.scale
        #         inset_width = highlight_width - 25 * gui.scale
        #
        #     else:
        #
        #         highlight_left = int(pow((window_size[0] * 0.01), 2))
        #         if window_size[0] < 600 * gui.scale:
        #             highlight_left = 3 * gui.scale
        #         highlight_width = highlight_width - (highlight_left * 2)
        #
        #         inset_left = highlight_left + 18 * gui.scale
        #         inset_width = highlight_width - 25 * gui.scale

        #
        # if window_size[0] < 600 and gui.lsp:
        #     inset_width = highlight_width - 18 * gui.scale

        highlight_width = gui.tracklist_highlight_width
        highlight_left = gui.tracklist_highlight_left
        inset_width = gui.tracklist_inset_width
        inset_left = gui.tracklist_inset_left
        center_mode = gui.tracklist_center_mode

        w = 0
        gui.row_extra = 0
        cv = 0  # update gui.playlist_current_visible_tracks

        # Draw the background
        SDL_SetRenderTarget(renderer, gui.tracklist_texture)
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0)
        SDL_RenderClear(renderer)

        rect = (left, gui.panelY, width, window_size[1])
        ddt.rect(rect, colours.playlist_panel_background)

        # This draws an optional background image
        if pl_bg:
            x = (left + highlight_width) - (pl_bg.w + round(60 * gui.scale))
            pl_bg.render(x, window_size[1] - gui.panelBY - pl_bg.h)
            #if not gui.set_mode:
            ddt.pretty_rect = ((left + highlight_width) - (pl_bg.w + 60 * gui.scale), window_size[1] - gui.panelBY - pl_bg.h, pl_bg.w, pl_bg.h)
            ddt.alpha_bg = True
                # ddt.rect(ddt.pretty_rect, [255, 0, 0, 100], True)

        # Mouse wheel scrolling
        if mouse_wheel != 0 and window_size[1] - gui.panelBY - 1 > mouse_position[
            1] > gui.panelY - 2 and gui.playlist_left < mouse_position[0] < gui.playlist_left + gui.plw \
                and not (coll(pl_rect)) and not search_over.active and not radiobox.active:

            # Set scroll speed
            mx = 4

            if gui.playlist_view_length < 25:
                mx = 3
            if gui.playlist_view_length < 10:
                mx = 2
            pctl.playlist_view_position -= mouse_wheel * mx


            if gui.playlist_view_length > 40:
                pctl.playlist_view_position -= mouse_wheel

            if mouse_wheel:
                console.print("DEBUG: Position changed by mouse wheel scroll: " + str(mouse_wheel))

            if pctl.playlist_view_position > len(default_playlist):
                pctl.playlist_view_position = len(default_playlist)
                console.print("DEBUG: Position changed by range bound")
            if pctl.playlist_view_position < 1:
                pctl.playlist_view_position = 0
                if default_playlist:
                    #edge_playlist.pulse()
                    edge_playlist2.pulse()

            scroll_hide_timer.set()
            gui.frame_callback_list.append(TestTimer(0.9))



        # Show notice if playlist empty
        if len(default_playlist) == 0:
            colour = alpha_mod(colours.index_text, 200)  # colours.playlist_text_missing

            top_a = gui.panelY
            if gui.artist_info_panel:
                top_a += gui.artist_panel_height

            b = window_size[1] - top_a - gui.panelBY
            half = int(top_a + (b * 0.60))

            if pl_bg:
                rect = (left + int(width / 2) - 80 * gui.scale, half - 10 * gui.scale,
                        190 * gui.scale, 60 * gui.scale)
                ddt.pretty_rect = rect
                ddt.alpha_bg = True

            ddt.text((left + int(width / 2) + 10 * gui.scale, half, 2),
                     _("Playlist is empty"), colour, 213, bg=colours.playlist_panel_background)
            ddt.text((left + int(width / 2) + 10 * gui.scale, half + 30 * gui.scale, 2),
                     _("Drag and drop files to import"), colour, 13, bg=colours.playlist_panel_background)

            ddt.pretty_rect = None
            ddt.alpha_bg = False

        # Show notice if at end of playlist
        elif pctl.playlist_view_position > len(default_playlist) - 1:
            colour = alpha_mod(colours.index_text, 200)

            top_a = gui.panelY
            if gui.artist_info_panel:
                top_a += gui.artist_panel_height

            b = window_size[1] - top_a - gui.panelBY
            half = int(top_a + (b * 0.17))

            if pl_bg:
                rect = (left + int(width / 2) - 60 * gui.scale, half - 5 * gui.scale,
                        140 * gui.scale, 30 * gui.scale)
                ddt.pretty_rect = rect
                ddt.alpha_bg = True

            ddt.text((left + int(width / 2) + 10 * gui.scale, half, 2), _("End of Playlist"),
                     colour, 213)

            ddt.pretty_rect = None
            ddt.alpha_bg = False

            # line = "Contains " + str(len(default_playlist)) + ' track'
            # if len(default_playlist) > 1:
            #     line += "s"
            #
            # ddt.draw_text((left + int(width / 2) + 10 * gui.scale, half + 24 * gui.scale, 2), line,
            #           colour, 12)


        # Process Input

        # type (0 is track, 1 is fold title), track_position, track_object, box, input_box,
        list_items = []
        number = 0

        for i in range(gui.playlist_view_length + 1):

            track_position = i + pctl.playlist_view_position

            # Make sure the view position is valid
            if pctl.playlist_view_position < 0:
                pctl.playlist_view_position = 0

            # Break if we are at end of playlist
            if len(default_playlist) <= track_position or number > gui.playlist_view_length:
                break

            track_object = pctl.g(default_playlist[track_position])
            track_id = track_object.index
            move_on_title = False

            line_y = gui.playlist_top + gui.playlist_row_height * number

            track_box = (left + highlight_left, line_y, highlight_width,
                            gui.playlist_row_height - 1)

            input_box = (track_box[0] + 30 * gui.scale, track_box[1] + 1, track_box[2] - 36 * gui.scale, track_box[3])

            # Are folder titles enabled?
            if pctl.multi_playlist[pctl.active_playlist_viewing][4] == 0 and break_enable:
                # Is this track from a different folder than the last?
                if track_position == 0 or track_object.parent_folder_path != pctl.g(default_playlist[track_position - 1]).parent_folder_path:
                    # Make folder title

                    highlight = False
                    drag_highlight = False

                    # Shift selection highlight
                    if (track_position in shift_selection and len(shift_selection) > 1):
                        highlight = True

                    # Tracks have been dropped?
                    if playlist_hold is True and coll(input_box):
                        if mouse_up:
                            move_on_title = True

                    # Ignore click in ratings box
                    click_title = (inp.mouse_click or right_click or middle_click) and coll(input_box)
                    if click_title and gui.show_album_ratings:
                        if mouse_position[0] > (input_box[0] + input_box[2]) - 80 * gui.scale:
                            click_title = False


                    # Detect folder title click
                    if click_title and mouse_position[1] < window_size[1] - gui.panelBY:

                        gui.pl_update += 1
                        # Add folder to queue if middle click
                        if middle_click and is_level_zero():
                            if key_ctrl_down:  # Add as ungrouped tracks
                                i = track_position
                                parent = pctl.g(default_playlist[i]).parent_folder_path
                                while i < len(default_playlist) and parent == pctl.g(default_playlist[i]).parent_folder_path:
                                    pctl.force_queue.append(queue_item_gen(default_playlist[i], i, pl_to_id(
                                        pctl.active_playlist_viewing)))
                                    i += 1
                                queue_timer_set(plural=True)
                                if prefs.stop_end_queue:
                                    pctl.auto_stop = False

                            else:  # Add as grouped album
                                add_album_to_queue(track_id, track_position)
                            playlist_selected = track_position
                            shift_selection = [playlist_selected]
                            gui.pl_update += 1

                        # Play if double click:
                        if d_mouse_click and track_position in shift_selection and coll_point(last_click_location, (input_box)):
                            click_time -= 1.5
                            pctl.jump(track_id, track_position)
                            line_hit = False
                            inp.mouse_click = False

                            if album_mode:
                                goto_album(pctl.playlist_playing_position)

                        # Show selection menu if right clicked after select
                        if right_click:
                            folder_menu.activate(track_id)
                            r_menu_position = track_position
                            selection_stage = 2
                            gui.pl_update = 1

                            if track_position not in shift_selection:
                                shift_selection = []
                                playlist_selected = track_position
                                u = track_position
                                while u < len(default_playlist) and track_object.parent_folder_path == pctl.master_library[
                                    default_playlist[u]].parent_folder_path:
                                    shift_selection.append(u)
                                    u += 1


                        # Add folder to selection if clicked
                        if inp.mouse_click and not (scroll_enable and mouse_position[0] < 30 * gui.scale) and not side_drag:

                            quick_drag = True
                            set_drag_source()

                            if not pl_is_locked(pctl.active_playlist_viewing) or key_shift_down:

                                playlist_hold = True

                            selection_stage = 1
                            temp = get_folder_tracks_local(track_position)
                            playlist_selected = track_position

                            if len(shift_selection) > 0 and key_shift_down:
                                if track_position < shift_selection[0]:
                                    for item in reversed(temp):
                                        if item not in shift_selection:
                                            shift_selection.insert(0, item)
                                else:
                                    for item in temp:
                                        if item not in shift_selection:
                                            shift_selection.append(item)

                            else:
                                shift_selection = copy.copy(temp)

                    # Should draw drag highlight?


                    if mouse_down and playlist_hold and coll(input_box) and track_position not in shift_selection:

                        if len(shift_selection) < 2 and not key_shift_down:
                            pass
                        else:
                            drag_highlight = True

                    # Something to do with quick search, I forgot
                    if playlist_selected > track_position + 1:
                        gui.row_extra += 1


                    list_items.append((1, track_position, track_object, track_box, input_box, highlight, number, drag_highlight, False))
                    number += 1

            if number > gui.playlist_view_length:
                break

            # Standard track ---------------------------------------------------------------------
            playing = False

            highlight = False
            drag_highlight = False
            line_y = gui.playlist_top + gui.playlist_row_height * number

            track_box = (left + highlight_left, line_y, highlight_width,
                            gui.playlist_row_height - 1)

            input_box = (track_box[0] + 30 * gui.scale, track_box[1] + 1, track_box[2] - 36 * gui.scale, track_box[3])

            # Test if line has mouse over or been clicked
            line_over = False
            line_hit = False
            if coll(input_box) and mouse_position[1] < window_size[1] - gui.panelBY:
                line_over = True
                if (inp.mouse_click or right_click or (middle_click and is_level_zero())):
                    line_hit = True
                    gui.pl_update += 1

                else:
                    line_hit = False
            else:
                line_hit = False
                line_over = False

            # Prevent click if near scroll bar
            if scroll_enable and mouse_position[0] < 30:
                line_hit = False

            # Double click to play
            if key_shift_down is False and d_mouse_click and line_hit and track_position == playlist_selected and coll_point(
                    last_click_location, input_box):


                pctl.jump(track_id, track_position)

                click_time -= 1.5
                quick_drag = False
                mouse_down = False
                mouse_up = False
                line_hit = False

                if album_mode:
                    goto_album(pctl.playlist_playing_position)

            if len(pctl.track_queue) > 0 and pctl.track_queue[pctl.queue_step] == track_id:
                if track_position == pctl.playlist_playing_position and pctl.active_playlist_viewing == pctl.active_playlist_playing:
                    this_line_playing = True

            # Add to queue on middle click
            if middle_click and line_hit:
                pctl.force_queue.append(queue_item_gen(track_id,
                                                       track_position, pl_to_id(pctl.active_playlist_viewing)))
                playlist_selected = track_position
                shift_selection = [playlist_selected]
                gui.pl_update += 1
                queue_timer_set()
                if prefs.stop_end_queue:
                    pctl.auto_stop = False


            # Deselect multiple if one clicked on and not dragged (mouse up is probably a bit of a hacky way of doing it)
            if len(shift_selection) > 1 and mouse_up and line_over and not key_shift_down and not key_ctrl_down and point_proximity_test(gui.drag_source_position, mouse_position, 15): # and not playlist_hold:
                shift_selection = [track_position]
                playlist_selected = track_position
                gui.pl_update = 1
                gui.update = 2


            # # Begin drag block selection
            # if mouse_down and line_over and track_position in shift_selection and len(shift_selection) > 1:
            #     if not pl_is_locked(pctl.active_playlist_viewing):
            #         playlist_hold = True
            #     elif key_shift_down:
            #         playlist_hold = True

            # Begin drag single track
            if inp.mouse_click and line_hit and not side_drag:
                quick_drag = True
                set_drag_source()


            # Shift Move Selection
            if move_on_title or mouse_up and playlist_hold is True and coll((
                    left + highlight_left, line_y, highlight_width, gui.playlist_row_height)):

                if len(shift_selection) > 1 or key_shift_down:
                    if track_position not in shift_selection: #p_track != playlist_hold_position and

                        if len(shift_selection) == 0:

                            ref = default_playlist[playlist_hold_position]
                            default_playlist[playlist_hold_position] = "old"
                            if move_on_title:
                                default_playlist.insert(track_position, "new")
                            else:
                                default_playlist.insert(track_position + 1, "new")
                            default_playlist.remove("old")
                            playlist_selected = default_playlist.index("new")
                            default_playlist[default_playlist.index("new")] = ref

                            gui.pl_update = 1


                        else:
                            ref = []
                            selection_stage = 2
                            for item in shift_selection:
                                ref.append(default_playlist[item])

                            for item in shift_selection:
                                default_playlist[item] = 'old'

                            for item in shift_selection:
                                if move_on_title:
                                    default_playlist.insert(track_position, "new")
                                else:
                                    default_playlist.insert(track_position + 1, "new")

                            for b in reversed(range(len(default_playlist))):
                                if default_playlist[b] == 'old':
                                    del default_playlist[b]
                            shift_selection = []
                            for b in range(len(default_playlist)):
                                if default_playlist[b] == 'new':
                                    shift_selection.append(b)
                                    default_playlist[b] = ref.pop(0)

                            playlist_selected = shift_selection[0]
                            gui.pl_update += 1

                        reload_albums(True)
                        pctl.notify_change()

            # Test show drag indicator
            if mouse_down and playlist_hold and coll(input_box) and track_position not in shift_selection:
                if len(shift_selection) > 1 or key_shift_down:
                    drag_highlight = True


            # Right click menu activation
            if right_click and line_hit and mouse_position[0] > gui.playlist_left + 10:

                if len(shift_selection) > 1 and track_position in shift_selection:
                    selection_menu.activate(default_playlist[track_position])
                    selection_stage = 2
                else:
                    r_menu_index = default_playlist[track_position]
                    r_menu_position = track_position
                    track_menu.activate(default_playlist[track_position])
                    gui.pl_update += 1
                    gui.update += 1

                    if track_position not in shift_selection:
                        playlist_selected = track_position
                        shift_selection = [playlist_selected]


            if line_over and inp.mouse_click:

                if track_position in shift_selection:
                    pass
                else:
                    selection_stage = 2
                    if key_shift_down:
                        start_s = track_position
                        end_s = playlist_selected
                        if end_s < start_s:
                            end_s, start_s = start_s, end_s
                        for y in range(start_s, end_s + 1):
                            if y not in shift_selection:
                                shift_selection.append(y)
                        shift_selection.sort()
                        playlist_selected = track_position
                    elif key_ctrl_down:
                        shift_selection.append(track_position)
                    else:
                        playlist_selected = track_position
                        shift_selection = [playlist_selected]

                if not pl_is_locked(pctl.active_playlist_viewing) or key_shift_down:
                    playlist_hold = True
                    playlist_hold_position = track_position

            # Activate drag if shift key down
            if quick_drag and pl_is_locked(pctl.active_playlist_viewing) and mouse_down:
                if key_shift_down:
                    playlist_hold = True
                else:
                    playlist_hold = False


            # Multi Select Highlight
            if track_position in shift_selection or track_position == playlist_selected:
                highlight = True

            if pctl.playing_state != 3 and len(pctl.track_queue) > 0 and pctl.track_queue[pctl.queue_step] == \
                    default_playlist[track_position]:
                if track_position == pctl.playlist_playing_position and pctl.active_playlist_viewing == pctl.active_playlist_playing:
                    playing = True

            list_items.append((0, track_position, track_object, track_box, input_box, highlight, number, drag_highlight, playing))
            number += 1

            if number > gui.playlist_view_length:
                break
        # ---------------------------------------------------------------------------------------

        # For every track in view
        #for i in range(gui.playlist_view_length + 1):
        gui.tracklist_bg_is_light = test_lumi(colours.playlist_panel_background) < 0.55


        for type, track_position, tr, track_box, input_box, highlight, number, drag_highlight, playing in list_items:

            line_y = gui.playlist_top + gui.playlist_row_height * number

            ddt.text_background_colour = colours.playlist_panel_background

            if type == 1:

                # Is type ALBUM TITLE

                line = tr.parent_folder_name


                album_artist_mode = False
                if tr.album_artist != "" and tr.album != "":
                    line = tr.album_artist + " - " + tr.album

                    if prefs.left_align_album_artist_title:
                        album_artist_mode = True
                        line = tr.album

                if len(line) < 6 and "CD" in line:
                    line = tr.album

                date = ""
                if prefs.append_date and year_search.search(tr.date):
                    date = "(" + d_date_display(tr) + ")"


                if line.endswith(")"):
                    b = line.split("(")
                    if len(b) > 1 and len(b[1]) <= 11:

                        match = year_search.search(b[1])

                        if match:
                            line = b[0]
                            date = "(" + b[1]

                elif line.startswith("("):

                    b = line.split(")")
                    if len(b) > 1 and len(b[0]) <= 11:

                        match = year_search.search(b[0])

                        if match:
                            line = b[1]
                            date = b[0] + ")"

                if "(" in line and year_search.search(line):
                    date = ""

                qq = 0

                # Calculate folder duration
                if prefs.append_total_time:
                    q = track_position

                    total_time = 0
                    while q < len(default_playlist):

                        if pctl.g(default_playlist[q]).parent_folder_path != tr.parent_folder_path:
                            break

                        total_time += pctl.g(default_playlist[q]).length

                        q += 1
                        qq += 1

                    if qq > 1:
                        date += " [ " + get_display_time(total_time) + " ]"  # Hair space inside brackets for better visual spacing


                ex = left + highlight_left + highlight_width - 7 * gui.scale


                height = line_y + gui.playlist_row_height - 19 * gui.scale  # gui.pl_title_y_offset

                # if tr.file_ext == "SPTY":
                #     ex -= round(20 * gui.scale)
                #     yyy = (line_y + gui.playlist_row_height // 2) - spotify_row_icon.h // 2
                #     colour = [255, 255, 255, 60]
                #     if gui.tracklist_bg_is_light:
                #         colour = [0,0,0,70]
                #     xxx = ex + round(6 * gui.scale)
                #     if colours.lm:
                #         xxx -= round(4 * gui.scale)
                #     spotify_row_icon.render(xxx, yyy, colour)

                star_offset = 0
                if gui.show_album_ratings:
                    star_offset = round(72 * gui.scale)
                    ex -= star_offset
                    draw_rating_widget(ex + 6 * gui.scale, height, tr, album=True)

                light_offset = 0
                if colours.lm:
                    light_offset = 3 * gui.scale
                ex -= light_offset

                if qq > 1:
                    ex += 1 * gui.scale

                ddt.text_background_colour = colours.playlist_panel_background

                if gui.scale == 2:
                    height += 1

                if highlight:
                    ddt.text_background_colour = alpha_blend(colours.row_select_highlight, colours.playlist_panel_background)
                    ddt.rect_a((left + highlight_left, gui.playlist_top + gui.playlist_row_height * number),
                               (highlight_width, gui.playlist_row_height), colours.row_select_highlight)

                date_w = 0
                if date:
                    date_w = ddt.text((ex, height, 1), date, colours.folder_title, gui.row_font_size + gui.pl_title_font_offset)
                    date_w += 4 * gui.scale
                    if qq > 1:
                        date_w -= 1 * gui.scale

                aa = 0
                if album_artist_mode:
                    colour = colours.artist_text
                    if "Album Artist" in colours.column_colours:
                        colour = colours.column_colours["Album Artist"]
                    aa = ddt.text((left + highlight_left + 14 * gui.scale, height), tr.album_artist, colour, gui.row_font_size + gui.pl_title_font_offset,
                                  gui.plw // 3)
                    aa += 12 * gui.scale

                ft_width = ddt.get_text_w(line, gui.row_font_size + gui.pl_title_font_offset)

                left_align = highlight_width - date_w - 13 * gui.scale - light_offset


                left_align -= star_offset

                extra = aa

                left_align -= extra



                if ft_width > left_align:
                    date_w += 19 * gui.scale
                    ddt.text((left + highlight_left + 8 * gui.scale + extra, height), line,
                             colours.folder_title,
                             gui.row_font_size + gui.pl_title_font_offset, highlight_width - date_w - extra - star_offset)

                else:

                    ddt.text((ex - date_w, height, 1), line,
                             colours.folder_title,
                             gui.row_font_size + gui.pl_title_font_offset)


                # Draw separation line below title
                ddt.rect((left + highlight_left, line_y + gui.playlist_row_height - 1 * gui.scale, highlight_width,
                          1 * gui.scale), colours.folder_line)


                # Draw blue highlight insert line
                if drag_highlight:
                    ddt.rect([left + highlight_left, line_y + gui.playlist_row_height - 1 * gui.scale,
                              highlight_width, 3 * gui.scale], [135, 145, 190, 255])

                continue


            # Draw playing highlight
            if playing:
                    ddt.rect(track_box, colours.row_playing_highlight)
                    ddt.text_background_colour = alpha_blend(colours.row_playing_highlight, ddt.text_background_colour)


            # Highlight blue if track is being broadcast
            if tr.index == pctl.broadcast_index and pctl.broadcast_active and pl_to_id(pctl.active_playlist_viewing) == pctl.broadcast_playlist:
                ddt.rect(track_box, [40, 40, 190, 80])
                ddt.text_background_colour = alpha_blend([40, 40, 190, 80], ddt.text_background_colour)

            # Highlight blue if track is being broadcast
            if tr.file_ext == "SPTY" and not spot_ctl.started_once:
                ddt.rect((track_box[0], track_box[1], track_box[2], track_box[3] + 1), [40, 190, 40, 20])
                ddt.text_background_colour = alpha_blend([40, 190, 40, 20], ddt.text_background_colour)

            # Blue drop line
            if drag_highlight: #playlist_hold_position != p_track:

                ddt.rect([left + highlight_left, line_y + gui.playlist_row_height - 1 * gui.scale, highlight_width,
                          3 * gui.scale], [125, 105, 215, 255])

            # Highlight
            if highlight:
                ddt.rect_a((left + highlight_left, line_y), (highlight_width, gui.playlist_row_height),
                           colours.row_select_highlight)

                ddt.text_background_colour = alpha_blend(colours.row_select_highlight, ddt.text_background_colour)


            if not gui.set_mode:

                line_render(tr, track_position, gui.playlist_text_offset + line_y,
                            playing, 255, left + inset_left, inset_width, 1, line_y)

            else:
                # NEE ---------------------------------------------------------
                n_track = tr
                p_track = track_position
                this_line_playing = playing


                start = 18 * gui.scale

                if center_mode:
                    start = inset_left

                elif gui.lsp:
                    start += gui.lspw

                run = start
                end = start + gui.plw

                if center_mode:
                    end = highlight_width + start

                # gui.tracklist_center_mode = center_mode
                # gui.tracklist_inset_left = inset_left - round(20 * gui.scale)
                # gui.tracklist_inset_width = inset_width + round(20 * gui.scale)


                for h, item in enumerate(gui.pl_st):

                    wid = item[1] - 20 * gui.scale
                    y = gui.playlist_text_offset + gui.playlist_top + gui.playlist_row_height * number
                    ry = gui.playlist_top + gui.playlist_row_height * number

                    if run > end - 50 * gui.scale:
                        break

                    if len(gui.pl_st) == h + 1:
                        wid -= 6 * gui.scale

                    if item[0] == "Rating":
                        if wid > 50 * gui.scale:
                            yy = ry + (gui.playlist_row_height // 2) - (6 * gui.scale)
                            draw_rating_widget(run + 4 * gui.scale, yy, n_track)

                    if item[0] == "Starline":

                        total = star_store.get_by_object(n_track)

                        if total > 0 and n_track.length != 0 and wid > 0:
                            if gui.star_mode == 'star':

                                star = star_count(total, n_track.length) - 1
                                rr = 0
                                if star > -1:
                                    if gui.tracklist_bg_is_light:
                                        colour = alpha_blend([0, 0, 0, 200], ddt.text_background_colour)
                                    else:
                                        colour = alpha_blend([255, 255, 255, 50], ddt.text_background_colour)

                                    sx = run + 6 * gui.scale
                                    sy = ry + (gui.playlist_row_height // 2) - (6 * gui.scale)
                                    for count in range(8):
                                        if star < count or rr > wid + round(6 * gui.scale):
                                            break
                                        star_pc_icon.render(sx, sy, colour)
                                        sx += round(13) * gui.scale
                                        rr += round(13) * gui.scale

                            else:

                                ratio = total / n_track.length
                                if ratio > 0.55:
                                    star_x = int(ratio * (4 * gui.scale))
                                    if star_x > wid:
                                        star_x = wid

                                    colour = colours.star_line
                                    if playing and colours.star_line_playing is not None:
                                        colour = colours.star_line_playing

                                    sy = (gui.playlist_top + gui.playlist_row_height * number) + int(gui.playlist_row_height / 2)
                                    ddt.rect((run + 4 * gui.scale, sy, star_x, 1 * gui.scale), colour)

                    else:
                        text = ""
                        font = gui.row_font_size
                        colour = [200, 200, 200, 255]
                        norm_colour = colour
                        y_off = 0
                        if item[0] == "Title":
                            colour = colours.title_text
                            if n_track.title != "":
                                text = n_track.title
                            else:
                                text = n_track.filename
                            #     colour = colours.index_playing
                            if this_line_playing is True:
                                colour = colours.title_playing

                        elif item[0] == "Artist":
                            text = n_track.artist
                            colour = colours.artist_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.artist_playing
                        elif item[0] == "Album":
                            text = n_track.album
                            colour = colours.album_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.album_playing
                        elif item[0] == "Album Artist":
                            text = n_track.album_artist
                            if not text and prefs.column_aa_fallback_artist:
                                text = n_track.artist
                            colour = colours.artist_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.artist_playing
                        elif item[0] == "Composer":
                            text = n_track.composer
                            colour = colours.index_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.index_playing
                        elif item[0] == "Comment":
                            text = n_track.comment.replace("\n", " ").replace("\r", " ")
                            colour = colours.index_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.index_playing
                        elif item[0] == "S":
                            if n_track.lfm_scrobbles > 0:
                                text = str(n_track.lfm_scrobbles)

                            colour = colours.index_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.index_playing
                        elif item[0] == "#":

                            if prefs.use_absolute_track_index and pctl.multi_playlist[pctl.active_playlist_viewing][4] == 1:
                                text = str(p_track)
                            else:
                                text = track_number_process(n_track.track_number)

                            colour = colours.index_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.index_playing
                        elif item[0] == "Date":
                            text = n_track.date
                            colour = colours.index_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.index_playing
                        elif item[0] == "Filepath":
                            text = clean_string(n_track.fullpath)
                            colour = colours.index_text
                            norm_colour = colour
                        elif item[0] == "Filename":
                            text = clean_string(n_track.filename)
                            colour = colours.index_text
                            norm_colour = colour
                        elif item[0] == "Codec":
                            text = n_track.file_ext
                            colour = colours.index_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.index_playing
                        elif item[0] == "Lyrics":
                            text = ""
                            if n_track.lyrics != "":
                                text = 'Y'
                            colour = colours.index_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.index_playing
                        elif item[0] == "Genre":
                            text = n_track.genre
                            colour = colours.index_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.index_playing
                        elif item[0] == "Bitrate":
                            text = str(n_track.bitrate)
                            if text == "0":
                                text = ""
                            if n_track.file_ext == "FLAC" or n_track.file_ext == "WAV" or n_track.file_ext == "APE":
                                text = str(round(n_track.samplerate / 1000, 1)).rstrip("0").rstrip(".") + "|" + str(n_track.bit_depth)
                            colour = colours.index_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.index_playing
                        elif item[0] == "Time":
                            text = get_display_time(n_track.length)
                            colour = colours.bar_time
                            norm_colour = colour
                            #colour = colours.time_text
                            if this_line_playing is True:
                                colour = colours.time_text
                        elif item[0] == "❤":
                            # col love
                            u = 5 * gui.scale
                            yy = ry + (gui.playlist_row_height // 2) - (5 * gui.scale)
                            if gui.scale == 1.25:
                                yy += 1

                            if get_love(n_track):

                                j = 0  # justify right
                                if run < start + 100 * gui.scale:
                                    j = 1  # justify left

                                display_you_heart(run + 6 * gui.scale, yy, j)
                                u += 18 * gui.scale

                            count = 0
                            for name in n_track.lfm_friend_likes:
                                spacing = 6 * gui.scale
                                if u + (heart_row_icon.w + spacing) * count > wid + 7 * gui.scale:
                                    break

                                x = run + u + (heart_row_icon.w + spacing) * count

                                j = 0  # justify right
                                if run < start + 100 * gui.scale:
                                    j = 1  # justify left

                                display_friend_heart(x, yy, name, j)
                                count += 1

                            # if n_track.track_number == 1 or n_track.track_number == "1":
                            #     ss = wid - (wid % 15)
                            #     gall_ren.render(n_track, (run, y), ss)


                        elif item[0] == "P":
                            ratio = 0
                            total = star_store.get_by_object(n_track)
                            if total > 0 and n_track.length > 2:
                                if n_track.length > 15:
                                    total += 2
                                ratio = total / (n_track.length - 1)

                            text = str(str(int(ratio)))
                            if text == "0":
                                text = ""
                            colour = colours.index_text
                            norm_colour = colour
                            if this_line_playing is True:
                                colour = colours.index_playing

                        if prefs.dim_art and album_mode and \
                                n_track.parent_folder_name \
                                != pctl.master_library[pctl.track_queue[pctl.queue_step]].parent_folder_name:
                            colour = alpha_mod(colour, 150)
                        if n_track.found is False:
                            colour = colours.playlist_text_missing

                        if text:
                            if item[0] in colours.column_colours:
                                colour = colours.column_colours[item[0]]

                            if this_line_playing and item[0] in colours.column_colours_playing:
                                colour = colours.column_colours_playing[item[0]]

                            if run + 6 * gui.scale + wid > end:
                                wid = end - run - 40 * gui.scale
                                if center_mode:
                                    wid += 25 * gui.scale

                            wid = max(0, wid)

                            # # Hacky. Places a dark background behind light text for readability over mascot
                            # if pl_bg and gui.set_mode and colour_value(norm_colour) < 400 and not colours.lm:
                            #     w, h = ddt.get_text_wh(text, font, wid)
                            #     quick_box = [run + round(5 * gui.scale), y + y_off, w + round(2 * gui.scale), h]
                            #     if coll_rect((left + width - pl_bg.w - 60 * gui.scale, window_size[1] - gui.panelBY - pl_bg.h, pl_bg.w, pl_bg.h), quick_box):
                            #         quick_box = (run, ry, item[1], gui.playlist_row_height)
                            #         ddt.rect(quick_box, [0, 0, 0, 40], True)
                            #         ddt.rect(quick_box, alpha_mod(colours.playlist_panel_background, 150), True)

                            ddt.text((run + 6 * gui.scale, y + y_off),
                                          text,
                                          colour,
                                          font,
                                          max_w=wid)


                            if ddt.was_truncated:
                                # print(text)
                                rect = (run, y, wid - 1, gui.playlist_row_height - 1)
                                gui.heart_fields.append(rect)

                                if coll(rect):
                                    columns_tool_tip.set(run - 7 * gui.scale, y, text, font, rect)

                    run += item[1]


            # -----------------------------------------------------------------
            # Count the number if visable tracks (used by Show Current function)
            if gui.playlist_top + gui.playlist_row_height * w > window_size[0] - gui.panelBY - gui.playlist_row_height:
                pass
            else:
                cv += 1

            #w += 1
            # if w > gui.playlist_view_length:
            #     break

        # This is a bit hacky since its only generated after drawing.
        # Used to keep track of how many tracks are actually in view.
        gui.playlist_current_visible_tracks = cv
        gui.playlist_current_visible_tracks_id = pctl.multi_playlist[pctl.active_playlist_viewing][6]


        if (right_click and gui.playlist_top + 5 * gui.scale + gui.playlist_row_height * len(list_items) < mouse_position[1] < window_size[
            1] - 55 and width + left > mouse_position[0] > gui.playlist_left + 15):
            playlist_menu.activate()

        SDL_SetRenderTarget(renderer, gui.main_texture)
        SDL_RenderCopy(renderer, gui.tracklist_texture, None, gui.tracklist_texture_rect)

        if mouse_down is False:
            playlist_hold = False

        ddt.pretty_rect = None
        ddt.alpha_bg = False

    def cache_render(self):

        SDL_RenderCopy(renderer, gui.tracklist_texture, None, gui.tracklist_texture_rect)


playlist_render = StandardPlaylist()


class ArtBox:

    def __init__(self):
        pass

    def draw(self, x, y, w, h, target_track=None, tight_border=False, default_border=None):

        # Draw a background for whole area
        ddt.rect((x, y, w, h), colours.side_panel_background)
        # ddt.rect_r((x, y, w ,h), [255, 0, 0, 200], True)

        # We need to find the size of the inner square for the artwork
        #box = min(w, h)

        box_w = w
        box_h = h

        box_w -= 17 * gui.scale  # Inset the square a bit
        box_h -= 17 * gui.scale  # Inset the square a bit

        box_x = x + ((w - box_w) // 2)
        box_y = y + ((h - box_h) // 2)

        # And position the square
        rect = (box_x, box_y, box_w, box_h)
        gui.main_art_box = rect

        # Draw the album art. If side bar is being dragged set quick draw flag
        showc = None
        result = 1

        if target_track:  # Only show if song playing or paused
            result = album_art_gen.display(target_track, (rect[0], rect[1]), (box_w, box_h), side_drag)
            showc = album_art_gen.get_info(target_track)

        # Draw faint border on album art
        if tight_border:
            if result == 0 and gui.art_drawn_rect:
                border = gui.art_drawn_rect
                ddt.rect_s(gui.art_drawn_rect, colours.art_box, 1 * gui.scale)
            elif default_border:
                border = default_border
                ddt.rect_s(default_border, colours.art_box, 1 * gui.scale)
            else:
                border = rect
        else:
            ddt.rect_s(rect, colours.art_box, 1 * gui.scale)
            border = rect

        fields.add(border)

        # Draw image downloading indicator
        if gui.image_downloading:
            ddt.text((x + int(box_w / 2), 38 * gui.scale + int(box_h / 2), 2), "Fetching image...", colours.side_bar_line1,
                     14, bg=colours.side_panel_background)
            gui.update = 2

        # Input for album art
        if target_track:

            # Cycle images on click

            if coll(gui.main_art_box) and inp.mouse_click is True and key_focused == 0:

                album_art_gen.cycle_offset(target_track)

                if pctl.mpris:
                    pctl.mpris.update(force=True)

        # Activate picture context menu on right click
        if tight_border and gui.art_drawn_rect:
            if right_click and coll(gui.art_drawn_rect) and target_track:
                picture_menu.activate(in_reference=target_track)
        else:
            if right_click and coll(rect) and target_track:
                picture_menu.activate(in_reference=target_track)

        # Draw picture metadata
        if showc is not None and coll(border) \
                and rename_track_box.active is False \
                and radiobox.active is False \
                and pref_box.enabled is False \
                and gui.rename_playlist_box is False \
                and gui.message_box is False \
                and track_box is False \
                and gui.layer_focus == 0:

            padding = 6 * gui.scale

            xw = box_x + box_w
            yh = box_y + box_h
            if tight_border and gui.art_drawn_rect and gui.art_drawn_rect[2] > 50 * gui.scale:
                xw = gui.art_drawn_rect[0] + gui.art_drawn_rect[2]
                yh = gui.art_drawn_rect[1] + gui.art_drawn_rect[3]

            art_metadata_overlay(xw, yh, showc)

art_box = ArtBox()

class ScrollBox():

    def __init__(self):

        self.held = False
        self.slide_hold = False
        self.source_click_y = 0
        self.source_bar_y = 0
        self.direction_lock = -1
        self.d_position = 0

    def draw(self, x, y, w, h, value, max_value, force_dark_theme=False, click=None, r_click=False, jump_distance=4, extend_field=0):

        if max_value < 2:
            return 0

        if click is None:
            click = inp.mouse_click

        bar_height = round(90 * gui.scale)

        if h > 400 * gui.scale and max_value < 20:
            bar_height = round(180 * gui.scale)


        bg = [255, 255, 255, 7]
        fg = [255, 255, 255, 30]
        fg_h = [255, 255, 255, 40]
        fg_off = [255, 255, 255, 15]

        if colours.lm and not force_dark_theme:
            bg = [0, 0, 0, 15]
            fg_off = [0, 0, 0, 30]
            fg = [0, 0, 0, 60]
            fg_h = [0, 0, 0, 70]

        ddt.rect((x, y, w, h), bg)

        half = bar_height // 2

        ratio = value / max_value

        mi = y + half
        mo = y + h - half
        distance = mo - mi
        position = int(round(distance * ratio))

        fw = w + extend_field
        fx = x - extend_field


        if coll((fx, y, fw, h)):

            if mouse_down:
                gui.update += 1

            if r_click:

                p = mouse_position[1] - half - y
                p = max(0, p)

                range = h - bar_height
                p = min(p, range)

                per = p / range

                value = int(round(max_value * per))

                ratio = value / max_value

                mi = y + half
                mo = y + h - half
                distance = mo - mi
                position = int(round(distance * ratio))

            in_bar = False
            if coll((x, mi + position - half, w, bar_height)):
                in_bar = True
                if click:
                    self.held = True

                    # p_y = pointer(c_int(0))
                    # SDL_GetGlobalMouseState(None, p_y)
                    get_sdl_input.mouse_capture_want = True
                    self.source_click_y = mouse_position[1]
                    self.source_bar_y = position


            if pctl.playlist_view_position < 0:
                pctl.playlist_view_position = 0


            elif mouse_down and not self.held:

                if click and not in_bar:
                    self.slide_hold = True
                    self.direction_lock = 1
                    if mouse_position[1] - y < position:
                        self.direction_lock = 0

                    self.d_position = value / max_value

                if self.slide_hold:
                    if (self.direction_lock == 1 and mouse_position[1] - y < position + half) or \
                        (self.direction_lock == 0 and mouse_position[1] - y > position + half):
                        pass
                    else:

                        tt = scroll_timer.hit()
                        if tt > 0.1:
                            tt = 0

                        flip = -1
                        if self.direction_lock:
                            flip = 1

                        self.d_position = min(max(self.d_position + (((tt * jump_distance) / max_value) * flip), 0), 1)

            else:
                self.slide_hold = False

        if self.held and mouse_up or not mouse_down:
            self.held = False

        if self.held and not window_is_focused():
            self.held = False

        if self.held:
            get_sdl_input.mouse_capture_want = True
            new_y = mouse_position[1]
            gui.update += 1

            offset = new_y - self.source_click_y

            position = self.source_bar_y + offset

            if position < 0:
                position = 0
            if position > distance:
                position = distance

            ratio = position / distance
            value = int(round(max_value * ratio))

        colour = fg_off
        rect = (x, mi + position - half, w, bar_height)
        fields.add(rect)
        if coll(rect):
            colour = fg
        if self.held:
            colour = fg_h

        ddt.rect(rect, colour)

        if self.slide_hold:
            return round(max_value * self.d_position)

        return value

mini_lyrics_scroll = ScrollBox()
playlist_panel_scroll = ScrollBox()
artist_info_scroll = ScrollBox()
device_scroll = ScrollBox()
artist_list_scroll = ScrollBox()
gallery_scroll = ScrollBox()
tree_view_scroll = ScrollBox()
radio_view_scroll = ScrollBox()


class RadioBox:

    def __init__(self):

        self.active = False
        self.station_editing = None
        self.edit_mode = True
        self.add_mode = False
        self.radio_field_active = 1
        self.radio_field = TextBox2()
        self.radio_field_title = TextBox2()
        self.radio_field_search = TextBox2()

        self.x = 1
        self.y = 1
        self.w = 1
        self.h = 1
        self.center = False

        self.scroll_position = 0
        self.scroll = ScrollBox()

        self.dummy_track = TrackClass()
        self.dummy_track.index = -2
        self.dummy_track.is_network = True
        self.dummy_track.art_url_key = "" #radio"
        self.dummy_track.file_ext = "RADIO"
        self.playing_title = ""

        self.proxy_started = False
        self.loaded_url = None
        self.loaded_station = None
        self.load_connecting = False
        self.load_failed = False
        self.searching = False
        self.load_failed_timer = Timer()
        self.right_clicked_station = None
        self.right_clicked_station_p = None
        self.click_point = (0, 0)

        self.song_key = ""
        
        self.drag = None

        self.tab = 0
        self.temp_list = []

        self.hosts = None
        self.host = None

        self.search_menu = Menu(170)
        self.search_menu.add(_("Search Tag"), self.search_tag, pass_ref=True)
        self.search_menu.add(_("Search Country Code"), self.search_country, pass_ref=True)
        self.search_menu.add(_("Search Title"), self.search_title, pass_ref=True)

        self.websocket = None
        self.ws_interval = 4.5
        self.websocket_source_urls = ("https://listen.moe/kpop/stream", "https://listen.moe/stream")

    def parse_vorbis_okay(self):
        return (self.loaded_url not in self.websocket_source_urls) and \
               "radio.plaza.one" not in self.loaded_url and \
                "gensokyoradio.net" not in self.loaded_url

    def search_country(self, text):

        if len(text) == 2 and text.isalpha():
            self.search_radio_browser(
                "/json/stations/search?countrycode=" + text + "&order=votes&limit=250&reverse=true")
        else:
            self.search_radio_browser(
                "/json/stations/search?country=" + text + "&order=votes&limit=250&reverse=true")

    def search_tag(self, text):

        text = text.lower()
        self.search_radio_browser("/json/stations/search?order=votes&limit=250&reverse=true&tag=" + text)

    def search_title(self, text):

        text = text.lower()
        self.search_radio_browser("/json/stations/search?order=votes&limit=250&reverse=true&name=" + text)

    def start(self, item):
        url = item["stream_url"]
        print("Start radio")
        print(url)
        if url.endswith("m3u") or url.endswith("m3u8"):

            show_message("Sorry, m3u parsing not fully implemented.")
            return

        if self.load_connecting:
            return

        if spot_ctl.playing or spot_ctl.coasting:
            spot_ctl.control("stop")

        try:
            self.websocket.close()
            print("Websocket closed")
        except:
            print("No socket to close?")

        self.playing_title = ""
        self.playing_title = item["title"]
        self.dummy_track.art_url_key = ""
        self.dummy_track.title = ""
        self.dummy_track.artist = ""
        self.dummy_track.album = ""
        self.dummy_track.date = ""
        pctl.radio_meta_on = ""

        album_art_gen.clear_cache()

        if shutil.which(ffmpeg) is None:
            show_message(_("FFmpeg does not appear to be installed"), mode="error")
            prefs.auto_rec = False

        if not self.proxy_started and prefs.backend != 4:
            shoot = threading.Thread(target=stream_proxy, args=[tauon])
            shoot.daemon = True
            shoot.start()
            self.proxy_started = True

        # pctl.url = url
        pctl.url = f"http://127.0.0.1:{7812}"
        self.loaded_url = None
        pctl.tag_meta = ""
        pctl.radio_meta_on = ""
        pctl.found_tags = {}
        self.song_key = ""
        pctl.playing_time = 0
        pctl.decode_time = 0
        self.loaded_station = item

        if tauon.stream_proxy.download_running:
            tauon.stream_proxy.abort = True

        self.load_connecting = True
        self.load_failed = False

        shoot = threading.Thread(target=self.start2, args=[url])
        shoot.daemon = True
        shoot.start()

    def start2(self, url):

        if not tauon.stream_proxy.start_download(url):
            self.load_failed_timer.set()
            self.load_failed = True
            self.load_connecting = False
            gui.update += 1
            print("Start radio failed")
            # show_message(_("Failed to establish a connection"), mode="error")
            return

        self.loaded_url = url
        pctl.playing_state = 0
        pctl.record_stream = False
        pctl.playerCommand = "url"
        pctl.playerCommandReady = True
        pctl.playing_state = 3
        pctl.playing_time = 0
        pctl.decode_time = 0
        pctl.playing_length = 0
        tm.ready_playback()
        hit_discord()

        if tauon.update_play_lock is not None:
            tauon.update_play_lock()

        time.sleep(0.1)
        self.load_connecting = False
        self.load_failed = False
        gui.update += 1

        wss = ""
        if url == "https://listen.moe/kpop/stream":
            wss = "wss://listen.moe/kpop/gateway_v2"
        if url == "https://listen.moe/stream":
            wss = "wss://listen.moe/gateway_v2"
        if wss:
            print("Connecting to Listen.moe")
            import websocket
            import _thread as th

            def send_heartbeat(ws):
                #print(self.ws_interval)
                time.sleep(self.ws_interval)
                ws.send("{\"op\":9}")
                print("Send heatbeat")

            def on_message(ws, message):
                print(message)
                d = json.loads(message)
                if d["op"] == 10:
                    shoot = threading.Thread(target=send_heartbeat, args=[ws])
                    shoot.daemon = True
                    shoot.start()

                if d["op"] == 0:
                    self.ws_interval = d["d"]["heartbeat"] / 1000
                    ws.send("{\"op\":9}")

                if d["op"] == 1:
                    try:

                        found_tags = {}
                        found_tags["title"] = d["d"]["song"]["title"]
                        if d["d"]["song"]["artists"]:
                            found_tags["artist"] = d["d"]["song"]["artists"][0]["name"]
                        line = ""
                        if "title" in found_tags:
                            line += found_tags["title"]
                            if "artist" in found_tags:
                                line = found_tags["artist"] + " - " + line

                        pctl.found_tags = found_tags
                        pctl.tag_meta = line

                        filename = d["d"]["song"]["albums"][0]["image"]
                        fulllink = "https://cdn.listen.moe/covers/" + filename

                        # print(fulllink)
                        art_response = requests.get(fulllink)
                        #print(art_response.status_code)

                        if art_response.status_code == 200:
                            if pctl.radio_image_bin:
                                pctl.radio_image_bin.close()
                                pctl.radio_image_bin = None
                            pctl.radio_image_bin = io.BytesIO(art_response.content)
                            pctl.radio_image_bin.seek(0)
                            radiobox.dummy_track.art_url_key = "ok"
                            print("Got new art")


                    except:
                        print("No image")
                        if pctl.radio_image_bin:
                            pctl.radio_image_bin.close()
                            pctl.radio_image_bin = None
                    gui.clear_image_cache_next += 1
                    gui.update += 1

            def on_error(ws, error):
                pass
                print(error)

            def on_close(ws):
                pass
                print("### closed ###")

            def on_open(ws):
                def run(*args):
                    pass
                    # for i in range(3):
                    #     time.sleep(4.5)
                    #     ws.send("{\"op\":9}")
                    # time.sleep(10)
                    # ws.close()
                    #print("thread terminating...")

                th.start_new_thread(run, ())

            #websocket.enableTrace(True)
            #print(wss)
            ws = websocket.WebSocketApp(wss,
                                        on_message=on_message,
                                        on_error=on_error)
            ws.on_open = on_open
            self.websocket = ws
            shoot = threading.Thread(target=ws.run_forever)
            shoot.daemon = True
            shoot.start()


    def delete_radio_entry(self, item):
        for i, saved in enumerate(prefs.radio_urls):
            if saved["stream_url"] == item["stream_url"] and saved["title"] == item["title"]:
                del prefs.radio_urls[i]

    def delete_radio_entry_after(self, item):
        p = radiobox.right_clicked_station_p
        del prefs.radio_urls[p + 1:]

    def edit_entry(self, item):
        radio = item
        self.radio_field_title.text = radio["title"]
        self.radio_field.text = radio["stream_url"]

    def browser_get_hosts(self):

        import socket
        """
        Get all base urls of all currently available radiobrowser servers

        Returns: 
        list: a list of strings

        """
        hosts = []
        # get all hosts from DNS
        ips = socket.getaddrinfo('all.api.radio-browser.info',
                                 80, 0, 0, socket.IPPROTO_TCP)
        for ip_tupple in ips:
            try:
                ip = ip_tupple[4][0]

                # do a reverse lookup on every one of the ips to have a nice name for it
                host_addr = socket.gethostbyaddr(ip)
                # add the name to a list if not already in there
                if host_addr[0] not in hosts:
                    hosts.append(host_addr[0])
            except:
                print("Ip lookup fail")

        # sort list of names
        hosts.sort()
        # add "https://" in front to make it an url
        return list(map(lambda x: "https://" + x, hosts))

    def search_page(self):

        y = self.y
        x = self.x
        w = self.w
        h = self.h

        yy = y + round(40 * gui.scale)

        width = round(330 * gui.scale)
        rect = (x + 8 * gui.scale, yy - round(2 * gui.scale), width, 22 * gui.scale)
        fields.add(rect)
        # if (coll(rect) and gui.level_2_click) or (input.key_tab_press and self.radio_field_active == 2):
        #     self.radio_field_active = 1
        #     input.key_tab_press = False
        if not self.radio_field_search.text and not editline:
            ddt.text((x + 14 * gui.scale, yy), _("Search text…"), colours.box_text_label, 312)
        self.radio_field_search.draw(x + 14 * gui.scale, yy, colours.box_input_text,
                                    active=True,
                                    width=width, click=gui.level_2_click)

        ddt.rect_s(rect, colours.box_text_border, 1 * gui.scale)

        if draw.button(_("Search"), x + width + round(21 * gui.scale), yy - round(3 * gui.scale),
                       press=gui.level_2_click, w=round(80 * gui.scale)) or inp.level_2_enter:

            text = self.radio_field_search.text.replace("/", "").replace(":", "").replace("\\", "").replace(".", "").replace("-", "").upper()
            text = urllib.parse.quote(text)
            if len(text) > 1:
                self.search_menu.activate(text, position=(x + width + round(21 * gui.scale), yy + round(20 * gui.scale)))
        if draw.button(_("Get Top Voted"), x + round(8 * gui.scale), yy + round(30 * gui.scale),
                       press=gui.level_2_click):
            self.search_radio_browser("/json/stations?order=votes&limit=250&reverse=true")

        ww = ddt.get_text_w(_("Get Top Voted"), 212)
        if key_shift_down:
            if draw.button(_("Developer Picks"), x + ww + round(35 * gui.scale), yy + round(30 * gui.scale),
                       press=gui.level_2_click):

                self.temp_list.clear()

                radio = {}
                radio["title"] = "Nightwave Plaza"
                radio["stream_url_unresolved"] = "http://radio.plaza.one/ogg"
                radio["stream_url"] = "http://radio.plaza.one/ogg"
                radio["website_url"] = "https://plaza.one/"
                radio["icon"] = "https://plaza.one//img/icons/mstile-144x144.png?v=7knqx0aR5k"
                radio["country"] = "Japan"
                self.temp_list.append(radio)


                radio = {}
                radio["title"] = "Gensokyo Radio"
                radio["stream_url_unresolved"] = " 	https://stream.gensokyoradio.net/GensokyoRadio-enhanced.m3u"
                radio["stream_url"] = "https://stream.gensokyoradio.net/1"
                radio["website_url"] = "https://gensokyoradio.net/"
                radio["icon"] = "https://gensokyoradio.net/favicon.ico"
                radio["country"] = "Japan"
                self.temp_list.append(radio)

                radio = {}
                radio["title"] = "Listen.moe | Jpop"
                radio["stream_url_unresolved"] = "https://listen.moe/stream"
                radio["stream_url"] = "https://listen.moe/stream"
                radio["website_url"] = "https://listen.moe/"
                radio["icon"] = "https://avatars.githubusercontent.com/u/26034028?s=200&v=4"
                radio["country"] = "Japan"
                self.temp_list.append(radio)

                radio = {}
                radio["title"] = "Listen.moe | Kpop"
                radio["stream_url_unresolved"] = "https://listen.moe/kpop/stream"
                radio["stream_url"] = "https://listen.moe/kpop/stream"
                radio["website_url"] = "https://listen.moe/"
                radio["icon"] = "https://avatars.githubusercontent.com/u/26034028?s=200&v=4"
                radio["country"] = "Korea"

                self.temp_list.append(radio)

                # radio = {}
                # radio["title"] = "DKFM Shoegaze Radio"
                # radio["stream_url_unresolved"] = "https://maggie.torontocast.com:8090/live.mp3"
                # radio["stream_url"] = "https://maggie.torontocast.com:8090/live.mp3"
                # radio["website_url"] = "https://decayfm.com/"
                # radio["icon"] = "https://decayfm.com/wp-content/uploads/2018/12/cropped-512-1-192x192.jpg"
                # radio["country"] = "California"
                # self.temp_list.append(radio)

                radio = {}
                radio["title"] = "HBR1 Dream Factory | Ambient"
                radio["stream_url_unresolved"] = "http://radio.hbr1.com:19800/ambient.ogg"
                radio["stream_url"] = "http://radio.hbr1.com:19800/ambient.ogg"
                radio["website_url"] = "http://www.hbr1.com/"
                self.temp_list.append(radio)

                radio = {}
                radio["title"] = "Yggdrasil Radio | Anime & Jpop"
                radio["stream_url_unresolved"] = "http://shirayuki.org:9200/"
                radio["stream_url"] = "http://shirayuki.org:9200/"
                radio["website_url"] = "https://yggdrasilradio.net/"
                self.temp_list.append(radio)

    def search_radio_browser(self, param):
        if self.searching:
            return
        self.searching = True
        shoot = threading.Thread(target=self.search_radio_browser2, args=[param])
        shoot.daemon = True
        shoot.start()

    def search_radio_browser2(self, param):

        if not self.hosts:
            self.hosts = self.browser_get_hosts()
        if not self.host:
            self.host = random.choice(self.hosts)

        uri = self.host + param
        req = urllib.request.Request(uri)
        req.add_header('User-Agent', t_agent)
        req.add_header('Content-Type', 'application/json')
        response = urllib.request.urlopen(req)
        data = response.read()
        data = json.loads(data.decode())
        self.parse_data(data)
        self.searching = False

    def parse_data(self, data):

        self.temp_list.clear()

        for station in data:
            radio = {}
            #print(station)
            radio["title"] = station["name"]
            radio["stream_url_unresolved"] = station["url"]
            radio["stream_url"] = station["url_resolved"]
            radio["icon"] = station["favicon"]
            radio["country"] = station["country"]
            if radio["country"] == "The Russian Federation":
                radio["country"] = "Russia"
            elif radio["country"] == "The United States Of America":
                radio["country"] = "USA"
            elif radio["country"] == "The United Kingdom Of Great Britain And Northern Ireland":
                radio["country"] = "United Kingdom"
            elif radio["country"] == "Islamic Republic Of Iran":
                radio["country"] = "Iran"
            elif len(station["country"]) > 20:
                radio["country"] = station["countrycode"]
            radio["website_url"] = station["homepage"]
            if "homepage" in station:
                radio["website_url"] = station["homepage"]
            self.temp_list.append(radio)
        gui.update += 1

    def render(self):

        if self.edit_mode:
            w = round(510 * gui.scale)
            h = round(120 * gui.scale)  # + sh

            self.w = w
            self.h = h
            # self.x = x
            # self.y = y
            width = w
            if self.center:
                x = int(window_size[0] / 2) - int(w / 2)
                y = int(window_size[1] / 2) - int(h / 2)
                yy = y
                self.y = y
                self.x = x
            else:
                yy = self.y
                y = self.y
                x = self.x
            ddt.rect_a((x - 2 * gui.scale, y - 2 * gui.scale), (w + 4 * gui.scale, h + 4 * gui.scale),
                       colours.box_border)
            ddt.rect_a((x, y), (w, h), colours.box_background)
            ddt.text_background_colour = colours.box_background
            if key_esc_press or (gui.level_2_click and not coll((x, y, w, h))):
                self.active = False

            if self.add_mode:
                ddt.text((x + 10 * gui.scale, yy + 8 * gui.scale,), _("Add Station"), colours.box_title_text, 213)
            else:
                ddt.text((x + 10 * gui.scale, yy + 8 * gui.scale,), _("Edit Station"), colours.box_title_text, 213)

            self.saved()
            return


        w = round(510 * gui.scale)
        h = round(356 * gui.scale)  # + sh
        x = int(window_size[0] / 2) - int(w / 2)
        y = int(window_size[1] / 2) - int(h / 2)

        self.w = w
        self.h = h
        self.x = x
        self.y = y

        yy = y

        ddt.rect_a((x - 2 * gui.scale, y - 2 * gui.scale), (w + 4 * gui.scale, h + 4 * gui.scale), colours.box_border)
        ddt.rect_a((x, y), (w, h), colours.box_background)

        ddt.text_background_colour = colours.box_background

        if key_esc_press or (gui.level_2_click and not coll((x, y, w, h))):
            self.active = False

        ddt.text((x + 10 * gui.scale, yy + 8 * gui.scale,), _("Station Browser"), colours.box_title_text, 213)

        # ---
        if self.load_connecting:
            ddt.text((x + 495 * gui.scale, yy + 8 * gui.scale, 1), _("Connecting..."), colours.box_title_text,
                     311)
        elif self.load_failed:
            ddt.text((x + 495 * gui.scale, yy + 8 * gui.scale, 1), _("Failed to connect!"), colours.box_title_text,
                     311)
            if self.load_failed_timer.get() > 3:
                gui.delay_frame(0.2)
                self.load_failed = False

        elif self.searching:
            ddt.text((x + 495 * gui.scale, yy + 8 * gui.scale, 1), _("Searching..."), colours.box_title_text,
                     311)
        elif pctl.playing_state == 3:

            text = ""
            if tauon.stream_proxy.s_format:
                text = str(tauon.stream_proxy.s_format)
            if tauon.stream_proxy.s_bitrate and tauon.stream_proxy.s_bitrate.isnumeric():
                text += " " + tauon.stream_proxy.s_bitrate + "kbps"

            ddt.text((x + 495 * gui.scale, yy + 8 * gui.scale, 1), text, colours.box_title_text, 311)
            # if tauon.stream_proxy.s_format:
            #     ddt.text((x + 425 * gui.scale, yy + 8 * gui.scale,), tauon.stream_proxy.s_format, colours.box_title_text, 311)
            # if tauon.stream_proxy.s_bitrate:
            #     ddt.text((x + 454 * gui.scale, yy + 8 * gui.scale,), tauon.stream_proxy.s_bitrate + "kbps", colours.box_title_text, 311)

        # --- ----------------------------------------------------------------------
        if self.tab == 1:
            self.search_page()
        elif self.tab == 0:
            self.saved()
        self.draw_list()
        #self.footer()
        return

    def saved(self):
        y = self.y
        x = self.x
        w = self.w
        h = self.h

        yy = y + round(40 * gui.scale)

        width = round(370 * gui.scale)

        rect = (x + 8 * gui.scale, yy - round(2 * gui.scale), width, 22 * gui.scale)
        fields.add(rect)
        if (coll(rect) and gui.level_2_click) or (inp.key_tab_press and self.radio_field_active == 2):
            self.radio_field_active = 1
            inp.key_tab_press = False
        if not self.radio_field_title.text and not (self.radio_field_active == 1 and editline):
            ddt.text((x + 14 * gui.scale, yy), _("Name / Title"), colours.box_text_label, 312)
        self.radio_field_title.draw(x + 14 * gui.scale, yy, colours.box_input_text,
                                    active=self.radio_field_active == 1,
                                    width=width, click=gui.level_2_click)

        ddt.rect_s(rect, colours.box_text_border, 1 * gui.scale)

        yy += round(30 * gui.scale)

        rect = (x + 8 * gui.scale, yy - round(2 * gui.scale), width, 22 * gui.scale)
        ddt.rect_s(rect, colours.box_text_border, 1 * gui.scale)
        fields.add(rect)
        if (coll(rect) and gui.level_2_click) or (inp.key_tab_press and self.radio_field_active == 1):
            self.radio_field_active = 2
            inp.key_tab_press = False

        if not self.radio_field.text and not (self.radio_field_active == 2 and editline):
            ddt.text((x + 14 * gui.scale, yy), _("Raw Stream URL http://example.stream:1234"), colours.box_text_label, 312)
        self.radio_field.draw(x + 14 * gui.scale, yy, colours.box_input_text, active=self.radio_field_active == 2,
                              width=width, click=gui.level_2_click)

        if draw.button(_("Save"), x + width + round(21 * gui.scale), yy - round(20 * gui.scale), press=gui.level_2_click):

            if not self.radio_field.text:
                show_message(_("Enter a stream URL"))
            elif "http://" in self.radio_field.text or "https://" in self.radio_field.text:
                radio = self.station_editing
                if self.add_mode:
                    radio = {}
                radio["title"] = self.radio_field_title.text
                radio["stream_url"] = self.radio_field.text
                radio["website_url"] = ""

                if self.add_mode:
                    pctl.radio_playlists[pctl.radio_playlist_viewing]["items"].append(radio)
                self.active = False

            else:
                show_message(_("Could not validate URL. Must start with https:// or http://"))

    def draw_list(self):

        x = self.x
        y = self.y
        w = self.w
        h = self.h

        if self.drag:
            gui.update_on_drag = True

        yy = y + round(100 * gui.scale)
        x += round(10 * gui.scale)

        radio_list = prefs.radio_urls
        if self.tab == 1:
            radio_list = self.temp_list

        rect = (x, y, w, h)
        if coll(rect):
            self.scroll_position += mouse_wheel * -1
        self.scroll_position = max(self.scroll_position, 0)
        self.scroll_position = min(self.scroll_position, len(radio_list) // 2 - 7)

        if len(radio_list) // 2 > 9:
            self.scroll_position = self.scroll.draw((x + w) - round(35 * gui.scale), yy, round(15 * gui.scale), round(210 * gui.scale), self.scroll_position, len(radio_list) // 2 - 7, True, click=gui.level_2_click)

        self.scroll_position = max(self.scroll_position, 0)

        p = self.scroll_position * 2
        offset = 0
        to_delete = None
        swap = None

        while True:

            if p > len(radio_list) - 1:
                break

            xx = x + offset
            item = radio_list[p]

            rect = (xx, yy, round(233 * gui.scale), round(40 * gui.scale))
            fields.add(rect)

            bg = colours.box_background
            text_colour = colours.box_input_text

            playing = pctl.playing_state == 3 and self.loaded_url == item["stream_url"]

            if playing:
                # bg = colours.box_sub_highlight
                # ddt.rect(rect, bg, True)

                bg = colours.tab_background_active
                text_colour = colours.tab_text_active
                ddt.rect(rect, bg)

            if radio_view.drag:
                if item == radio_view.drag:
                    text_colour = colours.box_sub_text
                    bg = [255, 255, 255, 10]
                    ddt.rect(rect, bg)
            elif (radio_entry_menu.active and radio_entry_menu.reference == p) or \
                (not radio_entry_menu.active and coll(rect)) and not playing:
                text_colour = colours.box_sub_text
                bg = [255, 255, 255, 10]
                ddt.rect(rect, bg)

            if coll(rect):

                if gui.level_2_click:
                    # self.drag = p
                    # self.click_point = copy.copy(mouse_position)
                    radio_view.drag = item
                    radio_view.click_point = copy.copy(mouse_position)
                if mouse_up: #gui.level_2_click:
                    gui.update += 1
                    # if self.drag is not None and p != self.drag:
                    #     swap = p
                    if point_proximity_test(radio_view.click_point, mouse_position, round(4 * gui.scale)):
                        self.start(item)
                if middle_click:
                    to_delete = p
                if level_2_right_click:
                    self.right_clicked_station = item
                    self.right_clicked_station_p = p
                    radio_entry_menu.activate(item)

            bg = alpha_blend(bg, colours.box_background)

            boxx = round(32 * gui.scale)
            toff = boxx + round(10 * gui.scale)
            if item["title"]:
                ddt.text((xx + toff, yy + round(3 * gui.scale)), item["title"], text_colour, 212, bg=bg, max_w=rect[2] - (15 * gui.scale + toff))
            else:
                ddt.text((xx + toff, yy + round(3 * gui.scale)), item["stream_url"], text_colour, 212, bg=bg, max_w=rect[2] - (15 * gui.scale + toff))

            country = item.get("country")
            if country:
                ddt.text((xx + toff, yy + round(18 * gui.scale)), country, text_colour, 11, bg=bg, max_w=rect[2] - (15 * gui.scale + toff))


            b_rect = (xx + round(4 * gui.scale), yy + round(4 * gui.scale), boxx, boxx)
            ddt.rect(b_rect, colours.box_thumb_background)
            radio_thumb_gen.draw(item, b_rect[0], b_rect[1], b_rect[2])

            if offset == 0:
                offset = rect[2] + round(4 * gui.scale)
            else:
                offset = 0
                yy += round(43 * gui.scale)

            if yy > y + 300 * gui.scale:
                break

            p += 1

        # if to_delete is not None:
        #     del radio_list[to_delete]
        #
        # if mouse_up and self.drag and mouse_position[1] > yy + round(22 * gui.scale):
        #     swap = len(radio_list)

        # if self.drag and not point_proximity_test(self.click_point, mouse_position, round(4 * gui.scale)):
        #     ddt.rect((
        #              mouse_position[0] + round(8 * gui.scale), mouse_position[1] - round(8 * gui.scale), 45 * gui.scale,
        #              13 * gui.scale), colours.grey(70))

        # if swap is not None:
        #
        #     old = radio_list[self.drag]
        #     radio_list[self.drag] = None
        #
        #     if swap > self.drag:
        #         swap += 1
        #
        #     radio_list.insert(swap, old)
        #     radio_list.remove(None)
        #
        #     self.drag = None
        #     gui.update += 1

        # if not mouse_down:
        #     self.drag = None


    def footer(self):

        y = self.y
        x = self.x + round(15 * gui.scale)
        w = self.w
        h = self.h

        yy = y + round(328 * gui.scale)
        if pctl.playing_state == 3 and not prefs.auto_rec:
            old = prefs.auto_rec
            if not old and pref_box.toggle_square(x, yy, prefs.auto_rec, _("Record and auto split songs"),
                                                  click=gui.level_2_click):
                show_message(_("Please stop playback first before toggling this setting"))
        else:
            if pctl.playing_state == 3:
                old = prefs.auto_rec
                if old and not pref_box.toggle_square(x, yy, prefs.auto_rec, _("Record and auto split songs"), click=gui.level_2_click):
                    show_message(_("Please stop playback first to end current recording"))

            else:
                old = prefs.auto_rec
                prefs.auto_rec = pref_box.toggle_square(x, yy, prefs.auto_rec, _("Record and auto split songs"), click=gui.level_2_click)
                if prefs.auto_rec != old and prefs.auto_rec:
                    show_message(_("Tracks will now be recorded."), _("Tip: You can press F9 to view the output folder."), mode="info")


        if self.tab == 0:
            if draw.button(_("Browse"), (x + w) - round(130 * gui.scale), yy - round(3 * gui.scale),
                           press=gui.level_2_click, w=round(100 * gui.scale)):
                self.tab = 1
        elif self.tab == 1:
            if draw.button(_("Saved"), (x + w) - round(130 * gui.scale), yy - round(3 * gui.scale),
                           press=gui.level_2_click, w=round(100 * gui.scale)):
                self.tab = 0
        gui.level_2_click = False


radiobox = RadioBox()
tauon.radiobox = radiobox
tauon.dummy_track = radiobox.dummy_track

# def visit_radio_site_show_test(p):
#     return "website_url" in prefs.radio_urls[p] and prefs.radio_urls[p]["website_url"]
#

def visit_radio_site_deco(item):

    if "website_url" in item and item["website_url"]:
        return [colours.menu_text, colours.menu_background, None]
    else:
        return [colours.menu_text_disabled, colours.menu_background, None]

def visit_radio_station_site_deco(item):
    return visit_radio_site_deco(item[1])

def visit_radio_site(item):
    if "website_url" in item and item["website_url"]:
        webbrowser.open(item["website_url"], new=2, autoraise=True)

def visit_radio_station(item):
    visit_radio_site(item[1])

def radio_saved_panel_test(_):
    return radiobox.tab == 0


def save_to_radios(item):
    pctl.radio_playlists[pctl.radio_playlist_viewing]["items"].append(item)
    toast(_("Added station to: ") + pctl.radio_playlists[pctl.radio_playlist_viewing]["name"])

radio_entry_menu.add(_("Visit Website"), visit_radio_site, visit_radio_site_deco, pass_ref=True, pass_ref_deco=True)
radio_entry_menu.add(_("Save"), save_to_radios, pass_ref=True)

class RenamePlaylistBox:

    def __init__(self):

        self.x = 300
        self.y = 300
        self.playlist_index = 0

        self.edit_generator = False


    def toggle_edit_gen(self):

        self.edit_generator ^= True
        if self.edit_generator:

            if len(rename_text_area.text) > 0:
                pctl.multi_playlist[self.playlist_index][0] = rename_text_area.text

            pl = self.playlist_index
            id = pl_to_id(pl)

            text = pctl.gen_codes.get(id)
            if not text:
                text = ""

            rename_text_area.set_text(text)
            rename_text_area.highlight_none()

            gui.regen_single = rename_playlist_box.playlist_index
            tm.ready("worker")


        else:
            rename_text_area.set_text(pctl.multi_playlist[self.playlist_index][0])
            rename_text_area.highlight_none()
            # rename_text_area.highlight_all()

    def render(self):

        if gui.level_2_click:
            inp.mouse_click = True
        gui.level_2_click = False

        if inp.key_tab_press:
            self.toggle_edit_gen()

        text_w = ddt.get_text_w(rename_text_area.text, 315)
        min_w = max(250 * gui.scale, text_w + 50 * gui.scale)

        rect = [self.x, self.y, min_w, 37 * gui.scale]
        bg = [40, 40, 40, 255]
        if self.edit_generator:
            bg = [70, 50, 100, 255]
        ddt.text_background_colour = bg

        # Draw background
        ddt.rect(rect, bg)

        # Draw text entry
        rename_text_area.draw(rect[0] + 10 * gui.scale, rect[1] + 8 * gui.scale, colours.alpha_grey(250),
                              width=350 * gui.scale, font=315)

        # Draw accent
        rect2 = [self.x, self.y + rect[3] - 4 * gui.scale, min_w, 4 * gui.scale]
        ddt.rect(rect2, [255, 255, 255, 60])

        if self.edit_generator:
            pl = self.playlist_index
            id = pl_to_id(pl)
            pctl.gen_codes[id] = rename_text_area.text

            if input_text or key_backspace_press:
                gui.regen_single = rename_playlist_box.playlist_index
                tm.ready("worker")

                #regenerate_playlist(rename_playlist_box.playlist_index)
            # if gui.gen_code_errors:
            #     del_icon.render(rect[0] + rect[2] - 21 * gui.scale, rect[1] + 10 * gui.scale, (255, 70, 70, 255))
            ddt.text_background_colour = [4, 4, 4, 255]
            hint_rect = [rect[0], rect[1] + round(50 * gui.scale), round(560 * gui.scale), round(300 * gui.scale)]

            if hint_rect[0] + hint_rect[2] > window_size[0]:
                hint_rect[0] = window_size[0] - hint_rect[2]

            ddt.rect(hint_rect, [0, 0, 0, 245])
            xx0 = hint_rect[0] + round(15 * gui.scale)
            xx = hint_rect[0] + round(25 * gui.scale)
            xx2 = hint_rect[0] + round(85 * gui.scale)
            yy = hint_rect[1] + round(10 * gui.scale)

            text_colour =  [150, 150, 150, 255]
            title_colour =  text_colour
            code_colour =  [250, 250, 250, 255]
            hint_colour =  [110, 110, 110, 255]

            title_font = 311
            code_font = 311
            hint_font = 310

            #ddt.pretty_rect = hint_rect

            ddt.text((xx0, yy), "Type codes separated by spaces. Codes will be executed left to right.", text_colour, title_font)
            yy += round(18 * gui.scale)
            ddt.text((xx0, yy), "Select sources: (default: all playlists)", title_colour, title_font)
            yy += round(14 * gui.scale)
            ddt.text((xx, yy), "s\"name\"", code_colour, code_font)
            ddt.text((xx2, yy), "Select source playlist by name", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "self", code_colour, code_font)
            ddt.text((xx2, yy), "Select playlist itself", hint_colour, hint_font)

            yy += round(16 * gui.scale)
            ddt.text((xx0, yy), "Add tracks from sources: (at least 1 required)", title_colour, title_font)
            yy += round(14 * gui.scale)

            ddt.text((xx, yy), "a\"name\"", code_colour, code_font)
            ddt.text((xx2, yy), "Search artist name", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "g\"genre\"", code_colour, code_font)
            ddt.text((xx2, yy), "Search genre", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "p\"text\"", code_colour, code_font)
            ddt.text((xx2, yy), "Search filepath segment", hint_colour, hint_font)

            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "f\"terms\"", code_colour, code_font)
            ddt.text((xx2, yy), "Find / Search", hint_colour, hint_font)


            # yy += round(12 * gui.scale)
            # ddt.text((xx, yy), "ext\"flac\"", code_colour, code_font)
            # ddt.text((xx2, yy), "Search by file type", hint_colour, hint_font)

            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "a", code_colour, code_font)
            ddt.text((xx2, yy), "Add all tracks", hint_colour, hint_font)

            yy += round(16 * gui.scale)
            ddt.text((xx0, yy), "Filters", title_colour, title_font)
            yy += round(14 * gui.scale)
            ddt.text((xx, yy), "n123", code_colour, code_font)
            ddt.text((xx2, yy), "Limit to number of tracks", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "y>1999", code_colour, code_font)
            ddt.text((xx2, yy), "Year: >, <, =", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "pc>5", code_colour, code_font)
            ddt.text((xx2, yy), "Play count: >, <", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "d>120", code_colour, code_font)
            ddt.text((xx2, yy), "Duration (seconds): >, <", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "rat>3.5", code_colour, code_font)
            ddt.text((xx2, yy), "Track rating 0-5: >, <, =", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "l", code_colour, code_font)
            ddt.text((xx2, yy), "Loved tracks", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "ly", code_colour, code_font)
            ddt.text((xx2, yy), "Has lyrics", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "ff\"terms\"", code_colour, code_font)
            ddt.text((xx2, yy), "Search and keep", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "fx\"terms\"", code_colour, code_font)
            ddt.text((xx2, yy), "Search and exclude", hint_colour, hint_font)

            # yy += round(12 * gui.scale)
            # ddt.text((xx, yy), "com\"text\"", code_colour, code_font)
            # ddt.text((xx2, yy), "Search in comment", hint_colour, hint_font)
            #yy += round(12 * gui.scale)


            xx += round(260 * gui.scale)
            xx2 += round(260 * gui.scale)
            xx0 += round(260 * gui.scale)
            yy = hint_rect[1] + round(10 * gui.scale)
            yy += round(18 * gui.scale)

            #yy += round(16 * gui.scale)
            ddt.text((xx0, yy), "Sorters", title_colour, title_font)
            yy += round(14 * gui.scale)

            ddt.text((xx, yy), "st", code_colour, code_font)
            ddt.text((xx2, yy), "Shuffle tracks", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "ra", code_colour, code_font)
            ddt.text((xx2, yy), "Shuffle albums", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "y>", code_colour, code_font)
            ddt.text((xx2, yy), "Year: >, <", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "d>", code_colour, code_font)
            ddt.text((xx2, yy), "Duration: >, <", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "pt>", code_colour, code_font)
            ddt.text((xx2, yy), "Track Playtime: >, <", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "pa>", code_colour, code_font)
            ddt.text((xx2, yy), "Album playtime: >, <", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "rv", code_colour, code_font)
            ddt.text((xx2, yy), "Invert tracks", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "rva", code_colour, code_font)
            ddt.text((xx2, yy), "Invert albums", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "rat>", code_colour, code_font)
            ddt.text((xx2, yy), "Track rating: >, <", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "rata>", code_colour, code_font)
            ddt.text((xx2, yy), "Album rating: >, <", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "m>", code_colour, code_font)
            ddt.text((xx2, yy), "Modification date: >, <", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "path", code_colour, code_font)
            ddt.text((xx2, yy), "Filepath", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "tn", code_colour, code_font)
            ddt.text((xx2, yy), "Track number per album", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "ypa", code_colour, code_font)
            ddt.text((xx2, yy), "Year per artist", hint_colour, hint_font)
            yy += round(12 * gui.scale)
            ddt.text((xx, yy), "\"artist\">", code_colour, code_font)
            ddt.text((xx2, yy), "Sort by column name: >, <", hint_colour, hint_font)


            yy += round(16 * gui.scale)
            ddt.text((xx0, yy), "Special", title_colour, title_font)
            yy += round(14 * gui.scale)
            ddt.text((xx, yy), "auto", code_colour, code_font)
            ddt.text((xx2, yy), "Automatically reload on imports", hint_colour, hint_font)

            yy += round(24 * gui.scale)
            #xx += round(80 * gui.scale)
            xx2 = xx
            xx2 += ddt.text((xx2, yy), "Status:", [90, 90, 90, 255], 212) + round(6 * gui.scale)
            if rename_text_area.text:
                if gui.gen_code_errors:
                    if gui.gen_code_errors == "playlist":
                        ddt.text((xx2, yy), "Playlist not found", [255, 100, 100, 255], 212)
                    elif gui.gen_code_errors == "empty":
                        ddt.text((xx2, yy), "Result is empty", [250, 190, 100, 255], 212)
                    elif gui.gen_code_errors == "close":
                        ddt.text((xx2, yy), "Close quotation...", [110, 110, 110, 255], 212)
                    else:
                        ddt.text((xx2, yy), "...", [255, 100, 100, 255], 212)
                else:
                    ddt.text((xx2, yy), "OK", [100, 255, 100, 255], 212)
            else:
                ddt.text((xx2, yy), "Disabled", [110, 110, 110, 255], 212)

        #ddt.pretty_rect = None

        # If enter or click outside of box: save and close
        if inp.key_return_press or (key_esc_press and len(editline) == 0) \
                or ((inp.mouse_click or level_2_right_click) and not coll(rect)):
            gui.rename_playlist_box = False

            if self.edit_generator:
                pass
            else:
                if len(rename_text_area.text) > 0:
                    if gui.radio_view:
                        pctl.radio_playlists[self.playlist_index]["name"] = rename_text_area.text
                    else:
                        pctl.multi_playlist[self.playlist_index][0] = rename_text_area.text
            inp.key_return_press = False


rename_playlist_box = RenamePlaylistBox()


class PlaylistBox:

    def __init__(self):

        self.scroll_on = prefs.old_playlist_box_position
        self.drag = False
        self.drag_source = 0
        self.drag_on = -1

        self.adds = []

        self.indicate_w = round(2 * gui.scale)

        self.lock_icon = asset_loader('lock-corner.png', True)
        self.pin_icon = asset_loader('dia-pin.png', True)
        self.gen_icon = asset_loader('gen-gear.png', True)

        #if gui.scale == 1.25:
        self.tab_h = round(25 * gui.scale)
        self.gap = round(2 * gui.scale)

        self.text_offset = 2 * gui.scale
        if gui.scale == 1.25:
            self.text_offset = 3


    def draw(self, x, y, w, h):

        global quick_drag

        #ddt.rect_r((x, y, w, h), colours.side_panel_background, True)
        ddt.rect((x, y, w, h), colours.playlist_box_background)
        ddt.text_background_colour = colours.playlist_box_background

        max_tabs = (h - 10 * gui.scale) // (self.gap + self.tab_h)

        tab_title_colour = [230, 230, 230, 255]

        bg_lumi = test_lumi(colours.playlist_box_background)
        light_mode = False

        if bg_lumi < 0.55:
            light_mode = True
            tab_title_colour = [20, 20, 20, 255]

        dark_mode = False
        if bg_lumi > 0.8:
            dark_mode = True

        if light_mode:
            indicate_w = round(3 * gui.scale)
        else:
            indicate_w = round(2 * gui.scale)

        show_scroll = False
        tab_start = x + 10 * gui.scale

        if window_size[0] < 700 * gui.scale:
            tab_start = x + 4 * gui.scale

        if mouse_wheel != 0 and coll((x, y, w, h)):
            self.scroll_on -= mouse_wheel

        if self.scroll_on > len(pctl.multi_playlist) - max_tabs + 1:
            self.scroll_on = len(pctl.multi_playlist) - max_tabs + 1

        if self.scroll_on < 0:
            self.scroll_on = 0

        if len(pctl.multi_playlist) > max_tabs:
            show_scroll = True
        else:
            self.scroll_on = 0


        if show_scroll:
            tab_start += 15 * gui.scale

        if colours.lm:
            w -= round(6 * gui.scale)
        tab_width = w - tab_start # - 0 * gui.scale



        # Draw scroll bar
        if show_scroll:
            self.scroll_on = playlist_panel_scroll.draw(x + 2, y + 1, 15 * gui.scale, h, self.scroll_on, len(pctl.multi_playlist) - max_tabs + 1)

        draw_pin_indicator = False #prefs.tabs_on_top

        # if not gui.album_tab_mode:
        #     if key_left_press or key_right_press:
        #         if pctl.active_playlist_viewing < self.scroll_on:
        #             self.scroll_on = pctl.active_playlist_viewing
        #         elif pctl.active_playlist_viewing + 1 > self.scroll_on + max_tabs:
        #             self.scroll_on = (pctl.active_playlist_viewing - max_tabs) + 1

        # Process inputs
        delete_pl = None
        tab_on = 0
        yy = y + 5 * gui.scale
        for i, pl in enumerate(pctl.multi_playlist):

            if tab_on >= max_tabs:
                break
            if i < self.scroll_on:
                continue

            # if not pl[8] and i in tabs_on_top:
            #     continue

            tab_on += 1

            if coll((tab_start, yy - 1, tab_width, (self.tab_h + 1))):
                if right_click:
                    if gui.radio_view:
                        radio_tab_menu.activate(i, mouse_position)
                    else:
                        tab_menu.activate(i, mouse_position)
                    gui.tab_menu_pl = i

                if tab_menu.active is False and middle_click:
                    delete_pl = i
                    #delete_playlist(i)
                    #break

                if mouse_up and self.drag and coll_point(mouse_up_position, (tab_start, yy - 1, tab_width, (self.tab_h + 1))):

                    # If drag from top bar to side panel, make hidden
                    if self.drag_source == 0 and prefs.drag_to_unpin:
                        pctl.multi_playlist[self.drag_on][8] = True

                    # Move playlist tab
                    if i != self.drag_on and not point_proximity_test(gui.drag_source_position, mouse_position, 10 * gui.scale):
                        if key_shift_down:
                            pctl.multi_playlist[i][2] += pctl.multi_playlist[self.drag_on][2]
                            delete_playlist(self.drag_on)
                        else:
                            move_playlist(self.drag_on, i)

                    gui.update += 1

                # Double click to play
                if mouse_up and pl_to_id(i) == top_panel.tab_d_click_ref == pl_to_id(pctl.active_playlist_viewing) and \
                    top_panel.tab_d_click_timer.get() < 0.25 and point_distance(last_click_location, mouse_up_position) < 5 * gui.scale:

                    if pctl.playing_state == 2 and pctl.active_playlist_playing == i:
                        pctl.play()
                    elif pctl.selected_ready() and (pctl.playing_state != 1 or pctl.active_playlist_playing != i):
                        pctl.jump(default_playlist[playlist_selected], pl_position=playlist_selected)
                if mouse_up:
                    top_panel.tab_d_click_timer.set()
                    top_panel.tab_d_click_ref = pl_to_id(i)



                if not draw_pin_indicator:
                    if inp.mouse_click:
                        switch_playlist(i)
                        self.drag_on = i
                        self.drag = True
                        self.drag_source = 1
                        set_drag_source()

                # Process input of dragging tracks onto tab
                if quick_drag is True and mouse_up:
                    top_panel.tab_d_click_ref = -1
                    top_panel.tab_d_click_timer.force_set(100)
                    if not (pctl.gen_codes.get(pl_to_id(i)) and "self" not in pctl.gen_codes[pl_to_id(i)]):
                        quick_drag = False
                        modified = False
                        gui.pl_update += 1

                        for item in shift_selection:
                            pctl.multi_playlist[i][2].append(default_playlist[item])
                            modified = True
                        if len(shift_selection) > 0:
                            self.adds.append([pctl.multi_playlist[i][6], len(shift_selection), Timer()]) # ID, num, timer
                            modified = True
                        if modified:
                            pctl.after_import_flag = True
                            tm.ready("worker")
                            pctl.notify_change()
                            pctl.update_shuffle_pool(pctl.multi_playlist[i][6], shift_selection)
                            tree_view_box.clear_target_pl(i)

            # Toggle hidden flag on click
            if draw_pin_indicator and inp.mouse_click and coll((tab_start + 5 * gui.scale, yy + 3 * gui.scale, 25 * gui.scale , 26 * gui.scale)):
                pl[8] ^= True

            yy += self.tab_h + self.gap

        # Draw tabs
        #delete_pl = None
        tab_on = 0
        yy = y + 5 * gui.scale
        for i, pl in enumerate(pctl.multi_playlist):

            # if yy + self.tab_h > y + h:
            #     break
            if tab_on >= max_tabs:
                break
            if i < self.scroll_on:
                continue

            tab_on += 1

            name = pl[0]
            hidden = pl[8]

            # Background is insivible by default (for hightlighting if selected)
            bg = [0, 0, 0, 0]

            # Highlight if playlist selected (viewing)
            if i == pctl.active_playlist_viewing or (tab_menu.active and tab_menu.reference == i):
                #bg = [255, 255, 255, 25]

                # Adjust highlight for different background brightnesses
                bg = rgb_add_hls(colours.playlist_box_background, 0, 0.06, 0)
                if light_mode:
                    bg = [0, 0, 0, 25]

            # Highlight target playlist when tragging tracks over
            if coll((tab_start + 50 * gui.scale, yy - 1, tab_width - 50 * gui.scale, (self.tab_h + 1))) and quick_drag and not (pctl.gen_codes.get(pl_to_id(i)) and "self" not in pctl.gen_codes[pl_to_id(i)]):
                #bg = [255, 255, 255, 15]
                bg = rgb_add_hls(colours.playlist_box_background, 0, 0.04, 0)
                if light_mode:
                    bg = [0, 0, 0, 16]

            # Get actual bg from blend for text bg
            real_bg = alpha_blend(bg, colours.playlist_box_background)

            # Draw highlight
            ddt.rect((tab_start, yy - round(1 * gui.scale), tab_width, self.tab_h), bg)

            # Draw title text
            text_start = 10 * gui.scale
            if draw_pin_indicator:
                #text_start = 40 * gui.scale
                text_start = 32 * gui.scale

            if not pl[8] and prefs.tabs_on_top:
                cl = [255, 255, 255, 25]

                if light_mode:
                    cl = [0, 0, 0, 40]

                xx = tab_start + tab_width - self.lock_icon.w
                self.lock_icon.render(xx, yy, cl)


            text_max_w = tab_width - text_start - 15 * gui.scale
            # if indicator_run_x:
            #     text_max_w = tab_width - (indicator_run_x + text_start + 17 * gui.scale + slide)
            ddt.text((tab_start + text_start, yy + self.text_offset), name, tab_title_colour, 211, max_w=text_max_w,
                     bg=real_bg)

            # Is mouse collided with tab?
            hit = coll((tab_start + 50 * gui.scale, yy - 1, tab_width - 50 * gui.scale, (self.tab_h + 1)))

            #if not prefs.tabs_on_top:
            if i == pctl.active_playlist_playing:

                indicator_colour = colours.title_playing
                if colours.lm:
                    indicator_colour = colours.seek_bar_fill

                ddt.rect((tab_start + 0 - 2 * gui.scale, yy - round(1 * gui.scale), indicate_w, self.tab_h),
                         indicator_colour)


            # # If mouse over...
            if hit:
                # Draw indicator for dragging tracks
                if quick_drag and pl_is_mut(i):
                    ddt.rect((tab_start + tab_width - self.indicate_w, yy, self.indicate_w, self.tab_h),
                             [80, 200, 180, 255])

                # Draw indicators for moving tab
                if self.drag and i != self.drag_on and not point_proximity_test(gui.drag_source_position, mouse_position, 10 * gui.scale):
                    if key_shift_down:
                        ddt.rect(
                            (tab_start + tab_width - 4 * gui.scale, yy, self.indicate_w, self.tab_h),
                            [80, 160, 200, 255])
                    else:
                        if i < self.drag_on:
                            ddt.rect((tab_start, yy - self.indicate_w, tab_width, self.indicate_w), [80, 160, 200, 255])
                        else:
                            ddt.rect((tab_start, yy + (self.tab_h - self.indicate_w), tab_width, self.indicate_w),
                                     [80, 160, 200, 255])

            elif quick_drag and not point_proximity_test(gui.drag_source_position, mouse_position, 15 * gui.scale):
                for item in shift_selection:
                    if default_playlist[item] in pl[2]:
                        ddt.rect((tab_start + tab_width - self.indicate_w, yy, self.indicate_w, self.tab_h),
                                 [190, 170, 20, 255])
                        break
            # Drag red line highlight if playlist is generator playlist
            if quick_drag and not point_proximity_test(gui.drag_source_position, mouse_position, 15 * gui.scale):
                if not pl_is_mut(i):
                    ddt.rect((tab_start + tab_width - self.indicate_w, yy, self.indicate_w, self.tab_h),
                             [200, 70, 50, 255])

# Draw effect of adding tracks to playlist
            if len(self.adds) > 0:
                for k in reversed(range(len(self.adds))):
                    if pctl.multi_playlist[i][6] == self.adds[k][0]:
                        if self.adds[k][2].get() > 0.3:
                            del self.adds[k]
                        else:
                            ay = yy + 4 * gui.scale
                            ay -= 6 * gui.scale * self.adds[k][2].get() / 0.3

                            ddt.text((tab_start + tab_width - 10 * gui.scale, int(round(ay)), 1), '+' + str(self.adds[k][1]), colours.pluse_colour, 212, bg=real_bg)
                            gui.update += 1

                            ddt.rect((tab_start + tab_width, yy, self.indicate_w, self.tab_h - self.indicate_w),
                                     [244, 212, 66, int(255 * self.adds[k][2].get() / 0.3) * -1])


            yy += self.tab_h + self.gap

        if delete_pl is not None:
            #delete_playlist(delete_pl)
            delete_playlist_ask(delete_pl)
            gui.update += 1

        # Create new playlist if drag in blank space after tabs
        rect = (x, yy, w - 10 * gui.scale, h - (yy - y))
        fields.add(rect)

        if coll(rect):
            if quick_drag:
                ddt.rect((tab_start, yy, tab_width, self.indicate_w), [80, 160, 200, 255])
                if mouse_up:
                    drop_tracks_to_new_playlist(shift_selection)

            if right_click:
                extra_tab_menu.activate(pctl.active_playlist_viewing)

            # Move tab to end playlist if dragged past end
            if self.drag:
                if mouse_up:
                    if key_ctrl_down:
                        # Duplicate playlist on ctrl
                        gen_dupe(playlist_box.drag_on)
                        gui.update += 2
                        self.drag = False
                    else:
                        # If drag from top bar to side panel, make hidden
                        if self.drag_source == 0 and prefs.drag_to_unpin:
                            pctl.multi_playlist[self.drag_on][8] = True

                        move_playlist(self.drag_on, i)
                        gui.update += 2
                        self.drag = False
                else:
                    if key_ctrl_down:
                        ddt.rect((tab_start, yy, tab_width, self.indicate_w), [255, 190, 0, 255])
                    else:
                        ddt.rect((tab_start, yy, tab_width, self.indicate_w), [80, 160, 200, 255])

playlist_box = PlaylistBox()


def create_artist_pl(artist, replace=False):

    source_pl = pctl.active_playlist_viewing
    this_pl = pctl.active_playlist_viewing

    if pctl.multi_playlist[source_pl][10]:
        if pctl.multi_playlist[source_pl][0].startswith("Artist:"):
            new = id_to_pl(pctl.multi_playlist[source_pl][10])
            if new is None:
                # The original playlist is now gone
                pctl.multi_playlist[source_pl][10] = ""
            else:
                source_pl = new
                #replace = True


    playlist = []

    for item in pctl.multi_playlist[source_pl][2]:
        track = pctl.g(item)
        if track.artist == artist or track.album_artist == artist:
            playlist.append(item)

    if replace:
        pctl.multi_playlist[this_pl][2][:] = playlist[:]
        pctl.multi_playlist[this_pl][0] = "Artist: " + artist
        if album_mode:
            reload_albums()

        # Transfer playing track back to original playlist
        if pctl.multi_playlist[this_pl][10]:
            new = id_to_pl(pctl.multi_playlist[this_pl][10])
            tr = pctl.playing_object()
            if new is not None and tr and pctl.active_playlist_playing == this_pl:
                if tr.index not in pctl.multi_playlist[this_pl][2] and tr.index in pctl.multi_playlist[source_pl][2]:
                    print("Transfer back playing")
                    pctl.active_playlist_playing = source_pl
                    pctl.playlist_playing_position = pctl.multi_playlist[source_pl][2].index(tr.index)


    else:

        pctl.multi_playlist.append(pl_gen(title="Artist: " + artist,
                                          playlist=playlist,
                                          hide_title=0,
                                          parent=pl_to_id(pctl.active_playlist_viewing)))

        pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[source_pl][0] + "\" a\"" + artist + "\""

        switch_playlist(len(pctl.multi_playlist) - 1)

artist_list_menu.add(_("Filter to New Playlist"), create_artist_pl, pass_ref=True, icon=filter_icon)

artist_list_menu.add_sub(_("View..."), 140)

def aa_sort_alpha():
    prefs.artist_list_sort_mode = "alpha"
    artist_list_box.saves.clear()
def aa_sort_popular():
    prefs.artist_list_sort_mode = "popular"
    artist_list_box.saves.clear()
def aa_sort_play():
    prefs.artist_list_sort_mode = "play"
    artist_list_box.saves.clear()

def toggle_artist_list_style():
    if prefs.artist_list_style == 1:
        prefs.artist_list_style = 2
    else:
        prefs.artist_list_style = 1

artist_list_menu.add_to_sub(_("Sort Alphabetically"), 0, aa_sort_alpha)
artist_list_menu.add_to_sub(_("Sort by Popularity"), 0, aa_sort_popular)
artist_list_menu.add_to_sub(_("Sort by Playtime"), 0, aa_sort_play)
artist_list_menu.add_to_sub(_("Toggle Thumbnails"), 0, toggle_artist_list_style)


def verify_discogs():
    return len(prefs.discogs_pat) == 40

def save_discogs_artist_thumb(artist, filepath):

    print("Searching discogs for artist image...")

    # Make artist name url safe
    artist = artist.replace("/", "").replace("\\", "").replace(":", "")

    # Search for Discogs artist id
    url = "https://api.discogs.com/database/search"
    r = requests.get(url, params={"query": artist, "type": "artist", 'token': prefs.discogs_pat}, headers={"User-Agent": t_agent})
    id = r.json()["results"][0]["id"]

    # Search artist info, get images
    url = "https://api.discogs.com/artists/" + str(id)
    r = requests.get(url, headers={"User-Agent": t_agent}, params={'token': prefs.discogs_pat})
    images = r.json()["images"]

    # Respect rate limit
    rate_remaining = r.headers["X-Discogs-Ratelimit-Remaining"]
    if int(rate_remaining) < 30:
        time.sleep(5)

    # Find a square image in list of images
    for image in images:
        if image['height'] == image['width']:
            print("Found square")
            url = image['uri']
            break
    else:
        url = images[0]['uri']

    response = urllib.request.urlopen(url)
    im = Image.open(response)

    width, height = im.size
    if width > height:
        delta = width - height
        left = int(delta/2)
        upper = 0
        right = height + left
        lower = height
    else:
        delta = height - width
        left = 0
        upper = int(delta/2)
        right = width
        lower = width + upper

    im = im.crop((left, upper, right, lower))
    im.save(filepath, 'JPEG', quality=90)
    im.close()
    print("Found artist image from Discogs")


def save_fanart_artist_thumb(mbid, filepath, preview=False):

    print("Searching fanart.tv for image...")
    #print("mbid is " + mbid)
    r = requests.get("http://webservice.fanart.tv/v3/music/" \
                     + mbid + "?api_key=" + prefs.fatvap, timeout=5)
    # print(r.json())
    thumblink = r.json()['artistthumb'][0]['url']
    if preview:
        thumblink = thumblink.replace("/fanart/music", "/preview/music")

    response = urllib.request.urlopen(thumblink, timeout=10)
    info = response.info()

    t = io.BytesIO()
    t.seek(0)
    t.write(response.read())
    l = 0
    t.seek(0, 2)
    l = t.tell()
    t.seek(0)

    if info.get_content_maintype() == 'image' and l > 1000:
        f = open(filepath, 'wb')
        f.write(t.read())
        f.close()

        if prefs.fanart_notify:
            prefs.fanart_notify = False
            show_message(_("Notice: Artist image sourced from fanart.tv"),
                         _('They encourage you to contribute at https://fanart.tv'), mode='link')
        print("Found artist thumbnail from fanart.tv")

class ArtistList:

    def __init__(self):

        self.tab_h = round(60 * gui.scale)
        self.thumb_size = round(55 * gui.scale)

        self.current_artists = []
        self.current_album_counts = {}
        self.current_artist_track_counts = {}

        self.thumb_cache = {}

        self.to_fetch = ""
        self.to_fetch_mbid_a = ""

        self.scroll_position = 0

        self.id_to_load = ""

        self.d_click_timer = Timer()
        self.d_click_ref = -1

        self.click_ref = -1
        self.click_highlight_timer = Timer()

        self.saves = {}

        self.load = False

        self.shown_letters = []

        self.hover_on = "NONE"
        self.hover_timer = Timer(10)

        self.sample_tracks = {}

    def load_img(self, artist):

        f_artist = filename_safe(artist)

        filepath = os.path.join(a_cache_dir, f_artist + "-lfm.png")

        if os.path.isfile(os.path.join(user_directory, "artist-pictures/" + f_artist + ".png")):
            filepath = os.path.join(user_directory, "artist-pictures/" + f_artist + ".png")

        elif os.path.isfile(os.path.join(user_directory, "artist-pictures/" + f_artist + ".jpg")):
            filepath = os.path.join(user_directory, "artist-pictures/" + f_artist + ".jpg")

        elif os.path.isfile(os.path.join(a_cache_dir, f_artist + "-ftv-full.jpg")):
            filepath = os.path.join(a_cache_dir, f_artist + "-ftv-full.jpg")

        elif os.path.isfile(os.path.join(a_cache_dir, f_artist + "-ftv.jpg")):
            filepath = os.path.join(a_cache_dir, f_artist + "-ftv.jpg")

        elif os.path.isfile(os.path.join(a_cache_dir, f_artist + "-dcg.jpg")):
            filepath = os.path.join(a_cache_dir, f_artist + "-dcg.jpg")

        if os.path.isfile(filepath):

            try:
                g = io.BytesIO()
                g.seek(0)

                im = Image.open(filepath)

                im.thumbnail((self.thumb_size, self.thumb_size), Image.ANTIALIAS)

                # bigsize = (im.size[0] * 4, im.size[1] * 4)
                # mask = Image.new('L', bigsize, 0)
                # draw = ImageDraw.Draw(mask)
                # draw.ellipse((0, 0) + bigsize, fill=255)
                # mask = mask.resize(im.size, Image.ANTIALIAS)
                # im.putalpha(mask)

                im.save(g, 'PNG')
                g.seek(0)

                wop = rw_from_object(g)
                s_image = IMG_Load_RW(wop, 0)
                texture = SDL_CreateTextureFromSurface(renderer, s_image)
                SDL_FreeSurface(s_image)
                tex_w = pointer(c_int(0))
                tex_h = pointer(c_int(0))
                SDL_QueryTexture(texture, None, None, tex_w, tex_h)
                sdl_rect = SDL_Rect(0, 0)
                sdl_rect.w = int(tex_w.contents.value)
                sdl_rect.h = int(tex_h.contents.value)

                self.thumb_cache[artist] = [texture, sdl_rect]
            except:
                print("Artist thumbnail processing error")
                self.thumb_cache[artist] = None

        elif artist in prefs.failed_artists:
            self.thumb_cache[artist] = None
        else:
            if not self.to_fetch:

                if prefs.auto_dl_artist_data:
                    self.to_fetch = artist
                    tm.ready("worker")

                else:
                    self.thumb_cache[artist] = None


    def worker(self):

        if self.load:

            if after_scan:
                return

            self.prep()
            self.load = False
            return

        if self.to_fetch:

            if get_lfm_wait_timer.get() < 2:
                return

            artist = self.to_fetch
            f_artist = filename_safe(artist)
            filename = f_artist + '-lfm.png'
            filename2 = f_artist + '-lfm.txt'
            filename3 = f_artist + '-ftv.jpg'
            filename4 = f_artist + '-dcg.jpg'
            filepath = os.path.join(a_cache_dir, filename)
            filepath2 = os.path.join(a_cache_dir, filename2)
            filepath3 = os.path.join(a_cache_dir, filename3)
            filepath4 = os.path.join(a_cache_dir, filename4)
            got_image = False
            try:
                # Lookup artist info on last.fm
                print("lastfm lookup artist: " + artist)
                mbid = lastfm.artist_mbid(artist)
                get_lfm_wait_timer.set()
                # if data[0] is not False:
                #     #cover_link = data[2]
                #     text = data[1]
                #
                #     if not os.path.exists(filepath2):
                #         f = open(filepath2, 'w', encoding='utf-8')
                #         f.write(text)
                #         f.close()

                if mbid and prefs.enable_fanart_artist:
                    save_fanart_artist_thumb(mbid, filepath3, preview=True)
                    got_image = True

            except:
                print("Failed to find image from fanart.tv")

            if not got_image and verify_discogs():
                try:
                    save_discogs_artist_thumb(artist, filepath4)
                except:
                    print("Failed to find image from discogs")

            if os.path.exists(filepath3) or os.path.exists(filepath4):
                gui.update += 1
            else:
                if artist not in prefs.failed_artists:
                    print("Failed featching: " + artist)
                    prefs.failed_artists.append(artist)

            self.to_fetch = ""

    def prep(self):
        self.scroll_position = 0

        curren_pl_no = id_to_pl(self.id_to_load)
        if curren_pl_no is None:
            return
        current_pl = pctl.multi_playlist[curren_pl_no]

        all = []
        artist_parents = {}
        counts = {}
        play_time = {}

        b = 0

        try:

            for item in current_pl[2]:
                b += 1
                if b % 100 == 0:
                    time.sleep(0.001)

                track = pctl.g(item)

                if 'artists' in track.misc:
                    artists = track.misc['artists']
                else:
                    if prefs.artist_list_prefer_album_artist and track.album_artist:
                        artists = track.album_artist
                    else:
                        artists = get_artist_strip_feat(track)

                    artists = [x.strip() for x in artists.split(';')]

                pp = 0
                if prefs.artist_list_sort_mode == "play":
                    pp = star_store.get(item)

                for artist in artists:

                    if artist:

                        # Add play time
                        if prefs.artist_list_sort_mode == "play":
                            p = play_time.get(artist, 0)
                            play_time[artist] = p + pp

                        # Get a sample track for fallback art
                        if artist not in self.sample_tracks:
                            self.sample_tracks[artist] = track

                        # Confirm to final list if appeared at least 5 times
                        #if artist not in all:
                        if artist not in counts:
                            counts[artist] = 0
                        counts[artist] += 1
                        if artist not in all:
                            if counts[artist] > 4:
                                all.append(artist)
                            elif len(current_pl[2]) < 1000:
                                all.append(artist)

                        if artist not in artist_parents:
                            artist_parents[artist] = []
                        if track.parent_folder_path not in artist_parents[artist]:
                            artist_parents[artist].append(track.parent_folder_path)

            current_album_counts = artist_parents

            if prefs.artist_list_sort_mode == "popular":
                all.sort(key=counts.get, reverse=True)
            elif prefs.artist_list_sort_mode == "play":
                all.sort(key=play_time.get, reverse=True)
            else:
                all.sort(key=lambda y: y.lower())

        except:
            print("Album scan failure")
            time.sleep(4)
            return

        # Artist-list, album-counts, scroll-position, playlist-length
        save = [all, current_album_counts, 0, len(current_pl[2]), counts]

        # Scroll to playing artist
        scroll = 0
        if pctl.playing_ready():
            track = pctl.playing_object()
            for i, item in enumerate(save[0]):
                if item == track.artist or item == track.album_artist:
                    scroll = i
                    break
        save[2] = scroll

        viewing_pl_id = pctl.multi_playlist[pctl.active_playlist_viewing][6]
        if viewing_pl_id in self.saves:
            self.saves[viewing_pl_id][2] = self.scroll_position

        self.saves[current_pl[6]] = save
        gui.update += 1

    def locate_artist_letter(self, text):

        if not text or prefs.artist_list_sort_mode != "alpha":
            return

        letter = text[0].lower()
        letter_upper = letter.upper()
        for i, item in enumerate(self.current_artists):
            if item and (item[0] == letter or item[0] == letter_upper):
                self.scroll_position = i
                break

        viewing_pl_id = pctl.multi_playlist[pctl.active_playlist_viewing][6]
        if pctl.multi_playlist[pctl.active_playlist_viewing][10]:
            viewing_pl_id = pctl.multi_playlist[pctl.active_playlist_viewing][10]
        if viewing_pl_id in self.saves:
            self.saves[viewing_pl_id][2] = self.scroll_position

    def locate_artist(self, track):

        for i, item in enumerate(self.current_artists):
            if item == track.artist or item == track.album_artist or ('artists' in track.misc and item in track.misc['artists']):
                self.scroll_position = i
                break

        viewing_pl_id = pctl.multi_playlist[pctl.active_playlist_viewing][6]
        if viewing_pl_id in self.saves:
            self.saves[viewing_pl_id][2] = self.scroll_position

    def draw_card_text_only(self, artist, x, y, w, area, thin_mode, line1_colour, line2_colour, light_mode, bg):

        album_mode = False
        for albums in self.current_album_counts.values():
            if len(albums) > 1:
                album_mode = True
                break

        if not album_mode:
            count = self.current_artist_track_counts[artist]
            text = str(count) + " track"
            if count > 1:
                text += "s"
        else:
            album_count = len(self.current_album_counts[artist])
            text = str(album_count) + " album"
            if album_count > 1:
                text += "s"

        if gui.preview_artist_loading == artist:
            # . Max 20 chars. Alt: Downloading image, Loading image
            text = _("Downloading data...")

        x_text = round(10 * gui.scale)
        artist_font = 313
        count_font = 312
        extra_text_space = 0
        ddt.text((x_text, y + round(2 * gui.scale)), artist, line1_colour, artist_font,
                 extra_text_space + w - x_text - 30 * gui.scale, bg=bg)
        # ddt.text((x_text, y + self.tab_h // 2 - 2 * gui.scale), text, line2_colour, count_font,
        #          extra_text_space + w - x_text - 15 * gui.scale, bg=bg)

    def draw_card_with_thumbnail(self, artist, x, y, w, area, thin_mode, line1_colour, line2_colour, light_mode, bg):

        if artist not in self.thumb_cache:
            self.load_img(artist)

        thumb_x = round(x + 10 * gui.scale)
        x_text = x + self.thumb_size + 19 * gui.scale
        artist_font = 513
        count_font = 312
        extra_text_space = 0
        if thin_mode:
            thumb_x = round(x + 10 * gui.scale)
            x_text = x + self.thumb_size + 17 * gui.scale
            artist_font = 211
            count_font = 311
            extra_text_space = 135 * gui.scale
            thin_mode = True
            area = (4 * gui.scale, y, w - 7 * gui.scale, self.tab_h - 2)
            fields.add(area)

        back_colour = [30, 30, 30, 255]
        back_colour_2 = [27, 27, 27, 255]
        border_colour = [60, 60, 60, 255]
        # if colours.lm:
        #     back_colour = [200, 200, 200, 255]
        #     back_colour_2 = [240, 240, 240, 255]
        #     border_colour = [160, 160, 160, 255]
        rect = (thumb_x, round(y), self.thumb_size, self.thumb_size)

        if thin_mode and coll(area) and is_level_zero() and y + self.tab_h < window_size[1] - gui.panelBY:
            tab_rect = (x, y - round(2 * gui.scale), round(190 * gui.scale), self.tab_h - round(1 * gui.scale))

            for r in subtract_rect(tab_rect, rect):
                r = SDL_Rect(r[0], r[1], r[2], r[3])
                style_overlay.hole_punches.append(r)

            ddt.rect(tab_rect, back_colour_2)
            bg = back_colour_2

        ddt.rect(rect, back_colour)
        ddt.rect(rect, border_colour)

        fields.add(rect)
        if coll(rect) and is_level_zero(True):
            self.hover_any = True

            hover_delay = 0.5
            if gui.compact_artist_list:
                hover_delay = 2


            if gui.preview_artist != artist:
                if self.hover_on != artist:
                    self.hover_on = artist
                    gui.preview_artist = ""
                    self.hover_timer.set()
                    gui.delay_frame(hover_delay)
                else:
                    if self.hover_timer.get() > hover_delay and not gui.preview_artist_loading:
                        gui.preview_artist = ""
                        path = artist_info_box.get_data(artist, get_img_path=True)
                        if not path:
                            gui.preview_artist_loading = artist
                            shoot = threading.Thread(target=get_artist_preview, args=((artist, round(thumb_x + self.thumb_size), round(y))))
                            shoot.daemon = True
                            shoot.start()

                        if path:
                            set_artist_preview(path, artist, round(thumb_x + self.thumb_size), round(y))

            if inp.mouse_click:
                self.hover_timer.force_set(-2)
                gui.delay_frame(2 + hover_delay)

        drawn = False
        if artist in self.thumb_cache:
            thumb = self.thumb_cache[artist]
            if thumb is not None:
                thumb[1].x = thumb_x
                thumb[1].y = round(y)
                SDL_RenderCopy(renderer, thumb[0], None, thumb[1])
                drawn = True
                if prefs.art_bg:
                    rect = SDL_Rect(thumb_x, round(y), self.thumb_size, self.thumb_size)
                    if (rect.y + rect.h) > window_size[1] - gui.panelBY:
                        diff = (rect.y + rect.h) - (window_size[1] - gui.panelBY)
                        rect.h -= round(diff)
                    style_overlay.hole_punches.append(rect)
        if not drawn:
            track = self.sample_tracks.get(artist)
            if track:
                gall_ren.render(track, (round(thumb_x), round(y)), self.thumb_size)

        if thin_mode:
            text = artist[:2].title()
            if text not in self.shown_letters:
                ww = ddt.get_text_w(text, 211)
                ddt.rect((thumb_x + round(1 * gui.scale), y + self.tab_h - 20 * gui.scale, ww + 5 * gui.scale,
                          13 * gui.scale), [20, 20, 20, 255])
                ddt.text((thumb_x + 3 * gui.scale, y + self.tab_h - 23 * gui.scale), text, [240, 240, 240, 255], 210, bg=[20, 20, 20, 255])
                self.shown_letters.append(text)

        # Draw labels
        if not thin_mode or (coll(area) and is_level_zero() and y + self.tab_h < window_size[1] - gui.panelBY):

            album_mode = False
            for albums in self.current_album_counts.values():
                if len(albums) > 1:
                    album_mode = True
                    break

            if not album_mode:
                count = self.current_artist_track_counts[artist]
                text = str(count) + " track"
                if count > 1:
                    text += "s"
            else:
                album_count = len(self.current_album_counts[artist])
                text = str(album_count) + " album"
                if album_count > 1:
                    text += "s"

            if gui.preview_artist_loading == artist:
                #. Max 20 chars. Alt: Downloading image, Loading image
                text = _("Downloading data...")

            ddt.text((x_text, y + self.tab_h // 2 - 19 * gui.scale), artist, line1_colour, artist_font, extra_text_space + w - x_text - 30 * gui.scale, bg=bg)
            ddt.text((x_text, y + self.tab_h // 2 - 2 * gui.scale), text, line2_colour, count_font, extra_text_space + w - x_text - 15 * gui.scale, bg=bg)


    def draw_card(self, artist, x, y, w):

        global playlist_selected

        area = (4 * gui.scale, y, w - 26 * gui.scale, self.tab_h - 2)
        if prefs.artist_list_style == 2:
            area = (4 * gui.scale, y, w - 26 * gui.scale, self.tab_h - 1)
        
        fields.add(area)

        light_mode = False
        line1_colour = [235, 235, 235, 255]
        line2_colour = [255, 255, 255, 120]
        fade_max = 50

        thin_mode = False
        if gui.compact_artist_list:
            thin_mode = True
            line2_colour = [115, 115, 115, 255]

        else:
            if test_lumi(colours.side_panel_background) < 0.55 and not thin_mode:
                light_mode = True
                fade_max = 20
                line1_colour = [35, 35, 35, 255]
                line2_colour = [100, 100, 100, 255]

        # Fade on click
        bg = colours.side_panel_background
        if not thin_mode:

            if coll(area) and is_level_zero(True): #or pctl.g(default_playlist[pctl.playlist_view_position]).artist == artist:
                ddt.rect(area, [50, 50, 50, 50])
                bg = alpha_blend([50, 50, 50, 50], colours.side_panel_background)
            else:

                fade = 0
                t = self.click_highlight_timer.get()
                if self.click_ref == artist and (t < 2.2 or artist_list_menu.active):

                    if t < 1.9 or artist_list_menu.active:
                        fade = fade_max
                    else:
                        fade = fade_max - round((t - 1.9) / 0.3 * fade_max)

                    gui.update += 1
                    ddt.rect(area, [50, 50, 50, fade])

                bg = alpha_blend([50, 50, 50, fade], colours.side_panel_background)


        if prefs.artist_list_style == 1:
            self.draw_card_with_thumbnail(artist, x, y, w, area, thin_mode, line1_colour, line2_colour, light_mode, bg)
        else:
            self.draw_card_text_only(artist, x, y, w, area, thin_mode, line1_colour, line2_colour, light_mode, bg)


        if coll(area) and mouse_position[1] < window_size[1] - gui.panelBY:
            if inp.mouse_click:
                self.click_ref = artist

                double_click = False
                if self.d_click_timer.get() < 0.4 and self.d_click_ref == artist:
                    double_click = True

                self.click_highlight_timer.set()

                if pctl.multi_playlist[pctl.active_playlist_viewing][10] and \
                        pctl.multi_playlist[pctl.active_playlist_viewing][0].startswith("Artist:"):
                    create_artist_pl(artist, replace=True)

                block_starts = []
                current = False
                for i in range(len(default_playlist)):
                    track = pctl.g(default_playlist[i])
                    if current is False:
                        if track.artist == artist or track.album_artist == artist or ('artists' in track.misc and artist in track.misc['artists']):
                            block_starts.append(i)
                            current = True
                    else:
                        if track.artist != artist and track.album_artist != artist or ('artists' in track.misc and artist in track.misc['artists']):
                            current = False

                if not block_starts:
                    print("No matching artists found in playlist")
                    return

                select = block_starts[0]

                if len(block_starts) > 1:
                    if -1 < playlist_selected < len(default_playlist):
                        if playlist_selected in block_starts:
                            scroll_hide_timer.set()
                            gui.frame_callback_list.append(TestTimer(0.9))
                            if block_starts[-1] == playlist_selected:
                                pass
                            else:
                                select = block_starts[block_starts.index(playlist_selected) + 1]


                gui.pl_update += 1
                if album_mode:
                    goto_album(select)

                self.click_highlight_timer.set()

                if double_click:
                    select = block_starts[0]
                    pctl.jump(default_playlist[select])
                    pctl.playlist_view_position = select
                    console.print("DEBUG: Position changed by artist click")
                    playlist_selected = select

                    shift_selection.clear()
                    self.d_click_timer.force_set(10)
                else:
                    #playlist_selected = i
                    pctl.playlist_view_position = select
                    console.print("DEBUG: Position changed by artist click")
                    playlist_selected = select
                    self.d_click_ref = artist
                    self.d_click_timer.set()

            if middle_click:
                self.click_ref = artist
                self.click_highlight_timer.set()
                create_artist_pl(artist)

            if right_click:
                self.click_ref = artist
                self.click_highlight_timer.set()

                artist_list_menu.activate(in_reference=artist)


    def render(self, x, y ,w ,h):

        if prefs.artist_list_style == 1:
            self.tab_h = round(60 * gui.scale)
        else:
            self.tab_h = round(22 * gui.scale)


        viewing_pl_id = pctl.multi_playlist[pctl.active_playlist_viewing][6]

        # use parent playlst is set
        if pctl.multi_playlist[pctl.active_playlist_viewing][10]:

            # test if parent still exsists
            new = id_to_pl(pctl.multi_playlist[pctl.active_playlist_viewing][10])
            if new is None:
                pctl.multi_playlist[pctl.active_playlist_viewing][10] = ""
            else:
                if not pctl.multi_playlist[pctl.active_playlist_viewing][0].startswith("Artist:"):
                    pctl.multi_playlist[pctl.active_playlist_viewing][10] = ""
                else:
                    viewing_pl_id = pctl.multi_playlist[pctl.active_playlist_viewing][10]

        if viewing_pl_id in self.saves:
            self.current_artists = self.saves[viewing_pl_id][0]
            self.current_album_counts = self.saves[viewing_pl_id][1]
            self.current_artist_track_counts = self.saves[viewing_pl_id][4]
            self.scroll_position = self.saves[viewing_pl_id][2]

            if self.saves[viewing_pl_id][3] != len(pctl.multi_playlist[id_to_pl(viewing_pl_id)][2]):
                del self.saves[viewing_pl_id]
                return

        else:

            # if self.current_pl != viewing_pl_id:
            self.id_to_load = viewing_pl_id
            if not self.load:

                #self.prep()
                self.current_artists = []
                self.current_album_counts = []
                self.current_artist_track_counts = {}
                self.load = True
                tm.ready("worker")


        area = (x, y, w, h)
        area2 = (x + 1, y, w - 3, h)

        ddt.rect(area, colours.side_panel_background)
        ddt.text_background_colour = colours.side_panel_background

        if coll(area) and mouse_wheel:
            mx = 1
            if prefs.artist_list_style == 2:
                mx = 3
            self.scroll_position -= mouse_wheel * mx
        if self.scroll_position < 0:
            self.scroll_position = 0

        range = (h // self.tab_h) - 1

        whole_rage = math.floor(h // self.tab_h)

        if range > 4 and self.scroll_position > len(self.current_artists) - range:
            self.scroll_position = len(self.current_artists) - range

        if len(self.current_artists) <= whole_rage :
            self.scroll_position = 0

        fields.add(area2)
        scroll_x = x + w - 18 * gui.scale
        if colours.lm:
            scroll_x = x + w - 22 * gui.scale
        if (coll(area2) or artist_list_scroll.held) and not pref_box.enabled:
            scroll_width = 15 * gui.scale
            inset = 0
            if gui.compact_artist_list:
                pass
                # scroll_width = round(6 * gui.scale)
                # scroll_x += round(9 * gui.scale)
            else:
                self.scroll_position = artist_list_scroll.draw(scroll_x, y + 1, scroll_width, h, self.scroll_position, len(self.current_artists) - range, r_click=right_click, jump_distance=35, extend_field=6*gui.scale)

        if not self.current_artists:
            text = _("No artists in playlist")

            if default_playlist:
                text = _("Artist threshold not met")
            if self.load:
                text = _("Loading Artist List...")
                if loading_in_progress or transcode_list or after_scan:
                    text = _("Busy...")

            ddt.text((x + w // 2, y + (h // 7), 2), text, alpha_mod(colours.side_bar_line2, 100), 212, max_w=w - 17 * gui.scale)

        yy = y + 12 * gui.scale

        i = int(self.scroll_position)

        if viewing_pl_id in self.saves:
            self.saves[viewing_pl_id][2] = self.scroll_position

        prefetch_mode = False
        prefetch_distance = 22

        self.shown_letters.clear()

        self.hover_any = False

        for i, artist in enumerate(self.current_artists[i:], start=i):

            if not prefetch_mode:
                self.draw_card(artist, x, round(yy), w)

                yy += self.tab_h

                if yy - y > h - 24 * gui.scale:
                    prefetch_mode = True
                    continue

            if prefetch_mode:
                if prefs.artist_list_style == 2:
                    break
                prefetch_distance -= 1
                if prefetch_distance < 1:
                    break
                if artist not in self.thumb_cache:
                    self.load_img(artist)
                    break

        if not self.hover_any:
            gui.preview_artist = ""
            self.hover_timer.force_set(10)
            artist_preview_render.show = False
            self.hover_on = False

artist_list_box = ArtistList()


class TreeView:

    def __init__(self):

        self.trees = {}  # Per playlist tree
        self.rows = []  # For display (parsed from tree)
        self.rows_id = ""

        self.opens = {}  # Folders clicks to show per playlist

        self.scroll_positions = {}

        # Recursive gen_rows vars
        self.count = 0
        self.depth = 0

        self.background_processing = False
        self.d_click_timer = Timer(100)
        self.d_click_id = ""

        self.menu_selected = ""
        self.folder_colour_cache = {}
        self.dragging_name = ""

        self.force_opens = []
        self.click_drag_source = None

        self.tooltip_on = ""
        self.tooltip_timer = Timer(10)

        self.lock_pl = None

        # self.bold_colours = ColourGenCache(0.6, 0.7)

    def clear_all(self):
        self.rows_id = ""
        self.trees.clear()

    def collapse_all(self):
        pl_id = pl_to_id(pctl.active_playlist_viewing)

        if self.lock_pl:
            pl_id = self.lock_pl

        opens = self.opens.get(pl_id)
        if opens is None:
            opens = []
            self.opens[pl_id] = opens

        opens.clear()
        self.rows_id = ""

    def clear_target_pl(self, pl_number, pl_id=None):

        if pl_id is None:
            pl_id = pl_to_id(pl_number)

        if gui.lsp and prefs.left_panel_mode == "folder view":

            if pl_id in self.trees:
                if not self.background_processing:
                    self.background_processing = True
                    shoot_dl = threading.Thread(target=self.gen_tree, args=[pl_id])
                    shoot_dl.daemon = True
                    shoot_dl.start()
        else:
            if pl_id in self.trees:
                del self.trees[pl_id]


    def show_track(self, track):

        if track is None:
            return

        # Get tree and opened folder data for this playlist
        pl_id = pctl.multi_playlist[pctl.active_playlist_viewing][6]
        opens = self.opens.get(pl_id)
        if opens is None:
            opens = []
            self.opens[pl_id] = opens

        tree = self.trees.get(pl_id)
        if not tree:
            return

        scroll_position = self.scroll_positions.get(pl_id)
        if scroll_position is None:
            scroll_position = 0

        # Clear all opened folders
        opens.clear()

        # Set every folder in path as opened
        path = ""
        crumbs = track.parent_folder_path.split("/")[1:]
        for c in crumbs:
            path += "/" + c
            opens.append(path)

        # Regenerate row display
        self.gen_rows(tree, opens)

        # Locate and set scroll position to playing folder
        for i, row in enumerate(self.rows):
            if row[1] + "/" + row[0] == track.parent_folder_path:

                scroll_position = i - 5
                if scroll_position < 0:
                    scroll_position = 0
                break

        max_scroll = len(self.rows) - ((window_size[0] - (gui.panelY + gui.panelBY)) // round(22 * gui.scale))
        if scroll_position > max_scroll:
            scroll_position = max_scroll
        if scroll_position < 0:
            scroll_position = 0

        self.scroll_positions[pl_id] = scroll_position

        gui.update_layout()
        gui.update += 1

    def get_pl_id(self):
        if self.lock_pl:
            return self.lock_pl
        else:
            return pctl.multi_playlist[pctl.active_playlist_viewing][6]

    def render(self, x, y, w, h):

        global quick_drag

        pl_id = self.get_pl_id()

        tree = self.trees.get(pl_id)

        # Generate tree data if not done yet
        if tree is None:
            if not self.background_processing:
                self.background_processing = True
                shoot_dl = threading.Thread(target=self.gen_tree, args=[pl_id])
                shoot_dl.daemon = True
                shoot_dl.start()

            self.playlist_id_on = pctl.multi_playlist[pctl.active_playlist_viewing][6]

        opens = self.opens.get(pl_id)
        if opens is None:
            opens = []
            self.opens[pl_id] = opens

        scroll_position = self.scroll_positions.get(pl_id)
        if scroll_position is None:
            scroll_position = 0

        area = (x, y, w, h)
        fields.add(area)
        ddt.rect(area, colours.side_panel_background)
        ddt.text_background_colour = colours.side_panel_background

        if self.background_processing and self.rows_id != pl_id:
            ddt.text((x + w // 2, y + (h // 7), 2), _("Loading Folder Tree..."), alpha_mod(colours.side_bar_line2, 100), 212, max_w=w - 17 * gui.scale)
            return

        # if not tree or not self.rows:
        #     ddt.text((x + w // 2, y + (h // 7), 2), _("Folder Tree"), alpha_mod(colours.side_bar_line2, 100),
        #              212, max_w=w - 17 * gui.scale)
        #     return
        if not tree:
            ddt.text((x + w // 2, y + (h // 7), 2), _("Folder Tree"), alpha_mod(colours.side_bar_line2, 100),
                     212, max_w=w - 17 * gui.scale)
            return

        if self.rows_id != pl_id:
            if not self.background_processing:
                self.gen_rows(tree, opens)
                self.rows_id = pl_id
                max_scroll = len(self.rows) - (h // round(22 * gui.scale))
                if scroll_position > max_scroll:
                    scroll_position = max_scroll

            else:
                return

        if not self.rows:
            ddt.text((x + w // 2, y + (h // 7), 2), _("Folder Tree"), alpha_mod(colours.side_bar_line2, 100),
                     212, max_w=w - 17 * gui.scale)
            return


        yy = y + round(11 * gui.scale)
        xx = x + round(22 * gui.scale)

        spacing = round(21 * gui.scale)
        max_scroll = len(self.rows) - (h // round(22 * gui.scale))

        mouse_in = coll(area)

        # Mouse wheel scrolling
        if mouse_in and mouse_wheel:
                scroll_position += mouse_wheel * -2
                if scroll_position < 0:
                    scroll_position = 0
                if scroll_position > max_scroll:
                    scroll_position = max_scroll

        focused = is_level_zero()

        # Draw scroll bar
        if mouse_in or tree_view_scroll.held:
            scroll_position = tree_view_scroll.draw(x + w - round(12 * gui.scale), y + 1, round(11 * gui.scale), h, scroll_position,
                                                           max_scroll, r_click=right_click, jump_distance=40)

        self.scroll_positions[pl_id] = scroll_position

        # Draw folder rows
        playing_track = pctl.playing_object()
        max_w = w - round(45 * gui.scale)

        light_mode = test_lumi(colours.side_panel_background) < 0.5
        semilight_mode = test_lumi(colours.side_panel_background) < 0.8

        for i, item in enumerate(self.rows):

            if i < scroll_position:
                continue

            if yy > y + h - spacing:
                break

            target = item[1] + "/" + item[0]

            inset = item[2] * round(10 * gui.scale)
            rect = (xx + inset - round(15 * gui.scale), yy, max_w - inset + round(15 * gui.scale), spacing - 1)
            fields.add(rect)

            #text_colour = [255, 255, 255, 100]
            text_colour = rgb_add_hls(colours.side_panel_background, 0, 0.35, -0.15)

            box_colour = [200, 100, 50, 255]

            if semilight_mode:
                text_colour = [255, 255, 255, 180]

            if light_mode:
                text_colour = [0, 0, 0, 200]

            full_folder_path = item[1] + "/" + item[0]

            # Hold highlight while menu open
            if (folder_tree_menu.active or folder_tree_stem_menu.active) and full_folder_path == self.menu_selected:
                text_colour = [255, 255, 255, 170]
                if semilight_mode:
                    text_colour = (255, 255, 255, 255)
                if light_mode:
                    text_colour = [0, 0, 0, 255]

            # Hold highlight while dragging folder
            if quick_drag and not point_proximity_test(gui.drag_source_position, mouse_position, 15):
                if shift_selection:
                    if pctl.g(pctl.multi_playlist[id_to_pl(pl_id)][2][shift_selection[0]]).fullpath.startswith(full_folder_path + "/") and self.dragging_name and item[0].endswith(self.dragging_name):
                        text_colour = (255, 255, 255, 230)
                        if semilight_mode:
                            text_colour = (255, 255, 255, 255)
                        if light_mode:
                            text_colour = [0, 0, 0, 255]

            # Set highlight colours if folder is playing
            if 0 < pctl.playing_state < 3 and playing_track:
                if playing_track.parent_folder_path == full_folder_path or full_folder_path + "/" in playing_track.fullpath:
                    text_colour = [255, 255, 255, 225]
                    box_colour = [140, 220, 20, 255]
                    if semilight_mode:
                        text_colour = (255, 255, 255, 255)
                    if light_mode:
                        text_colour = [0, 0, 0, 255]

            if right_click:
                mouse_in = coll(rect) and is_level_zero(False)
            else:
                mouse_in = coll(rect) and focused and not (quick_drag and not point_proximity_test(gui.drag_source_position, mouse_position, 15))

            if mouse_in and not tree_view_scroll.held:

                if middle_click:
                    stem_to_new_playlist(full_folder_path)

                elif right_click:

                    if item[3]:
                        
                        for p, id in enumerate(pctl.multi_playlist[id_to_pl(pl_id)][2]):
                            if msys:
                                if pctl.g(id).fullpath.startswith(target.lstrip("/")):
                                    folder_tree_menu.activate(in_reference=id)
                                    self.menu_selected = full_folder_path
                                    break                            
                            else:
                                if pctl.g(id).fullpath.startswith(target):
                                    folder_tree_menu.activate(in_reference=id)
                                    self.menu_selected = full_folder_path
                                    break
                    else:
                        if msys:
                            folder_tree_stem_menu.activate(in_reference=full_folder_path.lstrip("/"))
                            self.menu_selected = full_folder_path.lstrip("/")
                        else:
                            folder_tree_stem_menu.activate(in_reference=full_folder_path)
                            self.menu_selected = full_folder_path

                elif inp.mouse_click:
                    #quick_drag = True

                    if not self.click_drag_source:
                        self.click_drag_source = item
                        set_drag_source()

                elif mouse_up and self.click_drag_source == item:
                    # Click tree level folder to open/close branch

                    if target not in opens:
                        opens.append(target)
                    else:
                        for s in reversed(range(len(opens))):
                            if opens[s].startswith(target):
                                del opens[s]

                    if item[3]:

                        # Locate the first track of folder in playlist
                        track_id = None
                        for p, id in enumerate(default_playlist):
                            if msys:
                                if pctl.g(id).fullpath.startswith(target.lstrip("/")):
                                    track_id = id
                                    break
                            else:
                                if pctl.g(id).fullpath.startswith(target):
                                    track_id = id
                                    break                            
                        else:  # Fallback to folder name if full-path not found (hack for networked items)
                            for p, id in enumerate(default_playlist):
                                if pctl.g(id).parent_folder_name == item[0]:
                                    track_id = id
                                    break

                        if track_id is not None:
                            # Single click base folder to locate in playlist
                            if self.d_click_timer.get() > 0.5 or self.d_click_id != target:
                                pctl.show_current(select=True, index=track_id, no_switch=True, highlight=True, folder_list=False)
                                self.d_click_timer.set()
                                self.d_click_id = target

                            # Double click base folder to play
                            else:
                                pctl.jump(track_id)

                    # Regenerate display rows after clicking
                    self.gen_rows(tree, opens)

            # Highlight folder text on mouse over
            if (mouse_in and not mouse_down) or item == self.click_drag_source:
                text_colour = (255, 255, 255, 235)
                if semilight_mode:
                    text_colour = (255, 255, 255, 255)
                if light_mode:
                    text_colour = [0, 0, 0, 255]

            # Render folder name text
            if item[4] > 50:
                font = 514
                text_label_colour = text_colour  # self.bold_colours.get(full_folder_path)
            else:
                font = 414
                text_label_colour = text_colour

            if mouse_in:
                tw = ddt.get_text_w(item[0], font)

                if self.tooltip_on != item:
                    self.tooltip_on = item
                    self.tooltip_timer.set()
                    gui.frame_callback_list.append(TestTimer(0.6))

                if tw > max_w - inset and self.tooltip_on == item and self.tooltip_timer.get() >= 0.6:
                    rect = (xx + inset, yy - 2 * gui.scale, tw + round(20 * gui.scale), 20 * gui.scale)
                    ddt.rect(rect, ddt.text_background_colour)
                    ddt.text((xx + inset, yy), item[0], text_label_colour, font)
                else:
                    ddt.text((xx + inset, yy), item[0], text_label_colour, font, max_w=max_w - inset)
            else:
                ddt.text((xx + inset, yy), item[0], text_label_colour, font, max_w=max_w - inset)


            # # Draw inset bars
            # for m in range(item[2] + 1):
            #     if m == 0:
            #         continue
            #     colour = (255, 255, 255, 20)
            #     if semilight_mode:
            #         colour = (255, 255, 255, 30)
            #     if light_mode:
            #         colour = (0, 0, 0, 60)
            #
            #     if i > 0 and self.rows[i - 1][2] == m - 1:  # the top one needs to be slightly lower lower
            #         ddt.rect((x + (12 * m) + 2, yy - round(1 * gui.scale), round(1 * gui.scale), round(17 * gui.scale)), colour, True)
            #     else:
            #         ddt.rect((x + (12 * m) + 2, yy - round(5 * gui.scale), round(1 * gui.scale), round(21 * gui.scale)), colour, True)


            if prefs.folder_tree_codec_colours:
                box_colour = self.folder_colour_cache.get(full_folder_path)
                if box_colour is None:
                    box_colour = (150, 150, 150 , 255)

            # Draw indicator box and +/- icons next to folder name
            if item[3]:
                rect = (xx + inset - round(9 * gui.scale), yy + round(7 * gui.scale), round(4 * gui.scale), round(4 * gui.scale))
                if light_mode or semilight_mode:
                    border = round(1 * gui.scale)
                    ddt.rect((rect[0] - border, rect[1] - border, rect[2] + border * 2, rect[3] + border * 2),
                             [0, 0, 0, 150])
                ddt.rect(rect, box_colour)

            elif True:
                if not mouse_in or tree_view_scroll.held:
                    #text_colour = [255, 255, 255, 50]
                    text_colour = rgb_add_hls(colours.side_panel_background, 0, 0.2, -0.10)
                    if semilight_mode:
                        text_colour = [255, 255, 255, 70]
                    if light_mode:
                        text_colour = [0, 0, 0, 70]
                if target in opens:
                    ddt.text((xx + inset - round(7 * gui.scale), yy + round(1 * gui.scale), 2), "-", text_colour, 19)
                else:
                    ddt.text((xx + inset - round(7 * gui.scale), yy + round(1 * gui.scale), 2), "+", text_colour, 19)

            yy += spacing



        if self.click_drag_source and not point_proximity_test(gui.drag_source_position, mouse_position, 15) and default_playlist is pctl.multi_playlist[id_to_pl(pl_id)][2]:
            quick_drag = True
            global playlist_hold
            playlist_hold = True

            self.dragging_name = self.click_drag_source[0]
            print(self.dragging_name)

            if "/" in self.dragging_name:
                self.dragging_name = os.path.basename(self.dragging_name)

            shift_selection.clear()
            set_drag_source()
            for p, id in enumerate(pctl.multi_playlist[id_to_pl(pl_id)][2]):
                if msys:
                    if pctl.g(id).fullpath.startswith(self.click_drag_source[1].lstrip("/") + "/" + self.click_drag_source[0] + "/"):
                        shift_selection.append(p)
                else:
                    if pctl.g(id).fullpath.startswith(f"{self.click_drag_source[1]}/{self.click_drag_source[0]}/"):
                        shift_selection.append(p)
            self.click_drag_source = None

        if self.dragging_name and not quick_drag:
            self.dragging_name = ""
        if not mouse_down:
            self.click_drag_source = None


    def gen_row(self, tree_point, path, opens):

        for item in tree_point:
            p = path + "/" + item[1]
            self.count += 1
            enter_level = False
            if len(tree_point) > 1 or path in self.force_opens:  # Ignore levels that are only a single folder wide

                if path in opens or self.depth == 0 or path in self.force_opens:  # Only show if parent stem is open, but always show the root displayed folders

                    # If there is a single base folder in subfolder, combine the path and show it in upper level
                    if len(item[0]) == 1 and len(item[0][0][0]) == 1 and len(item[0][0][0][0][0]) == 0:
                        self.rows.append([item[1] + "/" + item[0][0][1] + "/" + item[0][0][0][0][1], path, self.depth, True, len(item[0])])
                    elif len(item[0]) == 1 and len(item[0][0][0]) == 0:
                        self.rows.append([item[1] + "/" + item[0][0][1], path, self.depth, True, len(item[0])])

                    # Add normal base folder type
                    else:
                        self.rows.append([item[1], path, self.depth, len(item[0]) == 0, len(item[0])])  # Folder name, folder path, depth, is bottom

                    # If folder is open and has only one subfolder, mark that subfolder as open
                    if len(item[0]) == 1 and (p in opens or p in self.force_opens):
                        self.force_opens.append(p + "/" + item[0][0][1])

                self.depth += 1
                enter_level = True

            self.gen_row(item[0], p, opens)

            if enter_level:
                self.depth -= 1



    def gen_rows(self, tree, opens):
        self.count = 0
        self.depth = 0
        self.rows.clear()
        self.force_opens.clear()

        self.gen_row(tree, "", opens)

        gui.update_layout()
        gui.update += 1

    def gen_tree(self, pl_id):
        pl_no = id_to_pl(pl_id)
        if pl_no is None:
            return

        playlist = pctl.multi_playlist[pl_no][2]
        # Generate list of all unique folder paths
        paths = []
        z = 5000
        for p in playlist:

            z += 1
            if z > 1000:
                time.sleep(0.01)  # Throttle thread
                z = 0
            track = pctl.g(p)
            path = track.parent_folder_path
            if path not in paths:
                paths.append(path)
                self.folder_colour_cache[path] = format_colours.get(track.file_ext)

        # Genterate tree from folder paths
        tree = []
        news = []
        for path in paths:
            z += 1
            if z > 5000:
                time.sleep(0.01)  # Throttle thread
                z = 0
            split_path = path.split("/")
            on = tree
            for level in split_path:
                if not level:
                    continue
                # Find if level already exists
                for sub_level in on:
                    if sub_level[1] == level:
                        on = sub_level[0]
                        break
                else:  # Create new level
                    new = [[], level]
                    news.append(new)
                    on.append(new)
                    on = new[0]

        self.trees[pl_id] = tree
        self.rows_id = ""
        self.background_processing = False
        gui.update += 1

tree_view_box = TreeView()


def queue_pause_deco():

    if pctl.pause_queue:
        return [colours.menu_text, colours.menu_background, _("Resume Queue")]
    else:
        return [colours.menu_text, colours.menu_background, _('Pause Queue')]


# def finish_current_deco():
#
#     colour = colours.menu_text
#     line = "Finish Playing Album"
#
#     if pctl.playing_object() is None:
#         colour = colours.menu_text_disabled
#     if pctl.force_queue and pctl.force_queue[0][4] == 1:
#         colour = colours.menu_text_disabled
#
#     return [colour, colours.menu_background, line]

class QueueBox:

    def __init__(self):

        self.dragging = None
        self.fq = []
        self.drag_start_y = 0
        self.drag_start_top = 0
        self.tab_h = 34 * gui.scale
        self.scroll_position = 0
        self.right_click_id = None
        self.d_click_ref = None

        queue_menu.add(_("Remove This"), self.right_remove_item, show_test=self.queue_remove_show)
        queue_menu.add(_("Play Now"), self.play_now, show_test=self.queue_remove_show)
        queue_menu.add("Auto-Stop Here", self.toggle_auto_stop, self.toggle_auto_stop_deco, show_test=self.queue_remove_show)

        queue_menu.add("Pause Queue", self.toggle_pause, queue_pause_deco)
        queue_menu.add(_("Clear Queue"), clear_queue, queue_deco, hint="Alt+Shift+Q")

        queue_menu.add(_("↳ Except for This"), self.clear_queue_crop, show_test=self.except_for_this_show_test)

        queue_menu.add(_("Queue to New Playlist"), self.make_as_playlist, queue_deco)
        # queue_menu.add("Finish Playing Album", finish_current, finish_current_deco)

    def except_for_this_show_test(self, _):
        return self.queue_remove_show(_) and test_shift(_)

    def make_as_playlist(self):

        if pctl.force_queue:
            playlist = []
            for item in pctl.force_queue:

                if item[3] == 0:
                    playlist.append(item[0])
                else:

                    pl = id_to_pl(item[2])
                    if pl is None:
                        print("Lost the target playlist")
                        continue

                    pp = pctl.multi_playlist[pl][2]

                    i = item[1]#= pctl.playlist_playing_position + 1

                    parts = []
                    album_parent_path = pctl.g(item[0]).parent_folder_path

                    while i < len(pp):
                        if pctl.g(pp[i]).parent_folder_path != album_parent_path:
                            break

                        parts.append((pp[i], i))
                        i += 1

                    for part in parts:
                        playlist.append(part[0])

            pctl.multi_playlist.append(pl_gen(title="Queued Tracks",
                                              playlist=copy.deepcopy(playlist),
                                              hide_title=0))

    def drop_tracks_insert(self, insert_position):

        global quick_drag

        if not shift_selection:
            return

        # remove incomplete album from queue
        if insert_position == 0 and pctl.force_queue and pctl.force_queue[0][4] == 1:
            split_queue_album(pctl.force_queue[0][5])

        playlist_index = pctl.active_playlist_viewing
        playlist_id = pl_to_id(pctl.active_playlist_viewing)

        main_track_position = shift_selection[0]
        main_track_id = default_playlist[main_track_position]
        quick_drag = False


        if len(shift_selection) > 1:

            # if shift selection contains only same folder
            for position in shift_selection:
                if pctl.g(default_playlist[position]).parent_folder_path != pctl.g(main_track_id).parent_folder_path or key_ctrl_down:
                    break
            else:
                # Add as album type
                pctl.force_queue.insert(insert_position,
                                        queue_item_gen(main_track_id, main_track_position, playlist_id, 1))
                return

        if len(shift_selection) == 1:
            pctl.force_queue.insert(insert_position, queue_item_gen(main_track_id, main_track_position, playlist_id))
        else:
            # Add each track
            for position in reversed(shift_selection):
                pctl.force_queue.insert(insert_position,
                                        queue_item_gen(default_playlist[position], position, playlist_id))

    def clear_queue_crop(self):

        save = False
        for item in pctl.force_queue:
            if item[5] == self.right_click_id:
                save = item
                break

        clear_queue()
        if save:
            pctl.force_queue.append(save)

    def play_now(self):

        queue_item = None
        queue_index = 0
        for i, item in enumerate(pctl.force_queue):
            if item[5] == self.right_click_id:
                queue_item = item
                queue_index = i
                break
        else:
            return

        del pctl.force_queue[queue_index]
        # [trackid, position, pl_id, type, album_stage, uid_gen(), auto_stop]

        if pctl.force_queue and pctl.force_queue[0][4] == 1:
            split_queue_album(None)

        target_track_id = queue_item[0]

        pl = id_to_pl(queue_item[2])
        if pl is not None:
            pctl.active_playlist_playing = pl

        if target_track_id not in pctl.playing_playlist():
            pctl.advance()
            return

        pctl.jump(target_track_id, queue_item[1])

        if queue_item[3] == 1:  # is album type
            queue_item[4] = 1  # set as partway playing
            pctl.force_queue.insert(0, queue_item)


    def toggle_auto_stop(self):

        for item in pctl.force_queue:
            if item[5] == self.right_click_id:
                item[6] ^= True
                break

    def toggle_auto_stop_deco(self):

        enabled = False
        for item in pctl.force_queue:
            if item[5] == self.right_click_id:
                if item[6]:
                    enabled = True
                    break

        if enabled:
            return [colours.menu_text, colours.menu_background, _("Cancel Auto-Stop")]
        else:
            return [colours.menu_text, colours.menu_background, _('Auto-Stop')]

    def queue_remove_show(self, id):

        if self.right_click_id is not None:
            return True
        return False

    def right_remove_item(self):

        if self.right_click_id is None:
            show_message("Eh?")

        for u in reversed(range(len(pctl.force_queue))):
            if pctl.force_queue[u][5] == self.right_click_id:
                del pctl.force_queue[u]
                gui.pl_update += 1
                break
        else:
            show_message("Looks like it's gone now anyway")

    def toggle_pause(self):

        pctl.pause_queue ^= True

    def draw_card(self, x, y, w, h, yy, track, fqo, draw_back=False, draw_album_indicator=True):

        #text_colour = [230, 230, 230, 255]
        bg = colours.queue_background

        # if fq[i][3] == 0:

        rect = (x + 13 * gui.scale, yy, w - 28 * gui.scale, self.tab_h)

        if draw_back:
            ddt.rect(rect, colours.queue_card_background)
            bg = colours.queue_card_background

        text_colour1 = rgb_add_hls(bg, 0, 0.28, -0.15) #[255, 255, 255, 70]
        text_colour2 = [255, 255, 255, 230]
        if test_lumi(bg) < 0.2:
            text_colour1 = [0, 0, 0, 130]
            text_colour2 = [0, 0, 0, 230]

        gall_ren.render(track, (rect[0] + 4 * gui.scale, rect[1] + 4 * gui.scale), round(28 * gui.scale))

        ddt.rect((rect[0] + 4 * gui.scale, rect[1] + 4 * gui.scale, 26, 26), [0, 0, 0, 6])

        line = track.album
        if fqo[3] == 0:
            line = track.title

        if not line:
            line = clean_string(track.filename)

        line2y = yy + 14 * gui.scale

        artist_line = track.artist
        if fqo[3] == 1 and track.album_artist:
            artist_line = track.album_artist

        if fqo[3] == 0 and not artist_line:
            line2y -= 7 * gui.scale

        ddt.text((rect[0] + (40 * gui.scale), yy - 1 * gui.scale), artist_line, text_colour1, 210,
                 max_w=rect[2] - 60 * gui.scale, bg=bg)

        ddt.text((rect[0] + (40 * gui.scale), line2y), line, text_colour2, 211,
                 max_w=rect[2] - 60 * gui.scale, bg=bg)

        if draw_album_indicator:
            if fqo[3] == 1:
                if fqo[4] == 0:
                    ddt.rect((rect[0] + rect[2] - 5 * gui.scale, rect[1], 5 * gui.scale, rect[3]), [220, 130, 20, 255])
                else:
                    ddt.rect((rect[0] + rect[2] - 5 * gui.scale, rect[1], 5 * gui.scale, rect[3]), [140, 220, 20, 255])

            if fqo[6]:
                xx = rect[0] + rect[2] - 9 * gui.scale
                if fqo[3] == 1:
                    xx -= 11 * gui.scale
                ddt.rect((xx, rect[1] + 5 * gui.scale, 7 * gui.scale, 7 * gui.scale), [230, 190, 0, 255])

    def draw(self, x, y, w, h):

        yy = y

        yy += round(4 * gui.scale)

        sep_colour = alpha_blend([255, 255, 255, 11], colours.queue_background)

        if y > gui.panelY + 10 * gui.scale:  # Draw fancy light mode border
            gui.queue_frame_draw = y
        # else:
        #     if not colours.lm:
        #         ddt.rect((x, y, w, 3 * gui.scale),  colours.queue_background, True)


        yy += round(3 * gui.scale)

        box_rect = (x, yy - 6 * gui.scale, w, h)
        ddt.rect(box_rect, colours.queue_background)
        ddt.text_background_colour = colours.queue_background

        if coll(box_rect) and quick_drag and not pctl.force_queue:
            ddt.rect(box_rect, [255, 255, 255, 2])
            ddt.text_background_colour = alpha_blend([255, 255, 255, 2], ddt.text_background_colour)

        # if y < gui.panelY * 2:
        #     ddt.rect((x, y - 3 * gui.scale, w, 30 * gui.scale), colours.queue_background, True)

        if h > 40 * gui.scale:
            if not pctl.force_queue:
                if quick_drag:
                    text = _("Add to Queue")
                else:
                    text = _("Queue")
                ddt.text((x + (w // 2), y + 15 * gui.scale, 2), text, alpha_mod(colours.index_text, 200), 212)

        qb_right_click = 0

        if coll(box_rect):
            # Update scroll position
            self.scroll_position += mouse_wheel * -1
            if self.scroll_position < 0:
                self.scroll_position = 0

            if right_click:
                qb_right_click = 1


        #text_colour = [255, 255, 255, 91]
        text_colour = rgb_add_hls(colours.queue_background, 0, 0.3, -0.15)
        if test_lumi(colours.queue_background) < 0.2:
            text_colour = [0, 0, 0, 200]

        line = "Up Next:"
        if pctl.force_queue:
            #line = "Queue"
            ddt.text((x + (10 * gui.scale), yy + 2 * gui.scale), line, text_colour, 211)

        yy += 7 * gui.scale

        if len(pctl.force_queue) < 3:
            self.scroll_position = 0

        # Draw square dots to indicate view has been scrolled down
        if self.scroll_position > 0:

            ds = 3 * gui.scale
            gp = 4 * gui.scale

            ddt.rect((x + int(w / 2), yy, ds, ds), [230, 190, 0, 255])
            ddt.rect((x + int(w / 2), yy + gp, ds, ds), [230, 190, 0, 255])
            ddt.rect((x + int(w / 2), yy + gp + gp, ds, ds), [230, 190, 0, 255])

        # Draw pause icon
        if pctl.pause_queue:
            ddt.rect((x + w - 24 * gui.scale, yy + 2 * gui.scale, 3 * gui.scale, 9 * gui.scale), [230, 190, 0, 255])
            ddt.rect((x + w - 19 * gui.scale, yy + 2 * gui.scale, 3 * gui.scale, 9 * gui.scale), [230, 190, 0, 255])

        yy += 6 * gui.scale

        yy += 10 * gui.scale

        i = 0

        # Get new copy of queue if not dragging
        if not self.dragging:
            self.fq = copy.deepcopy(pctl.force_queue)
        else:
            #gui.update += 1
            gui.update_on_drag = True

        # End drag if mouse not in correct state for it
        if not mouse_down and not mouse_up:
            self.dragging = None

        if not queue_menu.active:
            self.right_click_id = None

        fq = self.fq

        list_top = yy

        i = self.scroll_position

        # Limit scroll distance
        if i > len(fq):
            self.scroll_position = len(fq)
            i = self.scroll_position

        showed_indicator = False
        list_extends = False
        x1 = x + 13 * gui.scale  # highlight position
        w1 = w - 28 * gui.scale - 10 * gui.scale


        while i < len(fq) + 1:

            # Stop drawing if past window
            if yy > window_size[1] - gui.panelBY - gui.panelY - (50 * gui.scale):
                list_extends = True
                break

            # Calculate drag collision box. Special case for first and last which extend out in y direction
            h_rect = (x + 13 * gui.scale, yy, w - 28 * gui.scale, self.tab_h + 3 * gui.scale)
            if i == len(fq):
                h_rect = (x + 13 * gui.scale, yy, w - 28 * gui.scale, self.tab_h + 3 * gui.scale + 1000 * gui.scale)
            if i == 0:
                h_rect = (0, yy - 1000 * gui.scale, w - 28 * gui.scale + 10000, self.tab_h + 3 * gui.scale + 1000 * gui.scale)


            if self.dragging is not None and coll(h_rect) and mouse_up:

                ob = None
                for u in reversed(range(len(pctl.force_queue))):

                    if pctl.force_queue[u][5] == self.dragging:
                        ob = pctl.force_queue[u]
                        pctl.force_queue[u] = None
                        break

                else:
                    self.dragging = None

                if self.dragging:
                    pctl.force_queue.insert(i, ob)
                    self.dragging = None

                for u in reversed(range(len(pctl.force_queue))):
                    if pctl.force_queue[u] is None:
                        del pctl.force_queue[u]
                        gui.pl_update += 1
                        continue

                    # Reset album in flag if not first item
                    if pctl.force_queue[u][4] == 1:
                        if u != 0:
                            pctl.force_queue[u][4] = 0

                inp.mouse_click = False
                self.draw(x, y, w, h)
                return

            if i > len(fq) - 1:
                break

            track = pctl.g(fq[i][0])

            rect = (x + 13 * gui.scale, yy, w - 28 * gui.scale, self.tab_h)

            if inp.mouse_click and coll(rect):

                self.dragging = fq[i][5]
                self.drag_start_y = mouse_position[1]
                self.drag_start_top = yy

                if d_click_timer.get() < 1:

                    if self.d_click_ref == fq[i][5]:

                        pl = id_to_pl(fq[i][5])
                        if pl is not None:
                            switch_playlist(pl)

                        pctl.show_current(playing=False, highlight=True, index=fq[i][0])
                        self.d_click_ref = None
                #else:
                self.d_click_ref = fq[i][5]

                d_click_timer.set()

            if self.dragging and coll(h_rect):
                yy += self.tab_h
                yy += 4 * gui.scale

            if qb_right_click and coll(rect):
                self.right_click_id = fq[i][5]
                qb_right_click = 2

            if middle_click and coll(rect):

                pctl.force_queue.remove(fq[i])
                gui.pl_update += 1

            if fq[i][5] == self.dragging:
                # ddt.rect_r(rect, [22, 22, 22, 255], True)
                pass
            else:

                db = False
                if fq[i][5] == self.right_click_id:
                    db = True

                self.draw_card(x, y, w, h, yy, track, fq[i], db)

                # Drag tracks from main playlist and insert ------------
                if quick_drag:

                    if x < mouse_position[0] < x + w:

                        y1 = yy - 4 * gui.scale
                        y2 = y1
                        h1 = self.tab_h // 2
                        if i == 0:
                            # Extend up if first element
                            y1 -= 5 * gui.scale
                            h1 += 10 * gui.scale

                        insert_position = None


                        if y1 < mouse_position[1] < y1 + h1:
                            ddt.rect((x1, yy - 2 * gui.scale, w1, 2 * gui.scale), colours.queue_drag_indicator_colour)
                            showed_indicator = True

                            if mouse_up:
                                insert_position = i

                        elif y2 < mouse_position[1] < y2 + self.tab_h + 5 * gui.scale:
                            ddt.rect((x1, yy + self.tab_h + 2 * gui.scale, w1, 2 * gui.scale),
                                     colours.queue_drag_indicator_colour)
                            showed_indicator = True

                            if mouse_up:
                                insert_position = i + 1

                        if insert_position is not None:

                            self.drop_tracks_insert(insert_position)

                # -----------------------------------------
                yy += self.tab_h
                yy += 4 * gui.scale

            i += 1

        # Show drag marker if mouse holding below list
        if quick_drag and not list_extends and not showed_indicator and fq and mouse_position[1] > yy - 4 * gui.scale and coll(box_rect):
            yy -= self.tab_h
            yy -= 4 * gui.scale
            ddt.rect((x1, yy + self.tab_h + 2 * gui.scale, w1, 2 * gui.scale), colours.queue_drag_indicator_colour)
            yy += self.tab_h
            yy += 4 * gui.scale

        yy += 15 * gui.scale
        if fq:
            ddt.rect((x, yy, w, 3 * gui.scale), sep_colour)
        yy += 11 * gui.scale


        # Calculate total queue duration
        duration = 0
        tracks = 0

        for item in fq:
            if item[3] == 0:
                duration += pctl.g(item[0]).length
                tracks += 1
            else:
                pl = id_to_pl(item[2])
                if pl is not None:
                    playlist = pctl.multi_playlist[pl][2]
                    i = item[1]


                    album_parent_path = pctl.g(item[0]).parent_folder_path

                    playing_track = pctl.playing_object()

                    if pl == pctl.active_playlist_playing and item[4] and playing_track and playing_track.parent_folder_path == album_parent_path:
                        i = pctl.playlist_playing_position + 1


                    elif i > len(playlist) - 1 or (playlist[i] != item[0] and item[0] in playlist):
                        i = playlist.index(item[0])

                    while i < len(playlist):
                        if pctl.g(playlist[i]).parent_folder_path != album_parent_path:
                            break

                        duration += pctl.g(playlist[i]).length
                        tracks += 1
                        i += 1

        # Show total duration text "n Tracks [0:00:00]"
        if tracks and fq:
            plural = 's'
            if tracks < 2:
                plural = ''

            line = str(tracks) + " Track" + plural + " [" + get_hms_time(duration) + "]"
            ddt.text((x + 12 * gui.scale, yy), line, text_colour, 11.5, bg=colours.queue_background)


        if self.dragging:

            fqo = None
            for item in fq:
                if item[5] == self.dragging:
                    fqo = item
                    break
            else:
                self.dragging = False

            if self.dragging:
                yyy = self.drag_start_top + (mouse_position[1] - self.drag_start_y)
                if yyy < list_top:
                    yyy = list_top
                track = pctl.g(fqo[0])
                self.draw_card(x, y, w, h, yyy, track, fqo, draw_back=True)

        # Drag and drop tracks from main playlist into queue
        if quick_drag and mouse_up and coll(box_rect) and shift_selection:

            self.drop_tracks_insert(len(fq))

        # Right click context menu in blank space
        if qb_right_click:
            if qb_right_click == 1:
                self.right_click_id = None
            queue_menu.activate(position=mouse_position)


queue_box = QueueBox()


def art_metadata_overlay(right, bottom, showc):

    if not showc:
        return

    padding = 6 * gui.scale

    if not key_shift_down:

        line = ""
        if showc[0] == 1:
            line += 'E '
        elif showc[0] == 2:
            line += 'N '
        else:
            line += 'F '

        line += str(showc[2] + 1) + "/" + str(showc[1])

        y = bottom - 40 * gui.scale

        tag_width = ddt.get_text_w(line, 12) + 12 * gui.scale
        ddt.rect_a((right - (tag_width + padding), y), (tag_width, 18 * gui.scale), [8, 8, 8, 255])
        ddt.text(((right) - (6 * gui.scale + padding), y, 1), line, [200, 200, 200, 255], 12, bg=[30, 30, 30, 255])

    else:  # Extended metadata

        line = ""
        if showc[0] == 1:
            line += 'Embedded'
        elif showc[0] == 2:
            line += 'Network'
        else:
            line += 'File'

        y = bottom - 76 * gui.scale

        tag_width = ddt.get_text_w(line, 12) + 12 * gui.scale
        ddt.rect_a((right - (tag_width + padding), y), (tag_width, 18 * gui.scale), [8, 8, 8, 255])
        ddt.text(((right) - (6 * gui.scale + padding), y, 1), line, [200, 200, 200, 255], 12, bg=[30, 30, 30, 255])

        y += 18 * gui.scale

        line = ""
        line += showc[4]
        line += " " + str(showc[3][0]) + "×" + str(showc[3][1])

        tag_width = ddt.get_text_w(line, 12) + 12 * gui.scale
        ddt.rect_a((right - (tag_width + padding), y), (tag_width, 18 * gui.scale), [8, 8, 8, 255])
        ddt.text(((right) - (6 * gui.scale + padding), y, 1), line, [200, 200, 200, 255], 12, bg=[30, 30, 30, 255])

        y += 18 * gui.scale

        line = ""
        line += str(showc[2] + 1) + "/" + str(showc[1])

        tag_width = ddt.get_text_w(line, 12) + 12 * gui.scale
        ddt.rect_a((right - (tag_width + padding), y), (tag_width, 18 * gui.scale), [8, 8, 8, 255])
        ddt.text(((right) - (6 * gui.scale + padding), y, 1), line, [200, 200, 200, 255], 12, bg=[30, 30, 30, 255])


class MetaBox:

    def l_panel(self, x, y, w, h, track, top_border=True):

        if not track:
            return

        border_colour = [255, 255, 255, 30]
        line1_colour = [255, 255, 255, 235]
        line2_colour = [255, 255, 255, 200]
        if test_lumi(colours.gallery_background) < 0.55:
            border_colour = [0, 0, 0, 30]
            line1_colour = [0, 0, 0, 200]
            line2_colour = [0, 0, 0, 230]

        rect = (x, y, w, h)

        ddt.rect(rect, colours.gallery_background)
        if top_border:
            ddt.rect((x, y, w, round(1 * gui.scale)), border_colour)
        else:
            ddt.rect((x, y + h - round(1 * gui.scale), w, round(1 * gui.scale)), border_colour)

        ddt.text_background_colour = colours.gallery_background

        insert = round(9 * gui.scale)
        border = round(2 * gui.scale)

        compact_mode = False
        if w < h * 1.9:
            compact_mode = True

        art_rect = [x + insert - 2 * gui.scale, y + insert, h - insert * 2 + 1 * gui.scale, h - insert * 2 + 1 * gui.scale]

        if compact_mode:
            art_rect[0] = x + round(w / 2 - art_rect[2] / 2) - round(1 * gui.scale) # - border

        border_rect = (art_rect[0] - border, art_rect[1] - border, art_rect[2] + (border * 2), art_rect[3] + (border * 2))

        if (inp.mouse_click or right_click) and is_level_zero(False):
            if coll(border_rect):
                if inp.mouse_click:
                    album_art_gen.cycle_offset(target_track)
                if right_click:
                    picture_menu.activate(in_reference=target_track)
            elif coll(rect):
                if inp.mouse_click:
                    pctl.show_current()
                if right_click:
                    showcase_menu.activate(track)

        ddt.rect(border_rect, border_colour)
        ddt.rect(art_rect, colours.gallery_background)
        album_art_gen.display(track, (art_rect[0], art_rect[1]), (art_rect[2], art_rect[3]))

        fields.add(border_rect)
        if coll(border_rect) and is_level_zero(True):
            showc = album_art_gen.get_info(target_track)
            art_metadata_overlay(art_rect[0] + art_rect[2] + 2 * gui.scale, art_rect[1] + art_rect[3] + 12 * gui.scale, showc)

        if not compact_mode:
            text_x = border_rect[0] + border_rect[2] + round(10 * gui.scale)
            max_w = w - (border_rect[2] + 28 * gui.scale)
            yy = y + round(15 * gui.scale)

            ddt.text((text_x, yy), track.title, line1_colour, 316, max_w=max_w)
            yy += round(20 * gui.scale)
            ddt.text((text_x, yy), track.artist, line2_colour, 14, max_w=max_w)
            yy += round(30 * gui.scale)
            ddt.text((text_x, yy), track.album, line2_colour, 14, max_w=max_w)
            yy += round(20 * gui.scale)
            ddt.text((text_x, yy), track.date, line2_colour, 14, max_w=max_w)

            gui.showed_title = True

    def lyrics(self, x, y, w, h, track):

        ddt.rect((x, y, w, h), colours.side_panel_background)
        ddt.text_background_colour = colours.side_panel_background

        if not track:
            return

        # Test for show lyric menu on right ckick
        if coll((x + 10, y, w - 10, h)):
            if right_click: # and 3 > pctl.playing_state > 0:
                gui.force_showcase_index = -1
                showcase_menu.activate(track)

        # Test for scroll wheel input
        if mouse_wheel != 0 and coll((x + 10, y, w - 10, h)):
            lyrics_ren_mini.lyrics_position += mouse_wheel * 30 * gui.scale
            if lyrics_ren_mini.lyrics_position > 0:
                lyrics_ren_mini.lyrics_position = 0
                lyric_side_top_pulse.pulse()

            gui.update += 1

        tw, th = ddt.get_text_wh(track.lyrics + "\n", 15, w - 50 * gui.scale, True)

        oth = th

        th -= h
        th += 25 * gui.scale  # Empty space buffer at end

        if lyrics_ren_mini.lyrics_position * -1 > th:
            lyrics_ren_mini.lyrics_position = th * -1
            if oth > h:
                lyric_side_bottom_pulse.pulse()

        scroll_w = 15 * gui.scale
        if gui.maximized:
            scroll_w = 17 * gui.scale

        lyrics_ren_mini.lyrics_position = mini_lyrics_scroll.draw(x + w - 17 * gui.scale, y, scroll_w, h,
                                                                  lyrics_ren_mini.lyrics_position * -1, th,
                                                                  jump_distance=160 * gui.scale) * -1

        margin = 10 * gui.scale
        if colours.lm:
            margin += 1 * gui.scale

        lyrics_ren_mini.render(pctl.track_queue[pctl.queue_step], x + margin,
                                    y + lyrics_ren_mini.lyrics_position + 13 * gui.scale,
                                    w - 50 * gui.scale,
                                    None, 0)

        ddt.rect((x, y + h - 1, w,
                  1), colours.side_panel_background)

        lyric_side_top_pulse.render(x, y, w - round(17 * gui.scale), 16 * gui.scale)
        lyric_side_bottom_pulse.render(x, y + h, w - round(17 * gui.scale), 15 * gui.scale, bottom=True)

    def draw(self, x, y, w ,h, track=None):

        ddt.rect((x, y, w, h), colours.side_panel_background)

        if not track:
            return

        # Test for show lyric menu on right ckick
        if coll((x + 10, y, w - 10, h)):
            if right_click: # and 3 > pctl.playing_state > 0:
                gui.force_showcase_index = -1
                showcase_menu.activate(track)

        if pctl.playing_state == 0:
            if not prefs.meta_persists_stop and not prefs.meta_shows_selected and not prefs.meta_shows_selected_always:
                return

        if h < 15:
            return

        # Check for lyrics if auto setting
        test_auto_lyrics(track)

        # # Draw lyrics if avaliable
        # if prefs.show_lyrics_side and pctl.track_queue \
        #             and track.lyrics != "" and h > 45 * gui.scale and w > 200 * gui.scale:
        #
        #     self.lyrics(x, y, w, h, track)

        # Draw standard metadata
        if len(pctl.track_queue) > 0:

            if pctl.playing_state == 0:
                if not prefs.meta_persists_stop and not prefs.meta_shows_selected and not prefs.meta_shows_selected_always:
                    return

            ddt.text_background_colour = colours.side_panel_background

            if coll((x + 10, y, w - 10, h)):
                # Click area to jump to current track
                if inp.mouse_click:
                    pctl.show_current()
                    gui.update += 1


            title = ""
            album = ""
            artist = ""
            ext = ""
            date = ""
            genre = ""

            margin = x + 10 * gui.scale
            if colours.lm:
                margin += 2 * gui.scale

            text_width = w - 25 * gui.scale
            tr = None

            #if pctl.playing_state < 3:

            if pctl.playing_state == 0 and prefs.meta_persists_stop:
                tr = pctl.master_library[pctl.track_queue[pctl.queue_step]]
            if pctl.playing_state == 0 and prefs.meta_shows_selected:

                if -1 < playlist_selected < len(pctl.multi_playlist[pctl.active_playlist_viewing][2]):
                    tr = pctl.g(pctl.multi_playlist[pctl.active_playlist_viewing][2][playlist_selected])

            if prefs.meta_shows_selected_always and not pctl.playing_state == 3:
                if -1 < playlist_selected < len(pctl.multi_playlist[pctl.active_playlist_viewing][2]):
                    tr = pctl.g(pctl.multi_playlist[pctl.active_playlist_viewing][2][playlist_selected])

            if tr is None:
                tr = pctl.playing_object()
            if tr is None:
                return

            title = tr.title
            album = tr.album
            artist = tr.artist
            ext = tr.file_ext
            if tr.lyrics:
                ext += ","
            date = tr.date
            genre = tr.genre

            if not title and not artist:
                title = pctl.tag_meta

            if h > 58 * gui.scale:

                block_y = y + 7 * gui.scale

                if not prefs.show_side_art:
                    block_y += 3 * gui.scale

                if title != "":
                    ddt.text((margin, block_y + 2 * gui.scale), title, colours.side_bar_line1, fonts.side_panel_line1, max_w=text_width)
                if artist != "":
                    ddt.text((margin, block_y + 23 * gui.scale), artist, colours.side_bar_line2, fonts.side_panel_line2, max_w=text_width)

                gui.showed_title = True

                if h > 140 * gui.scale:

                    block_y = y + 80 * gui.scale
                    if artist != "":
                        ddt.text((margin, block_y), album, colours.side_bar_line2,
                                 fonts.side_panel_line2, max_w=text_width)

                    if not genre == date == "":
                        line = date
                        if genre != "":
                            if line != "":
                                line += " | "
                            line += genre

                        ddt.text((margin, block_y + 20 * gui.scale), line, colours.side_bar_line2,
                                 fonts.side_panel_line2, max_w=text_width)

                    if ext != "":
                        if ext == "SPTY":
                            ext = "Spotify"
                        if ext == "RADIO":
                            ext = radiobox.playing_title
                        sp = ddt.text((margin, block_y + 40 * gui.scale), ext, colours.side_bar_line2,
                                      fonts.side_panel_line2, max_w=text_width)

                        if tr and tr.lyrics:
                            if draw_internel_link(margin + sp + 6 * gui.scale, block_y + 40 * gui.scale, "Lyrics", colours.side_bar_line2, fonts.side_panel_line2):
                                prefs.show_lyrics_showcase = True
                                enter_showcase_view(track_id=tr.index)


meta_box = MetaBox()


class PictureRender:

    def __init__(self):
        self.show = False
        self.path = ""

        self.image_data = None
        self.texture = None
        self.sdl_rect = None
        self.size = (0, 0)

    def load(self, path, box_size=None):

        if not os.path.isfile(path):
            print("NO PICTURE FILE TO LOAD")
            return

        g = io.BytesIO()
        g.seek(0)

        im = Image.open(path)
        if box_size is not None:
            im.thumbnail((box_size, box_size), Image.ANTIALIAS)

        im.save(g, 'BMP')
        g.seek(0)
        self.image_data = g
        print("Save BMP to memory")
        self.size = im.size[0], im.size[1]


    def draw(self, x, y):

        if self.show is False:
            return

        if self.image_data is not None:
            if self.texture is not None:
                SDL_DestroyTexture(self.texture)

            # Convert raw image to sdl texture
            # print("Create Texture")
            wop = rw_from_object(self.image_data)
            s_image = IMG_Load_RW(wop, 0)
            self.texture = SDL_CreateTextureFromSurface(renderer, s_image)
            SDL_FreeSurface(s_image)
            tex_w = pointer(c_int(0))
            tex_h = pointer(c_int(0))
            SDL_QueryTexture(self.texture, None, None, tex_w, tex_h)
            self.sdl_rect = SDL_Rect(round(x), round(y))
            self.sdl_rect.w = int(tex_w.contents.value)
            self.sdl_rect.h = int(tex_h.contents.value)
            self.image_data = None

        if self.texture is not None:
            self.sdl_rect.x = round(x)
            self.sdl_rect.y = round(y)
            SDL_RenderCopy(renderer, self.texture, None, self.sdl_rect)
            style_overlay.hole_punches.append(self.sdl_rect)

artist_picture_render = PictureRender()
artist_preview_render = PictureRender()


class ArtistInfoBox:

    def __init__(self):
        self.artist_on = None
        self.min_rq_timer = Timer()
        self.min_rq_timer.force_set(10)

        self.text = ""

        self.status = ""

        self.scroll_y = 0

        self.process_text_artist = ""
        self.processed_text = ""
        self.th = 0
        self.w = 0
        self.lock = False

        self.mini_box = asset_loader("mini-box.png", True)

    def manual_dl(self):

        track = pctl.playing_object()
        if track is None or not track.artist:
            show_message("No artist name found", mode="warning")
            return

        # Check if the artist has changed.
        self.artist_on = track.artist

        if not self.lock and self.artist_on:
            self.lock = True
            # self.min_rq_timer.set()

            self.scroll_y = 0
            self.status = _("Looking up...")
            self.process_text_artist = ""

            shoot_dl = threading.Thread(target=self.get_data, args=([self.artist_on, False, True]))
            shoot_dl.daemon = True
            shoot_dl.start()

    def draw(self, x, y, w, h):

        if gui.artist_panel_height > 300 and w < 500 * gui.scale:
            bio_set_small()


        if w < 300 * gui.scale:
            gui.artist_info_panel = False
            gui.update_layout()
            return

        track = pctl.playing_object()
        if track is None:
            return

        # Check if the artist has changed.
        artist = track.artist
        wait = False

        # Activate menu
        if right_click and coll((x, y ,w, h)):
            artist_info_menu.activate(in_reference=artist)

        background = colours.artist_bio_background
        text_colour = colours.artist_bio_text
        ddt.rect((x + 10, y + 5, w - 15, h - 5), background)


        if artist != self.artist_on:

            if artist == "":
                return

            if self.min_rq_timer.get() < 5:  # Limit rate
                if os.path.isfile(os.path.join(a_cache_dir, artist + '-lfm.txt')):
                    pass
                else:
                    self.status = _("Cooldown...")
                    wait = True

            if pctl.playing_time < 0.5:
                if os.path.isfile(os.path.join(a_cache_dir, artist + '-lfm.txt')):
                    pass
                else:
                    self.status = "..."
                    wait = True

            if not wait and not self.lock:
                self.lock = True
                #self.min_rq_timer.set()

                self.scroll_y = 0
                self.status = _("Loading...")

                shoot_dl = threading.Thread(target=self.get_data, args=([artist]))
                shoot_dl.daemon = True
                shoot_dl.start()


        if self.process_text_artist != self.artist_on:
            self.process_text_artist = self.artist_on

            text = self.text
            lic = ""
            link = ""

            if "<a" in text:
                text, ex = text.split('<a href="', 1)

                link, ex = ex.split('">', 1)

                lic = ex.split("</a>. ", 1)[1]

            text += "\n"

            self.urls = [(link, [200, 60, 60, 255], "L")]
            for word in text.replace("\n", " ").split(" "):
                if word.strip()[:4] == "http" or word.strip()[:4] == "www.":
                    word = word.rstrip(".")
                    if word.strip()[:4] == "www.":
                        word = "http://" + word
                    if 'bandcamp' in word:
                        self.urls.append((word.strip(), [200, 150, 70, 255], "B"))
                    elif 'soundcloud' in word:
                        self.urls.append((word.strip(), [220, 220, 70, 255], "S"))
                    elif 'twitter' in word:
                        self.urls.append((word.strip(), [80, 110, 230, 255], "T"))
                    elif 'facebook' in word:
                        self.urls.append((word.strip(), [60, 60, 230, 255], "F"))
                    elif 'youtube' in word:
                        self.urls.append((word.strip(), [210, 50, 50, 255], "Y"))
                    else:
                        self.urls.append((word.strip(), [120, 200, 60, 255], "W"))

            self.processed_text = text
            self.w = -1  # trigger text recalc

        if self.status == "Ready":

            # if self.w != w:
            #     tw, th = ddt.get_text_wh(self.processed_text, 14.5, w - 250 * gui.scale, True)
            #     self.th = th
            #     self.w = w

            text_max_w = w - gui.artist_panel_height - 55 * gui.scale

            if self.w != w:
                tw, th = ddt.get_text_wh(self.processed_text, 14.5, text_max_w - (text_max_w % 20), True)
                self.th = th
                self.w = w

            scroll_max = self.th - (h - 26)

            if coll((x, y, w, h)):
                self.scroll_y += mouse_wheel * -20
            if self.scroll_y < 0:
                self.scroll_y = 0
            if self.scroll_y > scroll_max:
                self.scroll_y = scroll_max

            right = x + w - 25 * gui.scale

            if self.th > h - 26:
                self.scroll_y = artist_info_scroll.draw(x + w - 20, y + 5, 15, h - 5,
                                                        self.scroll_y, scroll_max, True, jump_distance=250 * gui.scale)
                right -= 15
                #text_max_w -= 15

            artist_picture_render.draw(x + 20 * gui.scale, y + 10 * gui.scale)
            width = text_max_w - (text_max_w % 20)
            if width > 20 * gui.scale:
                ddt.text((x + round(gui.artist_panel_height + 15 * gui.scale), y + 14 * gui.scale, 4, width, 14000), self.processed_text, text_colour, 14.5, bg=background, range_height=h - 22 * gui.scale, range_top=self.scroll_y)

            yy = y + 12
            for item in self.urls:

                rect = (right - 2, yy - 2, 16, 16)

                fields.add(rect)
                self.mini_box.render(right, yy, alpha_mod(item[1], 100))
                if coll(rect):
                    if not inp.mouse_click:
                        gui.cursor_want = 3
                    if inp.mouse_click:
                        webbrowser.open(item[0], new=2, autoraise=True)
                    gui.pl_update += 1
                    w = ddt.get_text_w(item[0], 13)
                    xx = (right - w) - 17 * gui.scale
                    ddt.rect((xx - 10 * gui.scale, yy - 4 * gui.scale, w + 20 * gui.scale, 24 * gui.scale),
                             [15, 15, 15, 255])
                    ddt.rect((xx - 10 * gui.scale, yy - 4 * gui.scale, w + 20 * gui.scale, 24 * gui.scale),
                             [50, 50, 50, 255])

                    ddt.text((xx, yy), item[0], [250, 250, 250, 255], 13, bg=[15, 15, 15, 255])
                    self.mini_box.render(right, yy, (item[1][0] + 20, item[1][1] + 20, item[1][2] + 20, 255))
                # ddt.rect_r(rect, [210, 80, 80, 255], True)

                yy += 19 * gui.scale

        else:
            ddt.text((x + w // 2 , y + h // 2 - 7 * gui.scale , 2), self.status, [255, 255, 255, 60], 313, bg=background)


    def get_data(self, artist, get_img_path=False, force_dl=False):

        print("Load Bio Data")

        if artist is None:
            self.artist_on = artist
            self.lock = False
            return ""

        f_artist = filename_safe(artist)

        img_filename = f_artist + '-ftv-full.jpg'
        text_filename = f_artist + '-lfm.txt'
        img_filepath_lfm = os.path.join(a_cache_dir, f_artist + '-lfm.png')
        img_filepath_dcg = os.path.join(a_cache_dir, f_artist + '-dcg.jpg')
        img_filepath = os.path.join(a_cache_dir, img_filename)
        text_filepath = os.path.join(a_cache_dir, text_filename)

        image_paths = [
            os.path.join(user_directory, "artist-pictures/" + f_artist + ".png"),
            os.path.join(user_directory, "artist-pictures/" + f_artist + ".jpg"),
            img_filepath,
            img_filepath_dcg,
            img_filepath_lfm
        ]

        if get_img_path:
            for path in image_paths:
                if os.path.isfile(path):
                    print(path)
                    return path
            return ""

        # Check for cache
        try:

            if os.path.isfile(text_filepath):
                print("Load cached bio and image")

                artist_picture_render.show = False

                for path in image_paths:
                    if os.path.isfile(path):
                        filepath = path
                        artist_picture_render.load(filepath, round(gui.artist_panel_height - 20 * gui.scale))
                        artist_picture_render.show = True
                        break

                with open(text_filepath, encoding="utf-8") as f:
                    self.text = f.read()
                self.status = "Ready"
                gui.update = 2
                self.artist_on = artist
                self.lock = False

                return ""

            if not force_dl and not prefs.auto_dl_artist_data:
                #. Alt: No artist data has been downloaded (try imply this needs to be manually triggered)
                self.status = _("No artist data downloaded")
                self.artist_on = artist
                artist_picture_render.show = False
                self.lock = False
                return

            # Get new from last.fm
            #. Alt: Looking up artist data...
            self.status = _("Looking up...")
            gui.update += 1
            data = lastfm.artist_info(artist)
            self.text = ""
            if data[0] is False:
                artist_picture_render.show = False
                self.status = _("No artist bio found")
                self.artist_on = artist
                self.lock = False
                return
            else:
                if data[1]:
                    self.text = data[1]
                # cover_link = data[2]
                # Save text as file
                f = open(text_filepath, 'w', encoding='utf-8')
                f.write(self.text)
                f.close()
                print("save bio text")

                artist_picture_render.show = False
                if data[3] and prefs.enable_fanart_artist:
                    try:
                        save_fanart_artist_thumb(data[3], img_filepath)
                        artist_picture_render.load(img_filepath, round(gui.artist_panel_height - 20 * gui.scale))
                        artist_picture_render.show = True
                    except:
                        print("Failed to find image from fanart.tv")
                if not artist_picture_render.show:
                    if verify_discogs():
                        try:
                            save_discogs_artist_thumb(artist, img_filepath_dcg)
                            artist_picture_render.load(img_filepath_dcg, round(gui.artist_panel_height - 20 * gui.scale))
                            artist_picture_render.show = True
                        except:
                            print("Failed to find image from discogs")

                # Trigger reload of thumbnail in artist list box
                for key, value in list(artist_list_box.thumb_cache.items()):
                    if key is None and key == artist:
                        del artist_list_box.thumb_cache[artist]
                        break

                self.status = "Ready"
                gui.update = 2


            # if cover_link and 'http' in cover_link:
            #     # Fetch cover_link
            #     try:
            #         # print("Fetching artist image...")
            #         response = urllib.request.urlopen(cover_link)
            #         info = response.info()
            #         # print("got response")
            #         if info.get_content_maintype() == 'image':
            #
            #             f = open(filepath, 'wb')
            #             f.write(response.read())
            #             f.close()
            #
            #             # print("written file, now loading...")
            #
            #             artist_picture_render.load(filepath, round(gui.artist_panel_height - 20 * gui.scale))
            #             artist_picture_render.show = True
            #
            #             self.status = "Ready"
            #             gui.update = 2
            #     # except HTTPError as e:
            #     #     self.status = e
            #     #     print("request failed")
            #     except:
            #         print("request failed")
            #         self.status = "Request Failed"


        except:
            self.status = "Load Failed"

        self.artist_on = artist
        self.processed_text = ""
        self.process_text_artist = ""
        self.min_rq_timer.set()
        self.lock = False
        gui.update = 2
        return ""


# artist info box def
artist_info_box = ArtistInfoBox()


def artist_dl_deco():

    if artist_info_box.status == "Ready":
        return [colours.menu_text_disabled, colours.menu_background, None]
    else:
        return [colours.menu_text, colours.menu_background, None]

artist_info_menu.add(_("Download Artist Data"), artist_info_box.manual_dl, artist_dl_deco, show_test=test_artist_dl)
artist_info_menu.add(_("Clear Bio"), flush_artist_bio, pass_ref=True, show_test=test_shift)


class GuitarChords:

    def __init__(self):
        self.store_a = os.path.join(user_directory, "guitar-chords-a")  # inline format
        self.store_b = os.path.join(user_directory, "guitar-chords-b")  # 2 lines format

        self.data = []
        self.current = ""
        self.auto_scroll = True

        self.scroll_position = 0

        self.ready = {}

        self.widespace = "　"


    def clear(self, track):

        cache_title = self.get_cache_title(track)
        self.prep_folders()
        self.current = ""
        self.scroll_position = 0

        self.ready[cache_title] = 0

        for item in os.listdir(self.store_a):
            if item == cache_title:
                os.remove(os.path.join(self.store_a, cache_title))

        for item in os.listdir(self.store_b):
            if item == cache_title:
                os.remove(os.path.join(self.store_b, cache_title))


    def save_format_b(self, track):

        t = copy_from_clipboard()
        if not t:
            show_message("Clipboard has no text")
            inp.mouse_click = False
            return

        cache_title = self.get_cache_title(track)

        t = t.replace("\r", "")

        f = open(os.path.join(self.store_b, cache_title), 'w')
        f.write(t)
        f.close()


    def parse_b(self, lines):

        final = []

        last = ""

        for line in lines:


            if line == " " or line == "" or line == "\n":
                line = "                                          "

            line = line.replace("\n", "")
            line = line.replace("\r", "")

            if not last and (len(line) < 6 or \
                    "    " in line \
                    or "D " in line \
                    or "Am " in line \
                    or "Fm" in line \
                    or "Em " in line \
                    or "C " in line \
                    or "G " in line \
                    or "F " in line \
                    or "Dm" in line) and any(c.isalpha() for c in line):

                last = line
                continue

            w = list(line)
            for i, c in enumerate(w):
                if i > 0 and c == " ":
                    if w[i - 1] == " " or w[i - 1] == self.widespace:
                        w[i - 1] = self.widespace
                        w[i] = self.widespace
            line = "".join(w)

            if not last:
                final.append((line, []))
                continue


            on = 0
            mode = 0
            distance = 0
            chords = []

            while on < len(last):


                if mode == 0:
                    if last[on] == " ":
                        on += 1
                        continue
                    mode = 1
                    distance = ddt.get_text_w(line[:on], 16)

                on2 = on
                while on2 < len(last) and last[on2] != " ":
                    on2 += 1

                grab = last[on:on2]

                chords.append((grab, distance))
                mode = 0
                on = on2
                on += 1


            final.append((line, chords))
            last = ""
        self.data = final



    def prep_folders(self):

        if not os.path.exists(self.store_a):
            os.makedirs(self.store_a)

        if not os.path.exists(self.store_b):
            os.makedirs(self.store_b)

    def fetch(self, track):

        if track is None:
            return

        if self.test_ready_status(track) != 0:
            return

        cache_title = self.get_cache_title(track)

        try:

            r = requests.get('http://api.guitarparty.com/v2/songs/?query=' + urllib.parse.quote(cache_title), headers={"Guitarparty-Api-Key":"e9c0e543798c4249c24f698022ced5dd0c583ec7"})
            d = r.json()['objects'][0]['body']

            self.prep_folders()
            f = open(os.path.join(self.store_a, cache_title), "w")
            f.write(d)
            f.close()

            self.ready[cache_title] = 1

        except:
            show_message("Could not find matching track on GuitarParty")
            inp.mouse_click = False
            self.ready[cache_title] = 2

    def test_ready_status(self, track):

        # 0 not searched
        # 1 ready
        # 2 failed

        cache_title = self.get_cache_title(track)

        if cache_title in self.ready:
            if self.ready[cache_title] == 1:
                return 1
            if self.ready[cache_title] == 2:
                return 2
            return 0


        self.prep_folders()
        if cache_title in os.listdir(self.store_a):
            self.ready[cache_title] = 1
            return 1
        if cache_title in os.listdir(self.store_b):
            self.ready[cache_title] = 1
            return 1
        else:
            self.ready[cache_title] = 0
            return 0



    def parse(self, lines):

        final = []

        for line in lines:
            line = line.rstrip()
            #while "  " in line:
            #line = line.replace("  ", "　　")
            w = list(line)

            for i, c in enumerate(w):
                if i > 0 and c == " ":

                    if w[i - 1] == " " or w[i - 1] == self.widespace:
                        w[i - 1] = self.widespace
                        w[i] = self.widespace

            lyrics = []
            chords = []

            on = 0
            mode = 0

            chord_part = []

            while on < len(w):
                if mode == 0:
                    # If normal, add to lyric list
                    if w[on] != "[":
                        lyrics.append(w[on])
                        on += 1
                        continue

                    # Start of [, delete it
                    mode = 1
                    del w[on]
                    continue

                if w[on] == "]":
                    del w[on]
                    mode = 0

                    distance = 0
                    if on > 0:

                        distance = ddt.get_text_w("".join(w[:on]), 16)

                    chords.append(("".join(chord_part), distance))
                    chord_part = []
                    continue

                chord_part.append(w[on])
                del w[on]

            final.append(("".join(lyrics), chords))


        print(final)
        self.data = final

    def get_cache_title(self, track):

        name = track.artist + " " + track.title
        name = filename_safe(name, sub="_")
        return name

    def render(self, track, x, y):

        cache_title = self.get_cache_title(track)

        if self.current == cache_title:
            if not self.data:
                return False
        else:
            self.prep_folders()
            if cache_title in os.listdir(self.store_a):
                f = open(os.path.join(self.store_a, cache_title))
                lines = f.readlines()
                f.close()
                self.parse(lines)
                self.current = cache_title
                self.scroll_position = 0

            elif cache_title in os.listdir(self.store_b):
                f = open(os.path.join(self.store_b, cache_title))
                lines = f.readlines()
                f.close()
                self.parse_b(lines)
                self.current = cache_title
                self.scroll_position = 0
            else:
                return False


        if self.auto_scroll:

            if pctl.playing_length > 20:
                progress = max(0, pctl.playing_time - 12) / (pctl.playing_length - 3)
                height = len(self.data) * (18 + 15) * gui.scale

                self.scroll_position = height * progress
                #gui.update += 1
                gui.frame_callback_list.append(TestTimer(0.3))
                #time.sleep(0.032)



        if mouse_wheel and gui.panelY < mouse_position[1] < window_size[1] - gui.panelBY:
            self.scroll_position += int(mouse_wheel * 30 * gui.scale * -1)
            self.auto_scroll = False
        y = y - self.scroll_position

        if self.data:

            self.ready[cache_title] = 1

            for line in self.data:

                if window_size[0] > y > 0:
                    min_space = 0
                    for ch in line[1]:
                        xx = max(x + ch[1], min_space)

                        if len(ch[0]) == 2 and ch[0][1].lower() == "x":
                            min_space = 1 + xx + ddt.text((xx, y), ch[0], [220, 120, 240, 255], 214)
                        else:
                            min_space = 1 + xx + ddt.text((xx, y), ch[0], [140, 120, 240, 255], 213)
                y += 15 * gui.scale

                if window_size[0] > y > 0:
                    colour = colours.lyrics
                    if colours.lm:
                        colour = [30, 30, 30, 255]
                    ddt.text((x, y), line[0], colour, 16)

                y += 18 * gui.scale

            return True
        return False

# guitar chords def
gc = GuitarChords()


class RadioThumbGen:
    def __init__(self):
        self.cache = {}
        self.requests = []
        self.size = 100

    def loader(self):

        while self.requests:
            item = self.requests[0]
            del self.requests[0]
            station = item[0]
            size = item[1]
            key = (station["title"], size)
            src = None
            filename = filename_safe(station["title"])

            cache_path = os.path.join(r_cache_dir, filename + ".jpg")
            if os.path.isfile(cache_path):
                src = open(cache_path, "rb")
            else:
                cache_path = os.path.join(r_cache_dir, filename + ".png")
                if os.path.isfile(cache_path):
                    src = open(cache_path, "rb")
                else:
                    cache_path = os.path.join(r_cache_dir, filename)
                    if os.path.isfile(cache_path):
                        src = open(cache_path, "rb")

            if src:
                pass
                #print("found cached")
            elif station.get("icon") and station["icon"] not in prefs.radio_thumb_bans:
                try:
                    r = requests.get(station.get("icon"), headers={'User-Agent': t_agent}, timeout=5, stream=True)
                    if r.status_code != 200 or int(r.headers.get('Content-Length', 0)) > 2000000:
                        raise Exception("Error get radio thumb")
                except:
                    print("error get radio thumb")
                    self.cache[key] = [0, ]
                    if station.get("icon") and station.get("icon") not in prefs.radio_thumb_bans:
                        prefs.radio_thumb_bans.append(station.get("icon"))
                    continue
                src = io.BytesIO()
                length = 0
                for chunk in r.iter_content(1024):
                    src.write(chunk)
                    length += len(chunk)
                    if length > 2000000:
                        scr = None
                if src is None:
                    self.cache[key] = [0, ]
                    if station.get("icon") and station.get("icon") not in prefs.radio_thumb_bans:
                        prefs.radio_thumb_bans.append(station.get("icon"))
                    continue
                src.seek(0)
                f = open(cache_path, "wb")
                f.write(src.read())
                f.close()
                src.seek(0)
            else:
                #print("no icon")
                self.cache[key] = [0, ]
                continue

            try:
                im = Image.open(src)
                if im.mode != "RGBA":
                    im = im.convert("RGBA")
            except:
                print("malform get radio thumb")
                self.cache[key] = [0, ]
                if station.get("icon") and station.get("icon") not in prefs.radio_thumb_bans:
                    prefs.radio_thumb_bans.append(station.get("icon"))
                continue

            im = im.resize((size, size), Image.ANTIALIAS)
            g = io.BytesIO()
            g.seek(0)
            im.save(g, 'PNG')
            g.seek(0)
            wop = rw_from_object(g)
            s_image = IMG_Load_RW(wop, 0)
            self.cache[key] = [2, None, None, s_image]
            gui.update += 1

    def draw(self, station, x, y, w):
        if not station.get("title"):
            return 0
        key = (station["title"], w)

        r = self.cache.get(key)
        if r is None:
            if len(self.requests) < 3:
                self.requests.append((station, w))
                tm.ready("radio-thumb")
            return 0
        if r[0] == 2:
            texture = SDL_CreateTextureFromSurface(renderer, r[3])
            SDL_FreeSurface(r[3])
            tex_w = pointer(c_int(0))
            tex_h = pointer(c_int(0))
            SDL_QueryTexture(texture, None, None, tex_w, tex_h)
            sdl_rect = SDL_Rect(0, 0)
            sdl_rect.w = int(tex_w.contents.value)
            sdl_rect.h = int(tex_h.contents.value)
            r[2] = texture
            r[1] = sdl_rect
            r[0] = 1
        if r[0] == 1:
            r[1].x = round(x)
            r[1].y = round(y)
            SDL_RenderCopy(renderer, r[2], None, r[1])
            return 1
        return 0
radio_thumb_gen = RadioThumbGen()

def station_browse():
    radiobox.active = True
    radiobox.edit_mode = False
    radiobox.add_mode = False
    radiobox.center = True
    radiobox.tab = 1

def add_station():
    radiobox.active = True
    radiobox.edit_mode = True
    radiobox.add_mode = True
    radiobox.radio_field.text = ""
    radiobox.radio_field_title.text = ""
    radiobox.station_editing = None
    radiobox.center = True

def rename_station(item):
    station = item[1]
    radiobox.active = True
    radiobox.center = False
    radiobox.edit_mode = True
    radiobox.add_mode = False
    radiobox.radio_field.text = station["stream_url"]
    radiobox.radio_field_title.text = station.get("title", "")
    radiobox.station_editing = station

radio_context_menu.add(_("Edit..."), rename_station, pass_ref=True)
radio_context_menu.add(_("Visit Website"), visit_radio_station, visit_radio_station_site_deco, pass_ref=True, pass_ref_deco=True)

def remove_station(item):
    index = item[0]
    del pctl.radio_playlists[pctl.radio_playlist_viewing]["items"][index]

radio_context_menu.add(_("Remove"), remove_station, pass_ref=True)


class RadioView:
    def __init__(self):
        self.add_icon = asset_loader("add-station.png", True)
        self.search_icon = asset_loader("station-search.png", True)
        self.save_icon = asset_loader("save-station.png", True)
        self.menu_icon = asset_loader("radio-menu.png", True)
        self.drag = None
        self.click_point = (0, 0)

    def render(self):
        # box = int(window_size[1] * 0.4 + 120 * gui.scale)
        # box = min(window_size[0] // 2, box)

        ddt.rect((0, gui.panelY, window_size[0], window_size[1] - gui.panelY), colours.playlist_panel_background)
        #print(prefs.radio_urls)

        # Add station button
        x = window_size[0] - round(60 * gui.scale)
        y = gui.panelY + round(30 * gui.scale)
        rect = (x, y, round(25 * gui.scale), round(25 * gui.scale))
        fields.add(rect)
        colour = colours.box_button_text_highlight
        if colours.lm:
            colour = colours.vis_colour
        if coll(rect):
            if inp.mouse_click:
                add_station()
        else:
            if not colours.lm:
                colour = alpha_mod(colour, 40)
        self.add_icon.render(rect[0] + round(4 * gui.scale), rect[1] + round(4 * gui.scale), colour)

        y += round(33 * gui.scale)
        rect = (x, y, round(25 * gui.scale), round(25 * gui.scale))
        fields.add(rect)
        colour = colours.box_button_text_highlight
        if colours.lm:
            colour = colours.vis_colour
        if not coll(rect):
            if not colours.lm:
                colour = alpha_mod(colour, 40)
        else:
            if inp.mouse_click:
                station_browse()
        self.search_icon.render(rect[0] + round(4 * gui.scale), rect[1] + round(4 * gui.scale), colour)

        if pctl.radio_playlist_viewing > len(pctl.radio_playlists) - 1:
            pctl.radio_playlist_viewing = 0
        if not pctl.radio_playlists:
            return
        radios = pctl.radio_playlists[pctl.radio_playlist_viewing]["items"]


        y += round(32 * gui.scale)
        if pctl.playing_state == 3 and radiobox.loaded_station not in radios:
            rect = (x, y, round(25 * gui.scale), round(25 * gui.scale))
            fields.add(rect)
            colour = colours.box_button_text_highlight
            if colours.lm:
                colour = colours.vis_colour
            if not coll(rect):
                if not colours.lm:
                    colour = alpha_mod(colour, 40)
            else:
                if inp.mouse_click:
                    radios.append(radiobox.loaded_station)
                    toast(_("Added station to: " + pctl.radio_playlists[pctl.radio_playlist_viewing]["name"]))

            self.save_icon.render(rect[0] + round(3 * gui.scale), rect[1] + round(4 * gui.scale), colour)

        x = round(30 * gui.scale)
        y = gui.panelY + round(30 * gui.scale)
        yy = y

        bg = rgb_add_hls(colours.playlist_panel_background, 0, 0.03, -0.03)
        tbg = rgb_add_hls(colours.playlist_panel_background, 0, 0.07, -0.05)
        w = round(400 * gui.scale)
        h = round(55 * gui.scale)
        gap = round(7 * gui.scale)

        mm = (window_size[1] - (gui.panelBY + yy + h + round(15 * gui.scale))) // (h + gap) + 1

        count = 0
        scroll = pctl.radio_playlists[pctl.radio_playlist_viewing].get("scroll", 0)
        if not radiobox.active or (radiobox.active and not coll((radiobox.x, radiobox.y, radiobox.w, radiobox.h))):
            if gui.panelY < mouse_position[1] < window_size[1] - gui.panelBY and mouse_position[0] < w + round(70 * gui.scale):
                scroll += mouse_wheel * -1
        scroll = min(scroll, len(radios) - mm + 1)
        scroll = max(scroll, 0)
        if len(radios) > mm:
            scroll = radio_view_scroll.draw(round(7 * gui.scale), yy, round(15 * gui.scale), (mm * (h + gap)) - gap, scroll, len(radios) - mm + 1)
        else:
            scroll = 0

        pctl.radio_playlists[pctl.radio_playlist_viewing]["scroll"] = scroll
        insert = None

        for i, radio in enumerate(radios):
            if count == mm:
                break
            if i < scroll:
                continue
            count += 1
            rect = (x, yy, w, h)
            ddt.rect(rect, bg)
            yyy = yy
            pic_rect = (x + round(5 * gui.scale), yy + round(5 * gui.scale), h - round(10 * gui.scale), h - round(10 * gui.scale))
            ddt.rect(pic_rect, tbg)
            radio_thumb_gen.draw(radio, pic_rect[0], pic_rect[1], pic_rect[2])

            toff = h + round(2 * gui.scale)
            yyy += round(9 * gui.scale)
            ddt.text((x + toff, yyy), radio["title"], colours.side_bar_line1, 212, max_w=w-(toff + round(90 * gui.scale)))
            yyy += round(19 * gui.scale)
            ddt.text((x + toff, yyy), radio.get("country", ""), alpha_mod(colours.side_bar_line1, 170), 312, max_w=w-(toff + round(90 * gui.scale)))

            hit = False
            start_rect = (x + (w - round(40 * gui.scale)), yy + round(8 * gui.scale), h - round(15 * gui.scale), round(42 * gui.scale))
            #ddt.rect(hit_rect, [255, 255, 255, 3])
            fields.add(start_rect)
            colour = alpha_mod(colours.side_bar_line1, 27)
            if coll(start_rect):
                if inp.mouse_click:
                    radiobox.start(radio)
                    hit = True
                colour = colours.side_bar_line1
            bottom_bar1.play_button.render(x + (w - round(30 * gui.scale)), yy + round(23 * gui.scale), colour)

            extra_rect = (x + (w - round(82 * gui.scale)), yy + round(8 * gui.scale), h - round(15 * gui.scale), round(35 * gui.scale))
            #ddt.rect(extra_rect, [255, 255, 255, 2])
            fields.add(extra_rect)
            colour = alpha_mod(colours.side_bar_line1, 27)
            if coll(extra_rect):
                colour = colours.side_bar_line1
                if inp.mouse_click:
                    hit = True
                    radiobox.x = extra_rect[0] + extra_rect[2]
                    radiobox.y = extra_rect[1]
                    radio_context_menu.activate((i, radio), position=(radiobox.x, yy + round(20 * gui.scale)))
            self.menu_icon.render(x + (w - round(75 * gui.scale)), yy + round(26 * gui.scale), colour)

            #bottom_bar1.play_button.render(x + (w - round(30 * gui.scale)), yy + round(23 * gui.scale), colour)
            if mouse_up and self.drag and coll(rect):
                if radiobox.active and coll((radiobox.x, radiobox.y, radiobox.w, radiobox.h)):
                    pass
                else:
                    insert = i
                if not radiobox.active and self.drag in radios and radios.index(self.drag) < i:
                    insert += 1
            elif coll(rect) and not hit and inp.mouse_click:
                self.drag = radio
                self.click_point = copy.copy(mouse_position)

            yy += round(h + gap)

        if mouse_up and self.drag and not insert and not self.drag in radios:
            if not (radiobox.active and coll((radiobox.x, radiobox.y, radiobox.w, radiobox.h))):
                if mouse_position[1] > gui.panelY:
                    insert = len(radios)

        count = ((window_size[0] - w) / 2) + w
        boxx = round(200 * gui.scale)
        art_rect = (count - boxx / 2, window_size[1] / 3 - boxx / 2, boxx, boxx)

        if window_size[0] > round(700 * gui.scale):
            if pctl.playing_state == 3 and radiobox.loaded_station:
                r = album_art_gen.display(radiobox.dummy_track, (art_rect[0], art_rect[1]), (art_rect[2], art_rect[3]))
                if r:
                    r = radio_thumb_gen.draw(radiobox.loaded_station, art_rect[0], art_rect[1], art_rect[2])
                    # if not r:
                    #     ddt.rect(art_rect, colours.b)
            # else:
            #     ddt.rect(art_rect, [40, 40, 40, 255])

            yy = window_size[1] / 3 - boxx / 2
            yy += boxx + round(30 * gui.scale)

            if radiobox.loaded_station and pctl.playing_state == 3:
                space = window_size[0] - round(500 * gui.scale)
                ddt.text((count, yy, 2), radiobox.loaded_station.get("title", ""), [230, 230, 230, 255], 213, max_w=space)
                yy += round(25 * gui.scale)
                ddt.text((count, yy, 2), radiobox.song_key, [230, 230, 230, 255], 313, max_w=space)
                if radiobox.dummy_track.album:
                    yy += round(21 * gui.scale)
                    ddt.text((count, yy, 2), radiobox.dummy_track.album, [230, 230, 230, 255], 313, max_w=space)

        if self.drag:
            gui.update_on_drag = True

        if insert is not None:
            radios.insert(insert, "New")
            if self.drag in radios:
                radios.remove(self.drag)
            else:
                toast(_("Added station to: " + pctl.radio_playlists[pctl.radio_playlist_viewing]["name"]))

            radios[radios.index("New")] = self.drag
            self.drag = None
            gui.update += 1


radio_view = RadioView()

class Showcase:

    def __init__(self):

        self.lastfm_artist = None
        self.artist_mode = False

    def render(self):

        global right_click

        box = int(window_size[1] * 0.4 + 120 * gui.scale)
        box = min(window_size[0] // 2, box)

        hide_art = False
        if window_size[0] < 900 * gui.scale:
            hide_art = True

        x = int(window_size[0] * 0.15)
        y = int((window_size[1] / 2) - (box / 2)) - 10 * gui.scale

        if hide_art:
            box = 45 * gui.scale
        elif window_size[1] / window_size[0] > 0.7:
            x = int(window_size[0] * 0.07)

        bbg = rgb_add_hls(colours.playlist_panel_background, 0, 0.05, 0)  # [255, 255, 255, 18]
        bfg = rgb_add_hls(colours.playlist_panel_background, 0, 0.09, 0)  # [255, 255, 255, 30]
        bft = colours.grey(235)
        bbt = colours.grey(200)

        t1 = colours.grey(250)

        gui.vis_4_colour = None
        light_mode = False
        if colours.lm:
            bbg = colours.vis_colour
            bfg = alpha_blend([255, 255, 255, 60], colours.vis_colour)
            bft = colours.grey(250)
            bbt = colours.grey(245)
        elif prefs.art_bg and prefs.bg_showcase_only:
            bbg = [255, 255, 255, 18]
            bfg = [255, 255, 255, 30]
            bft = [255, 255, 255, 250]
            bbt = [255, 255, 255, 200]

        if test_lumi(colours.playlist_panel_background) < 0.7:
            light_mode = True
            t1 = colours.grey(30)
            gui.vis_4_colour = [40, 40, 40, 255]

        ddt.rect((0, gui.panelY, window_size[0], window_size[1] - gui.panelY), colours.playlist_panel_background)

        if prefs.bg_showcase_only and prefs.art_bg:
            style_overlay.display()

            # Draw textured background
            if not light_mode and not colours.lm and prefs.showcase_overlay_texture:
                rect = SDL_Rect()
                rect.x = 0
                rect.y = 0
                rect.w = 300
                rect.h = 300

                xx = 0
                yy = 0
                while yy < window_size[1]:
                    xx = 0
                    while xx < window_size[0]:
                        rect.x = xx
                        rect.y = yy
                        SDL_RenderCopy(renderer, overlay_texture_texture, None, rect)
                        xx += 300
                    yy += 300


        if prefs.bg_showcase_only and prefs.art_bg:
            ddt.alpha_bg = True
            ddt.force_gray = True


        # if not prefs.shuffle_lock:
        #     if draw.button(_("Return"), 25 * gui.scale, window_size[1] - gui.panelBY - 40 * gui.scale,
        #                    text_highlight_colour=bft, text_colour=bbt, backgound_colour=bbg,
        #                    background_highlight_colour=bfg):
        #         gui.switch_showcase_off = True
        #         gui.update += 1
        #         gui.update_layout()


        #ddt.force_gray = True

        if pctl.playing_state == 3 and not radiobox.dummy_track.title:

            if not pctl.tag_meta:
                y = int(window_size[1] / 2) - 60 - gui.scale
                ddt.text((window_size[0] // 2, y, 2), pctl.url, colours.side_bar_line2, 317)
            else:
                w = window_size[0] - (x + box) - 30 * gui.scale
                x = int((window_size[0]) / 2)

                y = int(window_size[1] / 2) - 60 - gui.scale
                ddt.text((x, y, 2), pctl.tag_meta, colours.side_bar_line1, 216, w)

        else:

            if len(pctl.track_queue) < 1:
                ddt.alpha_bg = False
                return

            # if draw.button("Return", 20, gui.panelY + 5, bg=colours.grey(30)):
            #     pass

            if prefs.bg_showcase_only and prefs.art_bg:
                ddt.alpha_bg = True
                ddt.force_gray = True

            if gui.force_showcase_index >= 0:
                if draw.button(_("Playing"), 25 * gui.scale, gui.panelY + 20 * gui.scale, text_highlight_colour=bft,
                               text_colour=bbt, background_colour=bbg, background_highlight_colour=bfg):
                    gui.force_showcase_index = -1
                    ddt.force_gray = False

            if gui.force_showcase_index >= 0:
                index = gui.force_showcase_index
                track = pctl.master_library[index]
            else:

                if pctl.playing_state == 3:
                    track = radiobox.dummy_track
                else:
                    index = pctl.track_queue[pctl.queue_step]
                    track = pctl.master_library[index]

            if not hide_art:

                # Draw frame around art box
                # drop_shadow.render(x + 5 * gui.scale, y + 5 * gui.scale, box + 10 * gui.scale, box + 10 * gui.scale)
                ddt.rect((x - round(2 * gui.scale), y - round(2 * gui.scale), box + round(4 * gui.scale),
                          box + round(4 * gui.scale)), [60, 60, 60, 135])
                ddt.rect((x, y, box, box), colours.playlist_panel_background)
                rect = SDL_Rect(round(x), round(y), round(box), round(box))
                style_overlay.hole_punches.append(rect)

                # Draw album art in box
                album_art_gen.display(track, (x, y), (box, box))

                # Click art to cycle
                if coll((x, y, box, box)):
                    if inp.mouse_click is True:
                        album_art_gen.cycle_offset(track)
                    if right_click:
                        picture_menu.activate(in_reference=track)
                        right_click = False

            # Check for lyrics if auto setting
            test_auto_lyrics(track)

            gui.draw_vis4_top = False

            if gui.panelY < mouse_position[1] < window_size[1] - gui.panelBY:
                if mouse_wheel != 0:
                    lyrics_ren.lyrics_position += mouse_wheel * 35 * gui.scale
                if right_click:
                    # track = pctl.playing_object()
                    if track != None:
                        showcase_menu.activate(track)

            gcx = x + box + int(window_size[0] * 0.15) + 10 * gui.scale
            gcx -= 100 * gui.scale

            timed_ready = False
            if True and prefs.show_lyrics_showcase:
                timed_ready = timed_lyrics_ren.generate(track)

            if timed_ready and track.lyrics:

                if not prefs.guitar_chords or gc.test_ready_status(track) != 1:

                    line = _("Prefer synced")
                    if prefs.prefer_synced_lyrics:
                        line = _("Prefer static")
                    if draw.button(line, 25 * gui.scale, window_size[1] - gui.panelBY - 70 * gui.scale,
                                   text_highlight_colour=bft, text_colour=bbt, background_colour=bbg,
                                   background_highlight_colour=bfg):
                        prefs.prefer_synced_lyrics ^= True

                timed_ready = prefs.prefer_synced_lyrics

            if prefs.guitar_chords and track.title and prefs.show_lyrics_showcase and gc.render(track, gcx, y):

                if not gc.auto_scroll:
                    if draw.button(_("Auto-Scroll"), 25 * gui.scale, window_size[1] - gui.panelBY - 70 * gui.scale,
                                   text_highlight_colour=bft, text_colour=bbt, background_colour=bbg,
                                   background_highlight_colour=bfg):
                        gc.auto_scroll = True

            elif True and prefs.show_lyrics_showcase and timed_ready:
                w = window_size[0] - (x + box) - round(30 * gui.scale)
                timed_lyrics_ren.render(track.index, gcx, y, w=w)

            elif track.lyrics == "" or not prefs.show_lyrics_showcase:

                w = window_size[0] - (x + box) - round(30 * gui.scale)
                x = int(x + box + (window_size[0] - x - box) / 2)

                if hide_art:
                    x = window_size[0] // 2

                #x = int((window_size[0]) / 2)
                y = int(window_size[1] / 2) - round(60 * gui.scale)

                if prefs.showcase_vis and prefs.backend == 1:
                    y -= round(30 * gui.scale)

                if track.artist == "" and track.title == "":

                    ddt.text((x, y, 2), clean_string(track.filename), t1, 216, w)

                else:

                    ddt.text((x, y, 2), track.artist, t1, 20, w)

                    y += round(48 * gui.scale)

                    if window_size[0] < 700 * gui.scale:
                        if len(track.title) < 30:
                            ddt.text((x, y, 2), track.title, t1, 220, w)
                        elif len(track.title) < 40:
                            ddt.text((x, y, 2), track.title, t1, 217, w)
                        else:
                            ddt.text((x, y, 2), track.title, t1, 213, w)

                    else:
                        if len(track.title) < 35:
                            ddt.text((x, y, 2), track.title, t1, 220, w)
                        elif len(track.title) < 50:
                            ddt.text((x, y, 2), track.title, t1, 219, w)
                        else:
                            ddt.text((x, y, 2), track.title, t1, 216, w)


                gui.spec4_rec.x = x - (gui.spec4_rec.w // 2)
                gui.spec4_rec.y = y + round(50 * gui.scale)


                if prefs.showcase_vis and window_size[1] > 369 and not search_over.active and not (spot_ctl.coasting or spot_ctl.playing):

                    if gui.message_box or not is_level_zero(include_menus=True):
                        self.render_vis()
                    else:
                        gui.draw_vis4_top = True

            else:
                x += box + int(window_size[0] * 0.15) + 10 * gui.scale
                x -= 100 * gui.scale
                w = window_size[0] - x - 30 * gui.scale

                if key_up_press and not (key_ctrl_down or key_shift_down or key_shiftr_down):
                    lyrics_ren.lyrics_position += 35 * gui.scale
                if key_down_press and not (key_ctrl_down or key_shift_down or key_shiftr_down):
                    lyrics_ren.lyrics_position -= 35 * gui.scale

                lyrics_ren.test_update(track)
                tw, th = ddt.get_text_wh(lyrics_ren.text + "\n", 17,
                                         w, True)



                if lyrics_ren.lyrics_position < th * -1 + 100 * gui.scale:
                    lyrics_ren.lyrics_position = th * -1 + 100 * gui.scale
                if lyrics_ren.lyrics_position > 70 * gui.scale:
                    lyrics_ren.lyrics_position = 70 * gui.scale

                lyrics_ren.render(x,
                                  y + lyrics_ren.lyrics_position,
                                  w,
                                  int(window_size[1] - 100 * gui.scale),
                                  0)
        ddt.alpha_bg = False
        ddt.force_gray = False

    def render_vis(self, top=False):

        SDL_SetRenderTarget(renderer, gui.spec4_tex)
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0)
        SDL_RenderClear(renderer)

        bx = 0
        by = 50 * gui.scale

        if gui.vis_4_colour is not None:
            SDL_SetRenderDrawColor(renderer, gui.vis_4_colour[0], gui.vis_4_colour[1], gui.vis_4_colour[2], gui.vis_4_colour[3])

        if (pctl.playing_time < 0.5 and (pctl.playing_state == 1 or pctl.playing_state == 3)) or (pctl.playing_state == 0 and gui.spec4_array.count(0) != len(gui.spec4_array)):
            gui.update = 2
            gui.level_update = True

            for i in range(len(gui.spec4_array)):
                gui.spec4_array[i] -= 0.1
                if gui.spec4_array[i] < 0:
                    gui.spec4_array[i] = 0

        if not top and (pctl.playing_state == 1 or pctl.playing_state == 3):
            gui.update = 2

        slide = 0.7
        for i, bar in enumerate(gui.spec4_array):

            # We wont draw higher bars that may not move
            if i > 40:
                break

            # Scale input amplitude to pixel distance (Applying a slight exponentional)
            dis = (2 + math.pow(bar / (2 + slide), 1.5))
            slide -= 0.03  # Set a slight bias for higher bars

            # Define colour for bar
            if gui.vis_4_colour is None:
                set_colour(hsl_to_rgb(0.7 + min(0.15, (bar / 150)) + pctl.total_playtime / 300, min(0.9, 0.7 + (dis / 300)), min(0.9, 0.7 + (dis / 600))))

            # Define bar size and draw
            gui.bar4.x = int(bx)
            gui.bar4.y = round(by - dis * gui.scale)
            gui.bar4.w = round(2 * gui.scale)
            gui.bar4.h = round(dis * 2 * gui.scale)

            SDL_RenderFillRect(renderer, gui.bar4)

            # Set distance between bars
            bx += 8 * gui.scale

        if top:
            SDL_SetRenderTarget(renderer, None)
        else:
            SDL_SetRenderTarget(renderer, gui.main_texture)

        #SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND)
        SDL_RenderCopy(renderer, gui.spec4_tex, None, gui.spec4_rec)



showcase = Showcase()


# Animates colour between two colours
class ColourPulse2:

    def __init__(self):

        self.timer = Timer()
        self.in_timer = Timer()
        self.out_timer = Timer()
        self.out_timer.start = 0
        self.active = False

    def get(self, hit, on, off, low_hls, high_hls):

        if on:

            return high_hls
            # rgb = colorsys.hls_to_rgb(high_hls[0], high_hls[1], high_hls[2])
            # return [int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255), 255]
        if off:

            return low_hls
            # rgb = colorsys.hls_to_rgb(low_hls[0], low_hls[1], low_hls[2])
            # return [int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255), 70]

        ani_time = 0.15

        if hit is True and self.active is False:
            self.active = True
            self.in_timer.set()

            out_time = self.out_timer.get()
            if out_time < ani_time:
                self.in_timer.force_set(ani_time - out_time)

        elif hit is False and self.active is True:
            self.active = False
            self.out_timer.set()

            in_time = self.in_timer.get()
            if in_time < ani_time:
                self.out_timer.force_set(ani_time - in_time)


        pro = 0.5
        if self.active:
            time = self.in_timer.get()
            if time <= 0:
                pro = 0
            elif time >= ani_time:
                pro = 1
            else:
                pro = time / ani_time
                gui.update = 2
        else:
            time = self.out_timer.get()
            if time <= 0:
                pro = 1
            elif time >= ani_time:
                pro = 0
            else:
                pro = 1 - (time / ani_time)
                gui.update = 2

        return colour_slide(low_hls, high_hls, pro, 1)

cctest = ColourPulse2()


class ViewBox:

    def __init__(self, reload=False):
        self.x = 0
        self.y = gui.panelY
        self.w = 52 * gui.scale
        self.h = 295 * gui.scale #257
        self.active = False

        self.border = 3 * gui.scale

        self.tracks_img = asset_loader("tracks.png", True)
        self.side_img = asset_loader("tracks+side.png", True)
        self.gallery1_img = asset_loader("gallery1.png", True)
        self.gallery2_img = asset_loader("gallery2.png", True)
        self.combo_img = asset_loader("combo.png", True)
        self.lyrics_img = asset_loader("lyrics.png", True)
        self.gallery2_img = asset_loader("gallery2.png", True)
        self.radio_img = asset_loader("radio.png", True)
        self.col_img = asset_loader("col.png", True)
        self.artist_img = asset_loader("artist.png", True)

        # _ .15 0
        self.tracks_colour = ColourPulse2() #(0.5) # .5 .6 .75
        self.side_colour = ColourPulse2() #(0.55) # .55 .6 .75
        self.gallery1_colour = ColourPulse2() #(0.6) # .6 .6 .75
        self.radio_colour = ColourPulse2() #(0.6) # .6 .6 .75
        #self.combo_colour = ColourPulse(0.75)
        self.lyrics_colour = ColourPulse2() #(0.7)
        #self.gallery2_colour = ColourPulse(0.65)
        self.col_colour = ColourPulse2() #(0.14)
        self.artist_colour = ColourPulse2() #(0.2)

        self.on_colour = [255, 190, 50, 255]
        self.over_colour = [255, 190, 50, 255]
        self.off_colour = colours.grey(40)

        if not reload:
            gui.combo_was_album = False

    def activate(self, x):
        self.x = x
        self.active = True
        self.clicked = False

        self.tracks_colour.out_timer.force_set(10)
        self.side_colour.out_timer.force_set(10)
        self.gallery1_colour.out_timer.force_set(10)
        self.radio_colour.out_timer.force_set(10)
        #self.combo_colour.out_timer.force_set(10)
        self.lyrics_colour.out_timer.force_set(10)
        #self.gallery2_colour.out_timer.force_set(10)
        self.col_colour.out_timer.force_set(10)
        self.artist_colour.out_timer.force_set(10)

        self.tracks_colour.active = False
        self.side_colour.active = False
        self.gallery1_colour.active = False
        self.radio_colour.active = False
        #self.combo_colour.active = False
        self.lyrics_colour.active = False
        #self.gallery2_colour.active = False
        self.col_colour.active = False
        self.artist_colour.active = False

        self.col_force_off = False

        #gui.level_2_click = False
        gui.update = 2

    def button(self, x, y, asset, test, colour_get=None, name="Unknown", animate=True, low=0, high=0):

        on = test()
        rect = [x - 8 * gui.scale,
                y - 8 * gui.scale,
                asset.w + 16 * gui.scale,
                asset.h + 16 * gui.scale]
        fields.add(rect)

        if on:
            colour = self.on_colour

        else:
            colour = self.off_colour


        fun = None
        col = False
        if coll(rect):

            tool_tip.test(x + asset.w + 10 * gui.scale, y - 15 * gui.scale, name)

            col = True
            if gui.level_2_click:
                fun = test
            if colour_get is None:
                colour = self.over_colour

        colour = colour_get.get(col, on, not on and not animate, low, high)

            # if "+" in name:
            #
            #     colour = cctest.get(col, on, [0, 0.2, 0.0], [0, 0.8, 0.8])

        # if not on and not animate:
        #     colour = self.off_colour

        asset.render(x, y, colour)

        return fun


    def tracks(self, hit=False):

        if hit is False:
            return album_mode is False and \
                   gui.combo_mode is False and \
                   gui.rsp is False

        elif not (album_mode is False and \
                   gui.combo_mode is False and \
                   gui.rsp is False):
            if x_menu.active:
                x_menu.close_next_frame = True

        view_tracks()

    def side(self, hit=False):

        if hit is False:
            return album_mode is False and \
                   gui.combo_mode is False and \
                   gui.rsp is True
        elif not (album_mode is False and \
                   gui.combo_mode is False and \
                   gui.rsp is True):
            if x_menu.active:
                x_menu.close_next_frame = True

        view_standard_meta()

    def gallery1(self, hit=False):

        if hit is False:
            return album_mode is True #and gui.show_playlist is True

        if album_mode and not gui.combo_mode:
            gui.hide_tracklist_in_gallery ^= True
            gui.rspw = gui.pref_gallery_w
            gui.update_layout()
            #x_menu.active = False
            x_menu.close_next_frame = True
            #Menu.active = False
            return

        if x_menu.active:
            x_menu.close_next_frame = True

        force_album_view()

    def radio(self, hit=False):

        if hit is False:
            return gui.radio_view

        if not gui.radio_view:
            enter_radio_view()
        else:
            exit_combo(restore=True)

        if x_menu.active:
            x_menu.close_next_frame = True

    def lyrics(self, hit=False):

        if hit is False:
            return gui.showcase_mode

        if not gui.showcase_mode:
            enter_showcase_view()
        else:
            exit_combo(restore=True)
        if x_menu.active:
            x_menu.close_next_frame = True

    def col(self, hit=False):

        if hit is False:
            return gui.set_mode

        if not gui.set_mode:
            if gui.combo_mode:
                exit_combo()

        if album_mode and gui.plw < 550 * gui.scale:
            toggle_album_mode()

        toggle_library_mode()

    def artist_info(self, hit=False):

        if hit is False:
            return gui.artist_info_panel

        gui.artist_info_panel ^= True
        gui.update_layout()

    def render(self):

        if prefs.shuffle_lock:
            self.active = False
            self.clicked = False
            return

        if not self.active:
            return

        #rect = [self.x, self.y, self.w, self.h]
        #if x_menu.clicked or inp.mouse_click:
        if self.clicked:
            gui.level_2_click = True
        self.clicked = False

        x = self.x - 40 * gui.scale

        vr = [x, gui.panelY, self.w, self.h]
        #vr = [x, gui.panelY, 52 * gui.scale, 220 * gui.scale]

        border_colour = colours.menu_tab#colours.grey(30)
        if colours.lm:
            ddt.rect((vr[0], vr[1], vr[2] + round(4 * gui.scale), vr[3]), border_colour)
        else:
            ddt.rect((vr[0] - round(4 * gui.scale), vr[1], vr[2] + round(8 * gui.scale), vr[3] + round(4 * gui.scale)),
                     border_colour)
        ddt.rect(vr, colours.menu_background)

        x = x + 7 * gui.scale
        y = gui.panelY + 14 * gui.scale

        func = None

        #low = (0, .15, 0)
        #low = (0, .40, 0)
        #low = rgb_to_hls(*alpha_blend(colours.menu_icons, colours.menu_background)[:3])  # fix me
        low = alpha_blend(colours.menu_icons, colours.menu_background)

        # if colours.lm:
        #     low = (0, 0.5, 0)

        # ----
        #print(hls_to_rgb(.55, .6, .75))
        high = [76, 183, 229, 255] #(.55, .6, .75)
        if colours.lm:
            #high = (.55, .75, .75)
            high = [63, 63, 63, 255]

        test = self.button(x, y, self.side_img, self.side, self.side_colour, _("Tracks + Art"), low=low, high=high)
        if test is not None:
            func = test



        # ----

        y += 40 * gui.scale

        high = [76, 137, 229, 255] #(.6, .6, .75)
        if colours.lm:
            #high = (.6, .80, .85)
            high = [63, 63, 63, 255]

        if gui.hide_tracklist_in_gallery:
            test = self.button(x - round(1 * gui.scale), y, self.gallery2_img, self.gallery1, self.gallery1_colour, _("Gallery"), low=low,
                               high=high)
        else:
            test = self.button(x, y, self.gallery1_img, self.gallery1, self.gallery1_colour, _("Gallery"), low=low, high=high)
        if test is not None:
            func = test


        # ---

        y += 40 * gui.scale

        high = [76, 229, 229, 255]
        if colours.lm:
            #high = (.5, .7, .65)
            high = [63, 63, 63, 255]

        test = self.button(x + 3 * gui.scale, y, self.tracks_img, self.tracks, self.tracks_colour, _("Tracks only"), low=low, high=high)
        if test is not None:
            func = test

        # ---

        y += 45 * gui.scale

        high = [107, 76, 229, 255]
        if colours.lm:
            #high = (.7, .75, .75)
            high = [63, 63, 63, 255]

        test = self.button(x + 4 * gui.scale, y, self.lyrics_img, self.lyrics, self.lyrics_colour, _("Showcase + Lyrics"), low=low, high=high)
        if test is not None:
            func = test

        # --

        y += 40 * gui.scale

        high = [92, 86, 255, 255]
        if colours.lm:
            #high = (.7, .75, .75)
            high = [63, 63, 63, 255]

        test = self.button(x + 3 * gui.scale, y, self.radio_img, self.radio, self.radio_colour, _("Radio"), low=low, high=high)
        if test is not None:
            func = test

        # --

        y += 45 * gui.scale

        high = [229, 205, 76, 255]
        if colours.lm:
            #high = (.9, .75, .65)
            high = [63, 63, 63, 255]

        test = self.button(x + 5 * gui.scale, y, self.col_img, self.col, self.col_colour, _("Toggle columns"), False, low=low, high=high)
        if test is not None:
            func = test

        # --

        y += 41 * gui.scale

        high = [198, 229, 76, 255]
        if colours.lm:
            #high = (.2, .6, .75)
            high = [63, 63, 63, 255]

        if gui.scale == 1.25:
            x-= 1

        test = self.button(x + 2 * gui.scale, y, self.artist_img, self.artist_info, self.artist_colour, _("Toggle artist info"), False, low=low, high=high)
        if test is not None:
            func = test

        if func is not None:
            func(True)

        if gui.level_2_click and coll(vr):
            x_menu.clicked = False

        gui.level_2_click = False
        if not x_menu.active:
            self.active = False

view_box = ViewBox()


class DLMon:

    def __init__(self):

        self.ticker = Timer()
        self.ticker.force_set(8)

        self.watching = {}
        self.ready = set()
        self.done = set()

    def scan(self):

        if len(self.watching) == 0:
            if self.ticker.get() < 10:
                return
        else:
            if self.ticker.get() < 2:
                return

        self.ticker.set()

        for downloads in download_directories:

            for item in os.listdir(downloads):

                path = os.path.join(downloads, item)

                if path in self.done:
                    continue

                if path in self.ready and not os.path.exists(path):
                    del self.ready[path]
                    continue

                if path in self.watching and not os.path.exists(path):
                    del self.watching[path]
                    continue

                #stamp = os.stat(path)[stat.ST_MTIME]
                stamp = os.path.getmtime(path)

                min_age = (time.time() - stamp) / 60
                ext = os.path.splitext(path)[1][1:].lower()

                if msys and "TauonMusicBox" in path:
                    continue

                if min_age < 240 and os.path.isfile(path) and ext in Archive_Formats:
                    size = os.path.getsize(path)
                    #print("Check: " + path)
                    if path in self.watching:
                        # Check if size is stable, then scan for audio files
                        #print("watching...")
                        if size == self.watching[path] and size != 0:
                            #print("scan")
                            del self.watching[path]

                            # Check if folder to extract to exists
                            split = os.path.splitext(path)
                            target_dir = split[0]
                            if prefs.extract_to_music and music_directory is not None:
                                target_dir = os.path.join(music_directory, os.path.basename(target_dir))

                            if os.path.exists(target_dir):
                                pass
                                #print("Target folder for archive already exists")

                            elif archive_file_scan(path, DA_Formats, launch_prefix) >= 0.4:
                                self.ready.add(path)
                                gui.update += 1
                                #print("Archive detected as music")
                            else:
                                pass
                                #print("Archive rejected as music")
                            self.done.add(path)
                        else:
                            #print("update.")
                            self.watching[path] = size
                    else:
                        self.watching[path] = size
                        #print("add.")

                elif min_age < 60 and os.path.isdir(path) and path not in quick_import_done and "encode-output" not in path:
                    size = get_folder_size(path)
                    if path in self.watching:
                        # Check if size is stable, then scan for audio files
                        if size == self.watching[path]:
                            del self.watching[path]
                            if folder_file_scan(path, DA_Formats) > 0.5:

                                # Check if folder not already imported
                                imported = False
                                for pl in pctl.multi_playlist:
                                    for i in pl[2]:
                                        if path.replace("\\", "/") == pctl.master_library[i].fullpath[:len(path)]:
                                            imported = True
                                        if imported:
                                            break
                                    if imported:
                                        break
                                else:
                                    self.ready.add(path)
                                gui.update += 1
                            self.done.add(path)
                        else:
                            self.watching[path] = size
                    else:
                        self.watching[path] = size
                else:
                    self.done.add(path)

        if len(self.ready) > 0:
            temp = set()
            # print(quick_import_done)
            # print(self.ready)
            for item in self.ready:
                if item not in quick_import_done:
                    if os.path.exists(path):
                        temp.add(item)
                # else:
                #     print("FILE IMPORTED")
            self.ready = temp

        if len(self.watching) > 0:
            gui.update += 1

dl_mon = DLMon()
tauon.dl_mon = dl_mon

def dismiss_dl():

    dl_mon.ready.clear()
    dl_mon.done.update(dl_mon.watching)
    dl_mon.watching.clear()

dl_menu.add("Dismiss", dismiss_dl)


class Fader:

    def __init__(self):

        self.total_timer = Timer()
        self.timer = Timer()
        self.ani_duration = 0.3
        self.state = 0  # 0 = Want off, 1 = Want fade on
        self.a = 0  # The fade progress (0-1)

    def render(self):

        if self.total_timer.get() > self.ani_duration:
            self.a = self.state
        elif self.state == 0:
            t = self.timer.hit()
            self.a -= t / self.ani_duration
            self.a = max(0, self.a)
        elif self.state == 1:
            t = self.timer.hit()
            self.a += t / self.ani_duration
            self.a = min(1, self.a)

        rect = [0, 0, window_size[0], window_size[1]]
        ddt.rect(rect, [0, 0, 0, int(110 * self.a)])

        if not (self.a == 0 or self.a == 1):
            gui.update += 1

    def rise(self):

        self.state = 1
        self.timer.hit()
        self.total_timer.set()

    def fall(self):

        self.state = 0
        self.timer.hit()
        self.total_timer.set()


fader = Fader()

class EdgePulse:

    def __init__(self):

        self.timer = Timer()
        self.timer.force_set(10)
        self.ani_duration = 0.5

    def render(self, x, y, w, h, r=200, g=120, b=0):
        r = colours.pluse_colour[0]
        g = colours.pluse_colour[1]
        b = colours.pluse_colour[2]
        time = self.timer.get()
        if time < self.ani_duration:
            alpha = 255 - int(255 * (time / self.ani_duration))
            ddt.rect((x, y, w, h), [r, g, b, alpha])
            gui.update = 2
            return True
        else:
            return False

    def pulse(self):
        self.timer.set()

class EdgePulse2:

    def __init__(self):

        self.timer = Timer()
        self.timer.force_set(10)
        self.ani_duration = 0.22

    def render(self, x, y, w, h, bottom=False):

        time = self.timer.get()
        if time < self.ani_duration:

            if bottom:
                if mouse_wheel > 0:
                    self.timer.force_set(10)
                    return
            else:
                if mouse_wheel < 0:
                    self.timer.force_set(10)
                    return

            alpha = 30 - int(25 * (time / self.ani_duration))
            h_off = (h // 5) * (time / self.ani_duration) * 4

            if colours.lm:
                colour = (0, 0, 0, alpha)
            else:
                colour = (255, 255, 255, alpha)

            if not bottom:
                ddt.rect((x, y, w, h - h_off), colour)
            else:
                ddt.rect((x, y - (h - h_off), w, h - h_off), colour)
            gui.update = 2
            return True
        else:
            return False

    def pulse(self):
        self.timer.set()

edge_playlist2 = EdgePulse2()
bottom_playlist2 = EdgePulse2()
gallery_pulse_top = EdgePulse2()
tab_pulse = EdgePulse()
lyric_side_top_pulse = EdgePulse2()
lyric_side_bottom_pulse = EdgePulse2()



def download_img(link, target_folder, track):

    try:
        response = urllib.request.urlopen(link)
        info = response.info()
        if info.get_content_maintype() == 'image':
            if info.get_content_subtype() == 'jpeg':
                save_target = os.path.join(target_dir, 'image.jpg')
                f = open(save_target, 'wb')
                f.write(response.read())
                f.close()
                #clear_img_cache()
                clear_track_image_cache(track)

            elif info.get_content_subtype() == 'png':
                save_target = os.path.join(target_dir, 'image.png')
                f = open(save_target, 'wb')
                f.write(response.read())
                f.close()
                #clear_img_cache()
                clear_track_image_cache(track)
            else:
                show_message("Image types other than PNG or JPEG are currently not supported", mode='warning')
        else:
            show_message("The link does not appear to refer to an image file.", mode='warning')
        gui.image_downloading = False

    except Exception as e:
        show_message("Image download failed.", str(e), mode='warning')
        gui.image_downloading = False


def display_you_heart(x, yy, just=0):

    rect = [x - 1 * gui.scale, yy - 4 * gui.scale, 15 * gui.scale, 17 * gui.scale]
    gui.heart_fields.append(rect)
    fields.add(rect, update_playlist_call)
    if coll(rect) and not track_box:
        gui.pl_update += 1
        w = ddt.get_text_w("You", 13)
        xx = (x - w) - 5 * gui.scale

        if just == 1:
            xx += w + 15 * gui.scale

        ty = yy - 28 * gui.scale
        tx = xx
        if ty < gui.panelY + 5 * gui.scale:
            ty = gui.panelY + 5 * gui.scale
            tx -= 20 * gui.scale

        # ddt.rect_r((xx - 1 * gui.scale, yy - 26 * gui.scale - 1 * gui.scale, w + 10 * gui.scale + 2 * gui.scale, 19 * gui.scale + 2 * gui.scale), [50, 50, 50, 255], True)
        ddt.rect((tx - 5 * gui.scale, ty, w + 20 * gui.scale, 24 * gui.scale), [15, 15, 15, 255])
        ddt.rect((tx - 5 * gui.scale, ty, w + 20 * gui.scale, 24 * gui.scale), [35, 35, 35, 255])
        ddt.text((tx + 5 * gui.scale, ty + 4 * gui.scale), "You", [250, 250, 250, 255], 13, bg=[15, 15, 15, 255])

    heart_row_icon.render(x,
                          yy, [244, 100, 100, 255])


def display_friend_heart(x, yy, name, just=0):

    heart_row_icon.render(x,
                          yy, heart_colours.get(name))

    rect = [x - 1, yy - 4, 15 * gui.scale, 17 * gui.scale]
    gui.heart_fields.append(rect)
    fields.add(rect, update_playlist_call)
    if coll(rect) and not track_box:
        gui.pl_update += 1
        w = ddt.get_text_w(name, 13)
        xx = (x - w) - 5 * gui.scale

        if just == 1:
            xx += w + 15 * gui.scale

        ty = yy - 28 * gui.scale
        tx = xx
        if ty < gui.panelY + 5 * gui.scale:
            ty = gui.panelY + 5 * gui.scale
            tx -= 20 * gui.scale

        ddt.rect((tx - 5 * gui.scale, ty, w + 20 * gui.scale, 24 * gui.scale), [15, 15, 15, 255])
        ddt.rect((tx - 5 * gui.scale, ty, w + 20 * gui.scale, 24 * gui.scale), [35, 35, 35, 255])
        ddt.text((tx + 5 * gui.scale, ty + 4 * gui.scale), name, [250, 250, 250, 255], 13, bg=[15, 15, 15, 255])


# Set SDL window drag areas
# if system != 'windows':

def hit_callback(win, point, data):
    
    x = point.contents.x / logical_size[0] * window_size[0]
    y = point.contents.y / logical_size[0] * window_size[0]

    # Special layout modes
    if gui.mode == 3:

        if key_shift_down or key_shiftr_down:
            return SDL_HITTEST_NORMAL

        if prefs.mini_mode_mode == 5:
            return SDL_HITTEST_NORMAL

        if prefs.mini_mode_mode == 4 and x > window_size[1] - 5 * gui.scale and y > window_size[1] - 12 * gui.scale:
            return SDL_HITTEST_NORMAL

        if y < gui.window_control_hit_area_h and x > window_size[
            0] - gui.window_control_hit_area_w:
            return SDL_HITTEST_NORMAL

        # Square modes
        y1 = window_size[0]
        y0 = 0
        if macos:
            y0 = round(35 * gui.scale)
        if window_size[0] == window_size[1]:
            y1 = window_size[1] - 79 * gui.scale
        if y0 < y < y1:
            return SDL_HITTEST_DRAGGABLE

        return SDL_HITTEST_NORMAL

    # Standard player mode
    if not gui.maximized:
        if y < 0 and x > window_size[0]:
            return SDL_HITTEST_RESIZE_TOPRIGHT

        if y < 0 and x < 1:
            return SDL_HITTEST_RESIZE_TOPLEFT

        if draw_border and y < 2 * gui.scale and x < window_size[0] - 40 * gui.scale and not gui.maximized:
            return SDL_HITTEST_RESIZE_TOP

    if y < gui.panelY:

        if gui.top_bar_mode2:

            if y < gui.panelY - gui.panelY2:
                if prefs.left_window_control and x < 100 * gui.scale:
                    return SDL_HITTEST_NORMAL

                if x > window_size[0] - 100 * gui.scale and y < 30 * gui.scale:
                    return SDL_HITTEST_NORMAL
                else:
                    return SDL_HITTEST_DRAGGABLE
            else:
                if top_panel.drag_zone_start_x > x or tab_menu.active:
                    return SDL_HITTEST_NORMAL
                else:
                    return SDL_HITTEST_DRAGGABLE

        elif top_panel.drag_zone_start_x < x < window_size[0] - (gui.offset_extra + 5):

            if tab_menu.active: # or pctl.broadcast_active:
                return SDL_HITTEST_NORMAL

            if prefs.left_window_control and x > window_size[0] - (100 * gui.scale) and (macos or system == "windows" or msys):
                return SDL_HITTEST_NORMAL
            elif not prefs.left_window_control and x > window_size[0] - (160 * gui.scale) and (macos or system == "windows" or msys):
                return SDL_HITTEST_NORMAL

            return SDL_HITTEST_DRAGGABLE

    if not gui.maximized:
        if x > window_size[0] - 20 * gui.scale and y > window_size[1] - 20 * gui.scale:
            return SDL_HITTEST_RESIZE_BOTTOMRIGHT
        elif x < 5 and y > window_size[1] - 5:
            return SDL_HITTEST_RESIZE_BOTTOMLEFT
        elif y > window_size[1] - 7 * gui.scale:
            return SDL_HITTEST_RESIZE_BOTTOM

        elif x > window_size[0] - 2 * gui.scale and y > 20 * gui.scale:
            return SDL_HITTEST_RESIZE_RIGHT
        elif x < 5 * gui.scale and y > 10 * gui.scale:
            return SDL_HITTEST_RESIZE_LEFT
        else:
            return SDL_HITTEST_NORMAL
    else:
        return SDL_HITTEST_NORMAL


c_hit_callback = SDL_HitTest(hit_callback)
SDL_SetWindowHitTest(t_window, c_hit_callback, 0)
# --------------------------------------------------------------------------------------------

class ThreadManager:

    def __init__(self):

        self.worker1 = None  # Artist list, download monitor, folder move, importing, db cleaning, transcoding
        self.worker2 = None  # Art bg, search
        self.worker3 = None  # Gallery rendering
        self.playback = None
        self.player_lock = threading.Lock()

        self.d = {}

    def ready(self, type):
        if self.d[type][2] is None or not self.d[type][2].is_alive():
            shoot = threading.Thread(target=self.d[type][0], args=self.d[type][1])
            shoot.daemon = True
            shoot.start()
            self.d[type][2] = shoot

    def ready_playback(self):
        if self.playback is None or not self.playback.is_alive():
            if prefs.backend == 4:
                from t_modules.t_phazor import player4
                self.playback = threading.Thread(target=player4, args=[tauon])
            elif prefs.backend == 2:
                from t_modules.t_gstreamer import player3
                self.playback = threading.Thread(target=player3, args=[tauon])
            self.playback.daemon = True
            self.playback.start()

    def check_playback_running(self):
        if self.playback is None:
            return False
        return self.playback.is_alive()

#caster = threading.Thread(target=enc, args=[tauon])
# caster.daemon = True
# caster.start()

tm = ThreadManager()
tauon.tm = tm
tm.ready_playback()

tm.d['caster'] = [enc, [tauon], None]
tm.d['worker'] = [worker1, (), None]
tm.d['search'] = [worker2, (), None]
tm.d['gallery'] = [worker3, (), None]
tm.d['style'] = [worker4, (), None]
tm.d['radio-thumb'] = [radio_thumb_gen.loader, (), None]

tm.ready("search")
tm.ready("gallery")
tm.ready("worker")

# thread = threading.Thread(target=worker1)
# thread.daemon = True
# thread.start()
# # #
# thread = threading.Thread(target=worker2)
# thread.daemon = True
# thread.start()
# # #
# thread = threading.Thread(target=worker3)
# thread.daemon = True
# thread.start()
#
# thread = threading.Thread(target=worker4)
# thread.daemon = True
# thread.start()

# MAIN LOOP---------------------------------------------------------------------------




# print("Almost done...")

gui.playlist_view_length = int(((window_size[1] - gui.playlist_top) / 16) - 1)

ab_click = False
d_border = 1

update_layout = True

event = SDL_Event()

mouse_moved = False

power = 0

for item in sys.argv:
    if (os.path.isdir(item) or os.path.isfile(item) or "file://" in item) and '.py' not in item and 'tauon.exe' not in item:
        open_uri(item)

sv = SDL_version()
SDL_GetVersion(sv)
sdl_version = sv.major * 100 + sv.minor * 10 + sv.patch
print("Using SDL version: " + str(sv.major) + "." + str(sv.minor) + "." + str(sv.patch))

# C-ML
# if prefs.backend == 2:
#     print("Using GStreamer as fallback. Some functions disabled")
if prefs.backend == 0:
    show_message("ERROR: No backend found", mode='error')


class Undo:

    def __init__(self):

        self.e = []

    def undo(self):

        if not self.e:
            show_message("There are no more steps to undo.")
            return

        job = self.e.pop()

        if job[0] == "playlist":
            pctl.multi_playlist.append(job[1])
            switch_playlist(len(pctl.multi_playlist) - 1)
        elif job[0] == 'tracks':

            uid = job[1]
            li = job[2]

            for i, playlist in enumerate(pctl.multi_playlist):
                if playlist[6] == uid:
                    pl = playlist[2]
                    switch_playlist(i)
                    break
            else:
                print("No matching playlist ID to restore tracks to")
                return

            for i, ref in reversed(li):

                if i > len(pl):
                    print("restore track error - playlist not correct length")
                    continue
                pl.insert(i, ref)

                if not pctl.playlist_view_position < i < pctl.playlist_view_position + gui.playlist_view_length:
                    pctl.playlist_view_position = i
                    console.print("DEBUG: Position changed by undo")
        elif job[0] == 'ptt':
            star_store.remove(job[1])
            if job[2]:
                star_store.insert(job[1], job[2])
            star_store.remove(job[3])
            if job[4]:
                star_store.insert(job[3], job[4])

        gui.pl_update = 1

    def bk_playlist(self, pl_index):

        self.e.append(("playlist", pctl.multi_playlist[pl_index]))

    def bk_tracks(self, pl_index, indis):

        uid = pctl.multi_playlist[pl_index][6]
        self.e.append(("tracks", uid, indis))

    def bk_playtime_transfer(self, source, target):
        self.e.append(("ptt", source, copy.deepcopy(star_store.full_get(source)), target, copy.deepcopy(star_store.full_get(target))))

undo = Undo()


def reload_scale():
    scale = prefs.scale_want

    gui.scale = scale
    ddt.scale = gui.scale
    prime_fonts()
    ddt.clear_text_cache()
    scale_assets(scale_want=scale, force=True)
    img_slide_update_gall(album_mode_art_size)

    for item in WhiteModImageAsset.assets:
        item.reload()
    for item in LoadImageAsset.assets:
        item.reload()
    for menu in Menu.instances:
        menu.rescale()
    bottom_bar1.__init__()
    bottom_bar_ao1.__init__()
    top_panel.__init__()
    view_box.__init__(reload=True)


def update_layout_do():
    if prefs.scale_want != gui.scale:
        reload_scale()

    w = window_size[0]
    h = window_size[1]

    if gui.switch_showcase_off:
        ddt.force_gray = False
        gui.switch_showcase_off = False
        exit_combo(restore=True)

    global draw_max_button
    if draw_max_button and prefs.force_hide_max_button:
        draw_max_button = False

    if gui.theme_name != prefs.theme_name:
        gui.reload_theme = True
        global theme
        theme = get_theme_number(prefs.theme_name)
        # print("Config reload theme...")

    # Restore in case of error
    if gui.rspw < 30 * gui.scale:
        gui.rspw = 100 * gui.scale

    # Lock right side panel to full size if fully extended -----
    if prefs.side_panel_layout == 0 and not album_mode:
        max_w = round(
            ((window_size[1] - gui.panelY - gui.panelBY - 17 * gui.scale) * gui.art_max_ratio_lock) + 17 * gui.scale)
        # 17 here is the art box inset value

        if not album_mode and gui.rspw > max_w - 12 * gui.scale:
            gui.rsp_full_lock = True
    # ----------------------------------------------------------

    # Auto shrink left side panel --------------
    pl_width = window_size[0]
    pl_width_a = pl_width
    if gui.rsp:
        pl_width_a = pl_width - gui.rspw
        pl_width -= gui.rspw - 300 * gui.scale  # More sensitivity for compact with rsp for better visual balancing

    if pl_width < 900 * gui.scale and not gui.hide_tracklist_in_gallery:
        gui.lspw = 180 * gui.scale

        if pl_width < 700 * gui.scale:
            gui.lspw = 150 * gui.scale

        if prefs.left_panel_mode == "artist list" and prefs.artist_list_style == 1:
            gui.compact_artist_list = True
            gui.lspw = 75 * gui.scale
            if gui.force_side_on_drag:
                gui.lspw = 180 * gui.scale
    else:
        gui.lspw = 220 * gui.scale
        gui.compact_artist_list = False
        if prefs.left_panel_mode == "artist list":
            gui.lspw = 230 * gui.scale

    if gui.lsp and prefs.left_panel_mode == "folder view":
        gui.lspw = 260 * gui.scale
        max_insets = 0
        for item in tree_view_box.rows:
            max_insets = max(item[2], max_insets)

        p = (pl_width_a * 0.15) - round(200 * gui.scale)
        if gui.hide_tracklist_in_gallery:
            p = ((window_size[0] - gui.lspw) * 0.15) - round(170 * gui.scale)

        p = min(round(200 * gui.scale), p)
        if p > 0:
            gui.lspw += p
        if max_insets > 1:
            if gui.lspw < 260 * gui.scale + round(15 * gui.scale) * max_insets:
                gui.lspw = 260 * gui.scale + round(15 * gui.scale) * max_insets


    # -----

    # Set bg art strength according to setting ----
    if prefs.art_bg_stronger == 3:
        prefs.art_bg_opacity = 29
    elif prefs.art_bg_stronger == 2:
        prefs.art_bg_opacity = 19
    else:
        prefs.art_bg_opacity = 10

    if prefs.bg_showcase_only:
        prefs.art_bg_opacity += 21

    # -----

    # Adjust for for compact window sizes ----
    if (prefs.always_art_header or w < 600 * gui.scale and not gui.rsp and prefs.art_in_top_panel) and not album_mode:
        gui.top_bar_mode2 = True
        gui.panelY = round(100 * gui.scale)
        gui.playlist_top = gui.panelY + (8 * gui.scale)
        gui.playlist_top_bk = gui.playlist_top

    else:
        gui.top_bar_mode2 = False
        gui.panelY = round(30 * gui.scale)
        gui.playlist_top = gui.panelY + (8 * gui.scale)
        gui.playlist_top_bk = gui.playlist_top

    gui.show_playlist = True
    if w < 750 * gui.scale and album_mode:
        gui.show_playlist = False


    # Set bio panel size according to setting
    if prefs.bio_large:
        gui.artist_panel_height = 320 * gui.scale
        if window_size[0] < 600 * gui.scale:
            gui.artist_panel_height = 200 * gui.scale

    else:
        gui.artist_panel_height = 200 * gui.scale
        if window_size[0] < 600 * gui.scale:
            gui.artist_panel_height = 150 * gui.scale

    # Trigger artist bio reload if panel size has changed
    if gui.artist_info_panel:
        if gui.last_artist_panel_height != gui.artist_panel_height:
            artist_info_box.get_data(artist_info_box.artist_on)
        gui.last_artist_panel_height = gui.artist_panel_height


    # prefs.art_bg_blur = 9
    # if prefs.bg_showcase_only:
    #     prefs.art_bg_blur = 15
    #
    # if w / h == 16 / 9:
    #     print("YEP")
    # elif w / h < 16 / 9:
    #     print("too low")
    # else:
    #     print("too high")
    # print((w, h))

    #input.mouse_click = False

    global renderer

    if prefs.spec2_colour_mode == 0:
        prefs.spec2_base = [10, 10, 100]
        prefs.spec2_multiply = [0.5, 1, 1]
    elif prefs.spec2_colour_mode == 1:
        prefs.spec2_base = [10, 10, 10]
        prefs.spec2_multiply = [2, 1.2, 5]
    # elif prefs.spec2_colour_mode == 2:
    #     prefs.spec2_base = [10, 100, 10]
    #     prefs.spec2_multiply = [1, -1, 0.4]

    gui.draw_vis4_top = False

    if gui.combo_mode and gui.showcase_mode and prefs.showcase_vis and not gui.mode == 3 and prefs.backend == 4:
        gui.vis = 4
        gui.turbo = True
    elif gui.vis_want == 0:
        gui.turbo = False
        gui.vis = 0
    else:
        gui.vis = gui.vis_want
        if gui.vis > 0:
            gui.turbo = True

    # Disable vis when in compact view
    if gui.mode == 3 or gui.top_bar_mode2: # or prefs.backend == 2:
        if not gui.combo_mode:
            gui.vis = 0
            gui.turbo = False

    if gui.mode == 1:
        if not gui.maximized and not gui.lowered and not gui.mode == 3:
            gui.save_size[0] = logical_size[0]
            gui.save_size[1] = logical_size[1]

        bottom_bar1.update()

        # if system != 'windows':
        #     if draw_border:
        #         gui.panelY = 30 * gui.scale + 3 * gui.scale
        #         top_panel.ty = 3 * gui.scale
        #     else:
        #         gui.panelY = 30 * gui.scale
        #         top_panel.ty = 0

        if gui.set_bar and gui.set_mode:
            gui.playlist_top = gui.playlist_top_bk + gui.set_height - 6 * gui.scale
        else:
            gui.playlist_top = gui.playlist_top_bk

        if gui.artist_info_panel:
            gui.playlist_top += gui.artist_panel_height

        gui.offset_extra = 0
        if draw_border and not prefs.left_window_control:

            offset = 61 * gui.scale
            if not draw_min_button:
                offset -= 35 * gui.scale
            if draw_max_button:
                offset += 33 * gui.scale
            if gui.macstyle:
                offset = 24
                if draw_min_button:
                    offset += 20
                if draw_max_button:
                    offset += 20
                offset = round(offset * gui.scale)
            gui.offset_extra = offset

        global album_v_gap
        global album_h_gap
        global album_v_slide_value

        album_v_slide_value = round(50 * gui.scale)
        if gui.gallery_show_text:
            album_h_gap = 30 * gui.scale
            album_v_gap = 66 * gui.scale
        else:
            album_h_gap = 30 * gui.scale
            album_v_gap = 25 * gui.scale

        if prefs.thin_gallery_borders:

            if gui.gallery_show_text:
                album_h_gap = 20 * gui.scale
                album_v_gap = 55 * gui.scale
            else:
                album_h_gap = 17 * gui.scale
                album_v_gap = 15 * gui.scale

            album_v_slide_value = round(45 * gui.scale)

        if prefs.increase_gallery_row_spacing:
            album_v_gap = round(album_v_gap * 1.3)

        gui.gallery_scroll_field_left = window_size[0] - round(40 * gui.scale)

        #gui.spec_rect[0] = window_size[0] - gui.offset_extra - 90
        gui.spec1_rec.x = int(round(window_size[0] - gui.offset_extra - 90 * gui.scale))

        #gui.spec_x = window_size[0] - gui.offset_extra - 90

        gui.spec2_rec.x = int(round(window_size[0] - gui.spec2_rec.w - 10 * gui.scale - gui.offset_extra))

        gui.scroll_hide_box = (1, gui.panelY, 28 * gui.scale, window_size[1] - gui.panelBY - gui.panelY)


        # Tracklist row size and text positioning ---------------------------------
        gui.playlist_row_height = prefs.playlist_row_height
        gui.row_font_size = prefs.playlist_font_size  # 13

        gui.playlist_text_offset = round(gui.playlist_row_height * 0.55) + 4 - 13 * gui.scale

        if gui.scale != 1:
            real_font_px = ddt.f_dict[gui.row_font_size][2]
            #gui.playlist_text_offset = (round(gui.playlist_row_height - real_font_px) / 2) - ddt.get_y_offset("AbcD", gui.row_font_size, 100) + round(1.3 * gui.scale)

            if gui.scale < 1.3:
                gui.playlist_text_offset = round(((gui.playlist_row_height - real_font_px) / 2) - 1.9 * gui.scale)
            elif gui.scale < 1.5:
                gui.playlist_text_offset = round(((gui.playlist_row_height - real_font_px) / 2) - 1.3 * gui.scale)
            elif gui.scale < 1.75:
                gui.playlist_text_offset = round(((gui.playlist_row_height - real_font_px) / 2) - 1.1 * gui.scale)
            elif gui.scale < 2.3:
                gui.playlist_text_offset = round(((gui.playlist_row_height - real_font_px) / 2) - 1.5 * gui.scale)
            else:
                gui.playlist_text_offset = round(((gui.playlist_row_height - real_font_px) / 2) - 1.8 * gui.scale)

        gui.playlist_text_offset += prefs.tracklist_y_text_offset


        gui.pl_title_real_height = round(gui.playlist_row_height * 0.55) + 4 - 12

        # -------------------------------------------------------------------------
        gui.playlist_view_length = int((window_size[1] - gui.panelBY - gui.playlist_top - 12 * gui.scale) // gui.playlist_row_height)

        box_r = gui.rspw / (window_size[1] - gui.panelBY - gui.panelY)

        if gui.art_aspect_ratio > 1.01:
            gui.art_unlock_ratio = True
            if gui.art_aspect_ratio > gui.art_max_ratio_lock:
                gui.art_max_ratio_lock = gui.art_aspect_ratio


        # print("Avaliabe: " + str(box_r))
        elif box_r <= 1:
            gui.art_unlock_ratio = False
            gui.art_max_ratio_lock = 1

        if side_drag and key_shift_down:
            gui.art_unlock_ratio = True
            gui.art_max_ratio_lock = 5

        gui.rspw = gui.pref_rspw
        if album_mode:
            gui.rspw = gui.pref_gallery_w

        # Limit the right side panel width to height of area
        if gui.rsp and prefs.side_panel_layout == 0:
            if album_mode:
                pass
            else:

                if not gui.art_unlock_ratio:

                    if gui.rsp_full_lock and not side_drag:
                        gui.rspw = window_size[0]

                    if gui.rspw > window_size[1] - gui.panelY - gui.panelBY:
                        gui.rspw = window_size[1] - gui.panelY - gui.panelBY


        # Determine how wide the playlist need to be
        gui.plw = window_size[0]
        gui.playlist_left = 0
        if gui.lsp:
            #if gui.plw > gui.lspw:
            gui.plw -= gui.lspw
            gui.playlist_left = gui.lspw
        if gui.rsp:
            gui.plw -= gui.rspw

        # Shrink side panel if playlist gets too small
        if window_size[0] > 100 and not gui.hide_tracklist_in_gallery:

            if gui.plw < 300:
                if gui.rsp:

                    l = 0
                    if gui.lsp:
                        l = gui.lspw

                    gui.rspw = max(window_size[0] - l - 300, 110)
                    # if album_mode and window_size[0] > 750 * gui.scale:
                    #     gui.pref_gallery_w = gui.rspw


        # Determine how wide the playlist need to be (again)
        gui.plw = window_size[0]
        gui.playlist_left = 0
        if gui.lsp:
            #if gui.plw > gui.lspw:
            gui.plw -= gui.lspw
            gui.playlist_left = gui.lspw
        if gui.rsp:
            gui.plw -= gui.rspw


        if window_size[0] < 630 * gui.scale:
            gui.compact_bar = True
        else:
            gui.compact_bar = False

        gui.pl_update = 1

        # Tracklist sizing ----------------------------------------------------
        left = gui.playlist_left
        width = gui.plw

        center_mode = True
        if gui.lsp or gui.rsp or gui.set_mode:
            center_mode = False

        if gui.set_mode and window_size[0] < 600:
            center_mode = False

        highlight_left = 0
        highlight_width = width

        inset_left = highlight_left + 23 * gui.scale
        inset_width = highlight_width - 32 * gui.scale

        if gui.lsp and not gui.rsp:
            inset_width -= 10 * gui.scale

        if gui.lsp:
            inset_left -= 10 * gui.scale
            inset_width += 10 * gui.scale

        if center_mode:
            if gui.set_mode:
                highlight_left = int(pow((window_size[0] * 0.005), 2))
            else:
                highlight_left = int(pow((window_size[0] * 0.01), 2))

            if window_size[0] < 600 * gui.scale:
                highlight_left = 3 * gui.scale

            highlight_width = highlight_width - (highlight_left * 2)
            inset_left = highlight_left + 18 * gui.scale
            inset_width = highlight_width - 25 * gui.scale

        if window_size[0] < 600 and gui.lsp:
            inset_width = highlight_width - 18 * gui.scale

        gui.tracklist_center_mode = center_mode
        gui.tracklist_inset_left = inset_left
        gui.tracklist_inset_width = inset_width
        gui.tracklist_highlight_left = highlight_left
        gui.tracklist_highlight_width = highlight_width

        if album_mode and gui.hide_tracklist_in_gallery:
            gui.show_playlist = False
            gui.rspw = window_size[0] - 20 * gui.scale
            if gui.lsp:
                gui.rspw -= gui.lspw

        # --------------------------------------------------------------------

        if window_size[0] > gui.max_window_tex or window_size[1] > gui.max_window_tex:

            while window_size[0] > gui.max_window_tex:
                gui.max_window_tex += 1000
            while window_size[1] > gui.max_window_tex:
                gui.max_window_tex += 1000


            gui.tracklist_texture_rect = SDL_Rect(0, 0, gui.max_window_tex, gui.max_window_tex)

            SDL_DestroyTexture(gui.tracklist_texture)
            SDL_RenderClear(renderer)
            gui.tracklist_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.max_window_tex,
                                                      gui.max_window_tex)

            SDL_SetRenderTarget(renderer, gui.tracklist_texture)
            SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0)
            SDL_RenderClear(renderer)
            SDL_SetTextureBlendMode(gui.tracklist_texture, SDL_BLENDMODE_BLEND)

            # SDL_SetRenderTarget(renderer, gui.main_texture)
            # SDL_RenderClear(renderer)

            SDL_DestroyTexture(gui.main_texture)
            gui.main_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.max_window_tex,
                                                 gui.max_window_tex)
            SDL_SetTextureBlendMode(gui.main_texture, SDL_BLENDMODE_BLEND)
            SDL_SetRenderTarget(renderer, gui.main_texture)
            SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0)
            SDL_SetRenderTarget(renderer, gui.main_texture)
            SDL_RenderClear(renderer)



            SDL_DestroyTexture(gui.main_texture_overlay_temp)
            gui.main_texture_overlay_temp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gui.max_window_tex,
                                                 gui.max_window_tex)
            SDL_SetTextureBlendMode(gui.main_texture_overlay_temp, SDL_BLENDMODE_BLEND)
            SDL_SetRenderTarget(renderer, gui.main_texture_overlay_temp)
            SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0)
            SDL_SetRenderTarget(renderer, gui.main_texture_overlay_temp)
            SDL_RenderClear(renderer)

        update_set()



# SDL_RenderClear(renderer)
# SDL_RenderPresent(renderer)


#SDL_ShowWindow(t_window)

# Clear spectogram texture
SDL_SetRenderTarget(renderer, gui.spec2_tex)
SDL_RenderClear(renderer)
ddt.rect((0, 0, 1000, 1000), [7, 7, 7, 255])

SDL_SetRenderTarget(renderer, gui.spec1_tex)
SDL_RenderClear(renderer)
ddt.rect((0, 0, 1000, 1000), [7, 7, 7, 255])

SDL_SetRenderTarget(renderer, gui.spec_level_tex)
SDL_RenderClear(renderer)
ddt.rect((0, 0, 1000, 1000), [7, 7, 7, 255])

SDL_SetRenderTarget(renderer, None)

#SDL_RenderPresent(renderer)

#time.sleep(3)

class GetSDLInput:

    def __init__(self):
        self.i_y = pointer(c_int(0))
        self.i_x = pointer(c_int(0))

        self.mouse_capture_want = False
        self.mouse_capture = False

    def mouse(self):
        SDL_PumpEvents()
        SDL_GetMouseState(self.i_x, self.i_y)
        return int(self.i_x.contents.value / logical_size[0] * window_size[0]), int(self.i_y.contents.value / logical_size[0] * window_size[0])


    def test_capture_mouse(self):
        if not self.mouse_capture and self.mouse_capture_want:
            SDL_CaptureMouse(SDL_TRUE)
            self.mouse_capture = True
        elif self.mouse_capture and not self.mouse_capture_want:
            SDL_CaptureMouse(SDL_FALSE)
            self.mouse_capture = False


gal_up = False
gal_down = False
gal_left = False
gal_right = False


get_sdl_input = GetSDLInput()

def window_is_focused():  # thread safe?
    if SDL_GetWindowFlags(t_window) & SDL_WINDOW_INPUT_FOCUS:
        return True
    return False


def save_state():

    print("Writing database to disk... ", end="")

    # view_prefs['star-lines'] = star_lines
    view_prefs['update-title'] = update_title
    view_prefs['side-panel'] = prefs.prefer_side
    view_prefs['dim-art'] = prefs.dim_art
    view_prefs['level-meter'] = gui.turbo
    # view_prefs['pl-follow'] = pl_follow
    view_prefs['scroll-enable'] = scroll_enable
    view_prefs['break-enable'] = break_enable
    # view_prefs['dd-index'] = dd_index
    view_prefs['append-date'] = prefs.append_date

    ds = []
    for v in pctl.master_library.values():
        ds.append(v.__dict__)

    save = [None,
            pctl.master_count,
            pctl.playlist_playing_position,
            pctl.active_playlist_viewing,
            pctl.playlist_view_position,
            pctl.multi_playlist,
            pctl.player_volume,
            pctl.track_queue,
            pctl.queue_step,
            default_playlist,
            pctl.playlist_playing_position,
            None,  # Was cue list
            "", #radio_field.text,
            theme,
            folder_image_offsets,
            None, # lfm_username,
            None, # lfm_hash,
            64,  # Version, used for upgrading
            view_prefs,
            gui.save_size,
            None,  # old side panel size
            0,  # save time (unused)
            gui.vis_want,  # gui.vis
            playlist_selected,
            album_mode_art_size,
            draw_border,
            prefs.enable_web,
            prefs.allow_remote,
            prefs.expose_web,
            prefs.enable_transcode,
            prefs.show_rym,
            None,  # was combo mode art size
            gui.maximized,
            prefs.prefer_bottom_title,
            gui.display_time_mode,
            prefs.transcode_mode,
            prefs.transcode_codec,
            prefs.transcode_bitrate,
            1, # prefs.line_style,
            prefs.cache_gallery,
            prefs.playlist_font_size,
            prefs.use_title,
            gui.pl_st,
            None, # gui.set_mode,
            None,
            prefs.playlist_row_height,
            prefs.show_wiki,
            prefs.auto_extract,
            prefs.colour_from_image,
            gui.set_bar,
            gui.gallery_show_text,
            gui.bb_show_art,
            False,  # Was show stars
            prefs.auto_lfm,
            prefs.scrobble_mark,
            prefs.replay_gain,
            True,  # Was radio lyrics
            prefs.show_gimage,
            prefs.end_setting,
            prefs.show_gen,
            [], # was old radio urls
            prefs.auto_del_zip,
            gui.level_meter_colour_mode,
            prefs.ui_scale,
            prefs.show_lyrics_side,
            prefs.last_device,
            album_mode,
            None,  # album_playlist_width
            prefs.transcode_opus_as,
            gui.star_mode,
            prefs.prefer_side, #gui.rsp,
            gui.lsp,
            gui.rspw,
            gui.pref_gallery_w,
            gui.pref_rspw,
            gui.show_hearts,
            prefs.monitor_downloads,  # 76
            gui.artist_info_panel,  # 77
            prefs.extract_to_music,  # 78
            lb.enable,
            None, #lb.key,
            rename_files.text,
            rename_folder.text,
            prefs.use_jump_crossfade,
            prefs.use_transition_crossfade,
            prefs.show_notifications,
            prefs.true_shuffle,
            gui.set_mode,
            None, #prefs.show_queue, # 88
            None, # prefs.show_transfer,
            pctl.force_queue, # 90
            prefs.use_pause_fade, # 91
            prefs.append_total_time, # 92
            None, #prefs.backend,
            pctl.album_shuffle_mode,
            pctl.album_repeat_mode, # 95
            prefs.finish_current,  # Not used
            prefs.reload_state,  # 97
            None, # prefs.reload_play_state,
            prefs.last_fm_token,
            prefs.last_fm_username,
            prefs.use_card_style,
            prefs.auto_lyrics,
            prefs.auto_lyrics_checked,
            prefs.show_side_art,
            prefs.window_opacity,
            prefs.gallery_single_click,
            prefs.tabs_on_top,
            prefs.showcase_vis,
            prefs.spec2_colour_mode,
            prefs.device_buffer,  # moved to config file
            prefs.use_eq,
            prefs.eq,
            prefs.bio_large,
            prefs.discord_show,
            prefs.min_to_tray,
            prefs.guitar_chords,
            None, #prefs.playback_follow_cursor,
            prefs.art_bg,
            pctl.random_mode,
            pctl.repeat_mode,
            prefs.art_bg_stronger,
            prefs.art_bg_always_blur,
            prefs.failed_artists,
            prefs.artist_list,
            None, #prefs.auto_sort,
            prefs.lyrics_enables,
            prefs.fanart_notify,
            prefs.bg_showcase_only,
            None, #prefs.discogs_pat,
            prefs.mini_mode_mode,
            after_scan,
            gui.gallery_positions,
            prefs.chart_bg,
            prefs.left_panel_mode,
            gui.last_left_panel_mode,
            prefs.gst_device,
            search_string_cache,
            search_dia_string_cache,
            pctl.gen_codes,
            gui.show_ratings,
            gui.show_album_ratings,
            prefs.radio_urls,
            gui.showcase_mode, #gui.combo_mode,
            top_panel.prime_tab,
            top_panel.prime_side,
            prefs.sync_playlist,
            prefs.spot_client,
            "", #prefs.spot_secret,
            prefs.show_band,
            prefs.download_playlist,
            spot_ctl.cache_saved_albums,
            prefs.auto_rec,
            prefs.spotify_token,
            prefs.use_libre_fm,
            playlist_box.scroll_on,
            prefs.artist_list_sort_mode,
            prefs.phazor_device_selected,
            prefs.failed_background_artists,
            prefs.bg_flips,
            prefs.tray_show_title,
            prefs.artist_list_style,
            ds,
            prefs.premium,
            gui.radio_view,
            pctl.radio_playlists,
            pctl.radio_playlist_viewing,
            prefs.radio_thumb_bans,
            prefs.playlist_exports,
            prefs.show_chromecast,
        ]


    try:
        pickle.dump(save, open(user_directory + "/state.p.backup", "wb"))
        # if not pctl.running:
        pickle.dump(save, open(user_directory + "/state.p", "wb"))

        old_position = old_window_position
        if not prefs.save_window_position:
            old_position = None

        save = [
            draw_border,
            gui.save_size,
            prefs.window_opacity,
            gui.scale,
            gui.maximized,
            old_position,
        ]

        pickle.dump(save, open(user_directory + "/window.p", "wb"))

        spot_ctl.save_token()

        with open(user_directory + "/lyrics_substitutions.json", 'w') as f:
            json.dump(prefs.lyrics_subs, f,)

        save_prefs()

        for key, item in prefs.playlist_exports.items():
            pl = id_to_pl(key)
            if pl is None:
                continue
            if item["auto"] is False:
                continue
            export_playlist_box.run_export(item, key, warnings=False)

        print("done")

    except PermissionError:
        show_message("Permission error encountered while writing database", "error")

# SDL_StartTextInput()
# SDL_SetHint(SDL_HINT_IME_INTERNAL_EDITING, b"1")
# SDL_EventState(SDL_SYSWMEVENT, 1)


def test_show_add_home_music():

    gui.add_music_folder_ready = True

    if music_directory is None:
        gui.add_music_folder_ready = False
        return

    for item in pctl.multi_playlist:
        if item[7] == music_directory:
            gui.add_music_folder_ready = False
            break

test_show_add_home_music()

if gui.restart_album_mode:
    toggle_album_mode(True)

if gui.remember_library_mode:
    toggle_library_mode()

quick_import_done = []

if reload_state:
    if reload_state[0] == 1:
        pctl.jump_time = reload_state[1]
        pctl.play()

    # if reload_state[0] == 2:
    #     pctl.playing_state = 2

pctl.notify_update()

key_focused = 0

theme = get_theme_number(prefs.theme_name)

if pl_to_id(pctl.active_playlist_viewing) in gui.gallery_positions:
    gui.album_scroll_px = gui.gallery_positions[pl_to_id(pctl.active_playlist_viewing)]


def menu_is_open():
    for menu in Menu.instances:
        if menu.active:
            return True
    return False

def is_level_zero(include_menus=True):

    if include_menus:
        for menu in Menu.instances:
            if menu.active:
                return False

    return not gui.rename_folder_box \
            and not track_box \
            and not rename_track_box.active \
            and not radiobox.active \
            and not pref_box.enabled \
            and not quick_search_mode \
            and not gui.rename_playlist_box \
            and not search_over.active \
            and not gui.box_over \
            and not trans_edit_box.active


# Hold the splash/loading screen for a minimum duration
# while core_timer.get() < 0.5:
#     time.sleep(0.01)

# Resize menu widths to text length (length can vary due to translations)
for menu in Menu.instances:

    w = 0
    icon_space = 0

    if menu.show_icons:
        icon_space = 25 * gui.scale

    for item in menu.items:
        if item is None:
            continue
        test_width = ddt.get_text_w(item[0], menu.font) + icon_space + 21 * gui.scale
        if not item[1] and item[6]:
            test_width += ddt.get_text_w(item[6], menu.font) + 4 * gui.scale

        if test_width > w:
            w = test_width

        # sub
        if item[1]:
            ww = 0
            sub_icon_space = 0
            for i in menu.subs[item[2]]:
                if i[7] is not None:
                    sub_icon_space = 25 * gui.scale
                    break
            for sub in menu.subs[item[2]]:

                test_width = ddt.get_text_w(sub[0], menu.font) + sub_icon_space + 23 * gui.scale
                if test_width > ww:
                    ww = test_width

            if ww > item[4]:
                # print("extend")
                # print(item)
                item[4] = ww

    if w > menu.w:
        menu.w = w


def drop_file(target):

    global new_playlist_cooldown
    global mouse_down
    global drag_mode

    if system != 'windows' and sdl_version >= 204:
        gmp = get_global_mouse()
        gwp = get_window_position()
        i_x = gmp[0] - gwp[0]
        if i_x < 0:
            i_x = 0
        if i_x > window_size[0]:
            i_x = window_size[0]
        i_y = gmp[1] - gwp[1]
        if i_y < 0:
            i_y = 0
        if i_y > window_size[1]:
            i_y = window_size[1]
    else:
        i_y = pointer(c_int(0))
        i_x = pointer(c_int(0))

        SDL_GetMouseState(i_x, i_y)
        i_y = i_y.contents.value / logical_size[0] * window_size[0]
        i_x = i_x.contents.value / logical_size[0] * window_size[0]

    # print((i_x, i_y))
    gui.drop_playlist_target = 0
    # print(event.drop)

    if i_y < gui.panelY and not new_playlist_cooldown and gui.mode == 1:
        x = top_panel.tabs_left_x
        for tab in top_panel.shown_tabs:
            wid = top_panel.tab_text_spaces[tab] + top_panel.tab_extra_width

            if x < i_x < x + wid:
                gui.drop_playlist_target = tab
                tab_pulse.pulse()
                gui.update += 1
                gui.pl_pulse = True
                print("Direct drop")
                break

            x += wid
        else:
            print("MISS")
            if new_playlist_cooldown:
                gui.drop_playlist_target = pctl.active_playlist_viewing
            else:
                if not target.lower().endswith(".xspf"):
                    gui.drop_playlist_target = new_playlist()
                new_playlist_cooldown = True

    elif gui.lsp and gui.panelY < i_y < window_size[1] - gui.panelBY and i_x < gui.lspw and gui.mode == 1:

        y = gui.panelY
        y += 5 * gui.scale
        y += playlist_box.tab_h + playlist_box.gap

        for i, pl in enumerate(pctl.multi_playlist):
            if i_y < y:
                gui.drop_playlist_target = i
                tab_pulse.pulse()
                gui.update += 1
                gui.pl_pulse = True
                print("Direct drop")
                break
            y += playlist_box.tab_h + playlist_box.gap
        else:
            if new_playlist_cooldown:
                gui.drop_playlist_target = pctl.active_playlist_viewing
            else:
                if not target.lower().endswith(".xspf"):
                    gui.drop_playlist_target = new_playlist()
                new_playlist_cooldown = True


    else:
        gui.drop_playlist_target = pctl.active_playlist_viewing

    if not os.path.exists(target) and flatpak_mode:
        show_message(_("Could not access! Possible insufficient Flatpak permissions."),
                     " See https://github.com/Taiko2k/TauonMusicBox/wiki/Flatpak-Extra-Steps for details.",
                     mode='bubble')

    load_order = LoadClass()
    load_order.target = target

    if os.path.isdir(load_order.target):
        quick_import_done.append(load_order.target)

        # if not pctl.multi_playlist[gui.drop_playlist_target][7]:
        pctl.multi_playlist[gui.drop_playlist_target][7].append(load_order.target)
        reduce_paths(pctl.multi_playlist[gui.drop_playlist_target][7])

    load_order.playlist = pctl.multi_playlist[gui.drop_playlist_target][6]
    load_orders.append(copy.deepcopy(load_order))

    # print('dropped: ' + str(dropped_file))
    gui.update += 1
    mouse_down = False
    drag_mode = False

if gui.restore_showcase_view:
    enter_showcase_view()
if gui.restore_radio_view:
    enter_radio_view()

#switch_playlist(len(pctl.multi_playlist) - 1)

SDL_SetRenderTarget(renderer, overlay_texture_texture)

block_size = 3

x = 0
y = 0
while y < 300:
    x = 0
    while x < 300:

        ddt.rect((x, y, 1, 1), [0, 0, 0, 70])
        ddt.rect((x + 2, y + 0, 1, 1), [0, 0, 0, 70])
        ddt.rect((x + 2, y + 2, 1, 1), [0, 0, 0, 70])
        ddt.rect((x + 0, y + 2, 1, 1), [0, 0, 0, 70])

        x += block_size
    y += block_size


sync_target.text = prefs.sync_target
SDL_SetRenderTarget(renderer, None)

if msys:
    SDL_SetWindowResizable(t_window, True)  # Not sure why this is needed

# Generate theme buttons
pref_box.themes.append((ColoursClass(), "Mindaro", 0))
theme_files = get_themes()
for i, theme in enumerate(theme_files):
    c = ColoursClass()
    load_theme(c, theme[0])
    pref_box.themes.append((c, theme[1], i + 1))

pctl.total_playtime = star_store.get_total()

mouse_up = False
mouse_wheel = 0

c_yax = 0
c_yax_timer = Timer()
c_xax = 0
c_xax_timer = Timer()
c_xay = 0
c_xay_timer = Timer()
rt = 0

while pctl.running:
    # bm.get('main')
    #time.sleep(100)
    if k_input:

        keymaps.hits.clear()

        d_mouse_click = False
        right_click = False
        level_2_right_click = False
        inp.mouse_click = False
        middle_click = False
        mouse_up = False
        inp.key_return_press = False
        key_down_press = False
        key_up_press = False
        key_right_press = False
        key_left_press = False
        key_esc_press = False
        key_del = False
        inp.backspace_press = 0
        key_backspace_press = False
        inp.key_tab_press = False
        key_c_press = False
        key_v_press = False
        #key_f_press = False
        key_a_press = False
        #key_t_press = False
        key_z_press = False
        key_x_press = False
        key_home_press = False
        key_end_press = False
        mouse_wheel = 0
        pref_box.scroll = 0
        new_playlist_cooldown = False
        input_text = ''
        inp.level_2_enter = False

        mouse_enter_window = False
        gui.mouse_in_window = True
        if key_focused:
            key_focused -= 1


    #f not mouse_down:
    k_input = False

    clicked = False
    focused = False
    mouse_moved = False
    gui.level_2_click = False

    # gui.update = 2

    while SDL_PollEvent(ctypes.byref(event)) != 0:

        # if event.type == SDL_SYSWMEVENT:
        #      print(event.syswm.msg.contents) # Not implemented by pysdl2

        if event.type == SDL_CONTROLLERDEVICEADDED:
            if SDL_IsGameController(event.cdevice.which):
                SDL_GameControllerOpen(event.cdevice.which)
                print(f"Found game controller: {SDL_GameControllerNameForIndex(event.cdevice.which).decode()}")

        if event.type == SDL_CONTROLLERAXISMOTION:
            if event.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT:
                rt = event.caxis.value > 5000
            if event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY:
                if event.caxis.value < -10000:
                    new = -1
                elif event.caxis.value > 10000:
                    new = 1
                else:
                    new = 0
                if new != c_yax:
                    c_yax_timer.force_set(1)
                c_yax = new
                power += 5
                gui.update += 1
            if event.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX:
                if event.caxis.value < -15000:
                    new = -1
                elif event.caxis.value > 15000:
                    new = 1
                else:
                    new = 0
                if new != c_xax:
                    c_xax_timer.force_set(1)
                c_xax = new
                power += 5
                gui.update += 1
            if event.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY:
                if event.caxis.value < -15000:
                    new = -1
                elif event.caxis.value > 15000:
                    new = 1
                else:
                    new = 0
                if new != c_xay:
                    c_xay_timer.force_set(1)
                c_xay = new
                power += 5
                gui.update += 1

        if event.type == SDL_CONTROLLERBUTTONDOWN:
            k_input = True
            power += 5
            gui.update += 2
            if event.cbutton.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
                if rt:
                    toggle_random()
                else:
                    pctl.advance()
            if event.cbutton.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
                if rt:
                    toggle_repeat()
                else:
                    pctl.back()
            if event.cbutton.button == SDL_CONTROLLER_BUTTON_A:
                if rt:
                    pctl.show_current(highlight=True)
                else:
                    if pctl.playing_ready() and pctl.active_playlist_playing == pctl.active_playlist_viewing and \
                        pctl.selected_ready() and default_playlist[playlist_selected] == pctl.playing_object().index:
                        pctl.play_pause()
                    else:
                        inp.key_return_press = True
            if event.cbutton.button == SDL_CONTROLLER_BUTTON_X:
                if rt:
                    random_track()
                else:
                    toggle_gallery_keycontrol(always_exit=True)
            if event.cbutton.button == SDL_CONTROLLER_BUTTON_Y:
                if rt:
                    pctl.advance(rr=True)
                else:
                    pctl.play_pause()
            if event.cbutton.button == SDL_CONTROLLER_BUTTON_B:
                if rt:
                    pctl.revert()
                else:
                    if is_level_zero():
                        pctl.stop()
                    else:
                        key_esc_press = True
            if event.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_UP:
                key_up_press = True
            if event.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN:
                key_down_press = True
            if event.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT:
                if gui.album_tab_mode:
                    key_left_press = True
                else:
                    if is_level_zero() or quick_search_mode:
                        cycle_playlist_pinned(1)
            if event.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
                if gui.album_tab_mode:
                    key_right_press = True
                else:
                    if is_level_zero() or quick_search_mode:
                        cycle_playlist_pinned(-1)


        if event.type == SDL_RENDER_TARGETS_RESET:
            print("Reset render targets!")
            clear_img_cache(delete_disk=False)
            ddt.clear_text_cache()
            for item in WhiteModImageAsset.assets:
                item.reload()

        if event.type == SDL_DROPTEXT:

            power += 5

            link = event.drop.file.decode()
            #print(link)

            if pctl.playing_ready() and link.startswith('http'):
                if system != 'windows' and sdl_version >= 204:
                    gmp = get_global_mouse()
                    gwp = get_window_position()
                    i_x = gmp[0] - gwp[0]
                    if i_x < 0:
                        i_x = 0
                    if i_x > window_size[0]:
                        i_x = window_size[0]
                    i_y = gmp[1] - gwp[1]
                    if i_y < 0:
                        i_y = 0
                    if i_y > window_size[1]:
                        i_y = window_size[1]
                else:
                    i_y = pointer(c_int(0))
                    i_x = pointer(c_int(0))

                    SDL_GetMouseState(i_x, i_y)
                    i_y = i_y.contents.value / logical_size[0] * window_size[0]
                    i_x = i_x.contents.value / logical_size[0] * window_size[0]

                if coll_point((i_x, i_y), gui.main_art_box):

                    print('Drop picture...')
                    #print(link)
                    gui.image_downloading = True
                    track = pctl.playing_object()
                    target_dir = track.parent_folder_path

                    shoot_dl = threading.Thread(target=download_img, args=(link, target_dir, track))
                    shoot_dl.daemon = True
                    shoot_dl.start()

                    gui.update = True

            elif link.startswith("file:///"):
                link = link.replace("\r", "")
                for line in link.split("\n"):
                    target = str(urllib.parse.unquote(line)).replace("file:///", "/")
                    drop_file(target)

        if event.type == SDL_DROPFILE:

            power += 5
            dropped_file_sdl = event.drop.file
            #print(dropped_file_sdl)
            target = str(urllib.parse.unquote(dropped_file_sdl.decode("utf-8"))).replace("file:///", "/").replace("\r","")
            #print(target)
            drop_file(target)


        elif event.type == 8192:
            gui.pl_update = 1
            gui.update += 2

        elif event.type == SDL_QUIT:
            power += 5

            if gui.tray_active and prefs.min_to_tray and not key_shift_down:
                tauon.min_to_tray()
            else:
                pctl.running = False
                break
        elif event.type == SDL_TEXTEDITING:
            power += 5
            #print("edit text")
            editline = event.edit.text
            #print(editline)
            editline = editline.decode("utf-8", 'ignore')
            k_input = True
            gui.update += 1

        elif event.type == SDL_MOUSEMOTION:

            mouse_position[0] = int(event.motion.x / logical_size[0] * window_size[0])
            mouse_position[1] = int(event.motion.y / logical_size[0] * window_size[0])
            mouse_moved = True
            gui.mouse_unknown = False
        elif event.type == SDL_MOUSEBUTTONDOWN:

            k_input = True
            focused = True
            power += 5
            gui.update += 1
            gui.mouse_in_window = True

            if ggc == 2:  # dont click on first full frame
                continue

            if event.button.button == SDL_BUTTON_RIGHT:
                right_click = True
                right_down = True
                #print("RIGHT DOWN")
            elif event.button.button == SDL_BUTTON_LEFT:
                #print("LEFT DOWN")

                # if mouse_position[1] > 1 and mouse_position[0] > 1:
                #     mouse_down = True

                inp.mouse_click = True

                mouse_down = True
            elif event.button.button == SDL_BUTTON_MIDDLE:
                if not search_over.active:
                    middle_click = True
                gui.update += 1
            elif event.button.button == SDL_BUTTON_X1:
                keymaps.hits.append("MB4")
            elif event.button.button == SDL_BUTTON_X2:
                keymaps.hits.append("MB5")
        elif event.type == SDL_MOUSEBUTTONUP:
            k_input = True
            power += 5
            gui.update += 1
            if event.button.button == SDL_BUTTON_RIGHT:
                right_down = False
            elif event.button.button == SDL_BUTTON_LEFT:
                if mouse_down:
                    mouse_up = True
                    mouse_up_position[0] = event.motion.x / logical_size[0] * window_size[0]
                    mouse_up_position[1] = event.motion.y / logical_size[0] * window_size[0]

                mouse_down = False
                gui.update += 1
        elif event.type == SDL_KEYDOWN and key_focused == 0:
            k_input = True
            power += 5
            gui.update += 2
            keymaps.hits.append(event.key.keysym.sym)

            if event.key.keysym.sym == (SDLK_RETURN or SDLK_RETURN2) and len(editline) == 0:
                inp.key_return_press = True

            elif event.key.keysym.sym == SDLK_KP_ENTER and len(editline) == 0:
                inp.key_return_press = True
            elif event.key.keysym.sym == SDLK_TAB:
                inp.key_tab_press = True
            elif event.key.keysym.sym == SDLK_BACKSPACE:
                inp.backspace_press += 1
                key_backspace_press = True
            elif event.key.keysym.sym == SDLK_DELETE:
                key_del = True
            elif event.key.keysym.sym == SDLK_ESCAPE:
                key_esc_press = True
            elif event.key.keysym.sym == SDLK_RALT:
                key_ralt = True
            elif event.key.keysym.sym == SDLK_LALT:
                key_lalt = True
            elif event.key.keysym.sym == SDLK_v:
                key_v_press = True
            elif event.key.keysym.sym == SDLK_a:
                key_a_press = True
            elif event.key.keysym.sym == SDLK_c:
                key_c_press = True
            # elif event.key.keysym.sym == SDLK_t:
            #     key_t_press = True
            elif event.key.keysym.sym == SDLK_z:
                key_z_press = True
            elif event.key.keysym.sym == SDLK_x:
                key_x_press = True
            elif event.key.keysym.sym == SDLK_DOWN:
                key_down_press = True
            elif event.key.keysym.sym == SDLK_UP:
                key_up_press = True
            elif event.key.keysym.sym == SDLK_LEFT:
                key_left_press = True
            elif event.key.keysym.sym == SDLK_RIGHT:
                key_right_press = True
            elif event.key.keysym.sym == SDLK_LSHIFT:
                key_shift_down = True
            elif event.key.keysym.sym == SDLK_RSHIFT:
                key_shiftr_down = True
            elif event.key.keysym.sym == SDLK_LCTRL:
                key_ctrl_down = True
            elif event.key.keysym.sym == SDLK_RCTRL:
                key_rctrl_down = True
            elif event.key.keysym.sym == SDLK_HOME:
                key_home_press = True
            elif event.key.keysym.sym == SDLK_END:
                key_end_press = True
            elif event.key.keysym.sym == SDLK_LGUI:
                key_meta = True
                key_focused = 1

        elif event.type == SDL_KEYUP:

            k_input = True
            power += 5
            gui.update += 2
            if event.key.keysym.sym == SDLK_LSHIFT:
                key_shift_down = False
            elif event.key.keysym.sym == SDLK_LCTRL:
                key_ctrl_down = False
            elif event.key.keysym.sym == SDLK_RCTRL:
                key_rctrl_down = False
            elif event.key.keysym.sym == SDLK_RSHIFT:
                key_shiftr_down = False
            elif event.key.keysym.sym == SDLK_RALT:
                gui.album_tab_mode = False
                key_ralt = False
            elif event.key.keysym.sym == SDLK_LALT:
                gui.album_tab_mode = False
                key_lalt = False
            elif event.key.keysym.sym == SDLK_LGUI:
                key_meta = False
                key_focused = 1

        elif event.type == SDL_TEXTINPUT:
            k_input = True
            power += 5
            input_text += event.text.text.decode('utf-8')

            gui.update += 1
            #print(input_text)

        elif event.type == SDL_MOUSEWHEEL:
            k_input = True
            power += 6
            mouse_wheel += event.wheel.y
            gui.update += 1
        elif event.type == SDL_WINDOWEVENT:

            power += 5
            # print(event.window.event)

            if event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED:
                if system == "linux" and not macos and not msys:
                    gnome.focus()
                k_input = True

                mouse_enter_window = True
                focused = True
                gui.lowered = False
                key_focused = 1 
                mouse_down = False
                gui.album_tab_mode = False
                gui.pl_update = 1
                gui.update += 1

            elif event.window.event == SDL_WINDOWEVENT_FOCUS_LOST:
                close_all_menus()
                key_focused = 1
                gui.update += 1

            elif event.window.event == SDL_WINDOWEVENT_RESIZED or event.window.event == SDL_WINDOWEVENT_DISPLAY_CHANGED:

                if restore_ignore_timer.get() > 1 or event.window.event == SDL_WINDOWEVENT_DISPLAY_CHANGED:  # Hacky
                    gui.update = 2

                    logical_size[0] = event.window.data1
                    logical_size[1] = event.window.data2

                    if gui.mode != 3:
                        logical_size[0] = max(300, logical_size[0])
                        logical_size[1] = max(300, logical_size[1])

                    i_x = pointer(c_int(0))
                    i_y = pointer(c_int(0))
                    SDL_GL_GetDrawableSize(t_window, i_x, i_y)
                    window_size[0] = i_x.contents.value
                    window_size[1] = i_y.contents.value

                    auto_scale()
                    update_layout = True


            elif event.window.event == SDL_WINDOWEVENT_ENTER:
                # print("ENTER")
                mouse_enter_window = True
                gui.mouse_in_window = True

            # elif event.window.event == SDL_WINDOWEVENT_HIDDEN:
            #
            elif event.window.event == SDL_WINDOWEVENT_EXPOSED:
                # print("expose")
                gui.lowered = False

            elif event.window.event == SDL_WINDOWEVENT_MINIMIZED:
                gui.lowered = True
                # if prefs.min_to_tray:
                #     tray.down()
                #tm.sleep()

            elif event.window.event == SDL_WINDOWEVENT_RESTORED:

                # print("RESTORE")
                gui.lowered = False
                gui.maximized = False
                gui.pl_update = 1
                gui.update += 1

                if update_title:
                    update_title_do()
                    # print("restore")

            elif event.window.event == SDL_WINDOWEVENT_SHOWN:
                focused = True
                gui.pl_update = 1
                gui.update += 1

            # elif event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED:
            #     print("FOCUS GAINED")
            #     # input.mouse_enter_event = True
            #     # gui.update += 1
            #     # k_input = True

            elif event.window.event == SDL_WINDOWEVENT_MAXIMIZED:
                if gui.mode != 3:  # workaround. sdl bug? gives event on window size set
                    gui.maximized = True
                update_layout = True
                gui.pl_update = 1
                gui.update += 1

            elif event.window.event == SDL_WINDOWEVENT_LEAVE:
                gui.mouse_in_window = False
                gui.update += 1
                power = 1000

    if mouse_moved:
        if fields.test():
            gui.update += 1

    if gui.request_raise:
        gui.request_raise = False
        print("Raise")
        SDL_ShowWindow(t_window)
        SDL_RestoreWindow(t_window)
        SDL_RaiseWindow(t_window)
        gui.lowered = False

    # if tm.sleeping:
    #     if not gui.lowered:
    #         tm.wake()
    if gui.lowered:
        gui.update = 0
    # ----------------
    # This section of code controls the internal processing speed or 'frame-rate'.
    # It's pretty messy.
    # if not gui.pl_update and gui.rendered_playlist_position != playlist_view_position:
    #     console.print("WARNING: The playlist failed to render at the latest position!!!!")

    power += 1

    if pctl.playerCommandReady:
        try:
            tm.player_lock.release()
        except:
            pass

    if gui.frame_callback_list:
        i = len(gui.frame_callback_list) - 1
        while i >= 0:
            if gui.frame_callback_list[i].test():
                gui.update = 1
                power = 1000
                del gui.frame_callback_list[i]
            i -= 1

    if animate_monitor_timer.get() < 1 or load_orders:

        if cursor_blink_timer.get() > 0.65:
            cursor_blink_timer.set()
            TextBox.cursor ^= True
            gui.update = 1

        if k_input:
            cursor_blink_timer.set()
            TextBox.cursor = True

        SDL_Delay(3)
        power = 1000

    if mouse_wheel or k_input or gui.pl_update or gui.update or top_panel.adds:  # or mouse_moved:
        power = 1000

    if prefs.art_bg and core_timer.get() < 3:
        power = 1000

    if mouse_down and mouse_moved:
        power = 1000
        if gui.update_on_drag:
            gui.update += 1
        if gui.pl_update_on_drag:
            gui.pl_update += 1

    if pctl.wake_past_time:

        if get_real_time() > pctl.wake_past_time:
            pctl.wake_past_time = 0
            power = 1000
            gui.update += 1

    if gui.level_update and not album_scroll_hold and not scroll_hold:
        power = 500

    # if gui.vis == 3 and (pctl.playing_state == 1 or pctl.playing_state == 3):
    #     power = 500
    #     if len(gui.spec2_buffers) > 0 and gui.spec2_timer.get() > 0.04:
    #         gui.spec2_timer.set()
    #         gui.level_update = True
    #         vis_update = True
    #     else:
    #         SDL_Delay(5)

    if not pctl.running:
        break

    if pctl.playing_state > 0 or pctl.broadcast_active:
        power += 400

    if power < 500:

        time.sleep(0.03)

        if (pctl.playing_state == 0 or pctl.playing_state == 2) and not load_orders and gui.update == 0 and not gall_ren.queue and not transcode_list and not gui.frame_callback_list and not pctl.broadcast_active:
            pass
        else:
            sleep_timer.set()
        if sleep_timer.get() > 2:
            SDL_WaitEventTimeout(None, 20000)
        continue

    else:
        power = 0

    if gui.pl_update > 2:
        gui.pl_update = 2

    new_playlist_cooldown = False

    if prefs.auto_extract and prefs.monitor_downloads:
        dl_mon.scan()

    if mouse_down and not coll((2, 2, window_size[0] - 4, window_size[1] - 4)):
        #print(SDL_GetMouseState(None, None))
        if SDL_GetGlobalMouseState(None, None) == 0:

            mouse_down = False
            mouse_up = True
            quick_drag = False

    # print(window_size)
    # if window_size[0] / window_size[1] == 16 / 9:
    #     print('OK')
    # if window_size[0] / window_size[1] > 16 / 9:
    #     print("A")

    if key_meta:
        input_text = ""
        k_input = False
        inp.key_return_press = False
        inp.key_tab_press = False

    if k_input:
        if inp.mouse_click or right_click or mouse_up:
            last_click_location = copy.deepcopy(click_location)
            click_location = copy.deepcopy(mouse_position)

        if key_focused != 0:
            keymaps.hits.clear()

            #d_mouse_click = False
            #right_click = False
            #level_2_right_click = False
            #inp.mouse_click = False
            #middle_click = False
            mouse_up = False
            inp.key_return_press = False
            key_down_press = False
            key_up_press = False
            key_right_press = False
            key_left_press = False
            key_esc_press = False
            key_del = False
            inp.backspace_press = 0
            key_backspace_press = False
            inp.key_tab_press = False
            key_c_press = False
            key_v_press = False
            # key_f_press = False
            key_a_press = False
            # key_t_press = False
            key_z_press = False
            key_x_press = False
            key_home_press = False
            key_end_press = False
            mouse_wheel = 0
            pref_box.scroll = 0
            input_text = ''
            inp.level_2_enter = False

    if c_yax != 0:
        if c_yax_timer.get() >= 0:
            if c_yax == -1:
                key_up_press = True
            if c_yax == 1:
                key_down_press = True
            c_yax_timer.force_set(-0.01)
            gui.delay_frame(0.02)
            k_input = True
    if c_xax != 0:
        if c_xax_timer.get() >= 0:
            if c_xax == 1:
                pctl.seek_time(pctl.playing_time + 2)
            if c_xax == -1:
                pctl.seek_time(pctl.playing_time - 2)
            c_xax_timer.force_set(-0.01)
            gui.delay_frame(0.02)
            k_input = True
    if c_xay != 0:
        if c_xay_timer.get() >= 0:
            if c_xay == -1:
                pctl.player_volume += 1
                if pctl.player_volume > 100:
                    pctl.player_volume = 100
                pctl.set_volume()
            if c_xay == 1:
                if pctl.player_volume > 1:
                    pctl.player_volume -= 1
                else:
                    pctl.player_volume = 0
                pctl.set_volume()
            c_xay_timer.force_set(-0.01)
            gui.delay_frame(0.02)
            k_input = True

    if k_input and key_focused == 0:

        if keymaps.hits:
            n = 1
            while n < 10:
                if keymaps.test(f"jump-playlist-{n}"):
                    if len(pctl.multi_playlist) > n - 1:
                        switch_playlist(n - 1)
                n += 1

            if keymaps.test('cycle-playlist-left'):
                if gui.album_tab_mode and key_left_press:
                    pass
                else:
                    if is_level_zero() or quick_search_mode:
                        cycle_playlist_pinned(1)
            if keymaps.test('cycle-playlist-right'):
                if gui.album_tab_mode and key_right_press:
                    pass
                else:
                    if is_level_zero() or quick_search_mode:
                        cycle_playlist_pinned(-1)

            if keymaps.test('toggle-console'):
                console.show ^= True

            if keymaps.test("toggle-fullscreen"):
                if not fullscreen and not gui.mode == 3:
                    fullscreen = True
                    SDL_SetWindowFullscreen(t_window, SDL_WINDOW_FULLSCREEN_DESKTOP)
                elif fullscreen:
                    fullscreen = False
                    SDL_SetWindowFullscreen(t_window, 0)

            if keymaps.test("playlist-toggle-breaks"):
                # Toggle force off folder break for viewed playlist
                pctl.multi_playlist[pctl.active_playlist_viewing][4] ^= 1
                gui.pl_update = 1

            if keymaps.test("find-playing-artist"):
                #standard_size()
                if len(pctl.track_queue) > 0:
                    quick_search_mode = True
                    search_text.text = ""
                    input_text = pctl.playing_object().artist

            if keymaps.test("show-encode-folder"):
                open_encode_out()

            if keymaps.test('toggle-left-panel'):
                gui.lsp ^= True
                update_layout_do()

            if keymaps.test('toggle-last-left-panel'):
                toggle_left_last()
                update_layout_do()

            if keymaps.test("toggle-broadcast"):
                toggle_broadcast()

        if key_ctrl_down:
            gui.pl_update += 1

        if mouse_enter_window:
            inp.key_return_press = False

        if fullscreen and key_esc_press:
            fullscreen = False
            SDL_SetWindowFullscreen(t_window, 0)

        # Disable keys for text cursor control
        if not gui.rename_folder_box and not rename_track_box.active and not gui.rename_playlist_box and not radiobox.active and not pref_box.enabled and not trans_edit_box.active:

            if not quick_search_mode and not search_over.active:
                if album_mode and gui.album_tab_mode \
                        and not key_ctrl_down \
                        and not key_meta \
                        and not key_lalt:
                    if key_left_press:
                        gal_left = True
                        key_left_press = False
                    if key_right_press:
                        gal_right = True
                        key_right_press = False
                    if key_up_press:
                        gal_up = True
                        key_up_press = False
                    if key_down_press:
                        gal_down = True
                        key_down_press = False

            if not search_over.active:
                if key_del:
                    close_all_menus()
                    del_selected()

                # Arrow keys to change playlist
                if (key_left_press or key_right_press) and len(pctl.multi_playlist) > 1:
                    gui.pl_update = 1
                    gui.update += 1


            if keymaps.test("start"):
                if pctl.playing_time < 4:
                    pctl.back()
                else:
                    pctl.new_time = 0
                    pctl.playing_time = 0
                    pctl.decode_time = 0
                    pctl.playerCommand = 'seek'
                    pctl.playerCommandReady = True

            if keymaps.test("goto-top"):
                pctl.playlist_view_position = 0
                console.print("DEBUG: Position changed by key")
                playlist_selected = 0
                gui.pl_update = 1

            if keymaps.test("goto-bottom"):
                n = len(default_playlist) - gui.playlist_view_length + 1
                if n < 0:
                    n = 0
                pctl.playlist_view_position = n
                console.print("DEBUG: Position changed by key")
                playlist_selected = len(default_playlist) - 1
                gui.pl_update = 1


        if not pref_box.enabled and not radiobox.active and not rename_track_box.active \
                and not gui.rename_folder_box \
                and not gui.rename_playlist_box and not search_over.active and not gui.box_over and not trans_edit_box.active:

            if quick_search_mode:
                if keymaps.test("add-to-queue") and pctl.selected_ready():
                    add_selected_to_queue()
                    input_text = ""

            else:

                if key_c_press and key_ctrl_down:
                    gui.pl_update = 1
                    s_copy()

                if key_x_press and key_ctrl_down:
                    gui.pl_update = 1
                    s_cut()

                if key_v_press and key_ctrl_down:
                    gui.pl_update = 1
                    paste()

                if keymaps.test("playpause"):
                    if pctl.playing_state == 0:
                        pctl.play()
                    else:
                        pctl.pause()

        if inp.key_return_press and (gui.rename_folder_box or rename_track_box.active or radiobox.active):
            inp.key_return_press = False
            inp.level_2_enter = True

        if key_ctrl_down and key_z_press:
            undo.undo()

        if keymaps.test('quit'):
            pctl.running = False

        if keymaps.test('testkey'):  # F7: test
            pass

        if gui.mode < 3:
            if keymaps.test("toggle-auto-theme"):
                prefs.colour_from_image ^= True
                if prefs.colour_from_image:
                    show_message(_("Enabled auto theme"))
                else:
                    show_message(_("Disabled auto theme"))
                    gui.reload_theme = True
                    gui.theme_temp_current = -1

            if keymaps.test("transfer-playtime-to"):
                if len(cargo) == 1 and tauon.cargo_playtime and -1 < playlist_selected < len(default_playlist):
                    undo.bk_playtime_transfer(cargo[0], default_playlist[playlist_selected])
                    star_store.remove(cargo[0])
                    star_store.add(default_playlist[playlist_selected], tauon.cargo_playtime)
                    tauon.cargo_playtime = None
                    gui.pl_update += 1
                elif tauon.cargo_playtime is None:
                    show_message(_("First select a source track by copying it into clipboard"))

            if keymaps.test("toggle-gallery"):
                toggle_album_mode()

            if keymaps.test("toggle-right-panel"):
                if gui.combo_mode:
                    exit_combo()
                elif not album_mode:
                    toggle_side_panel()
                else:
                    toggle_album_mode()

            if keymaps.test("toggle-minimode"):
                set_mini_mode()
                gui.update += 1

            if keymaps.test("cycle-layouts"):

                if view_box.tracks():
                    view_box.side(True)
                elif view_box.side():
                    view_box.gallery1(True)
                elif view_box.gallery1():
                    view_box.lyrics(True)
                else:
                    view_box.tracks(True)

            if keymaps.test("cycle-layouts-reverse"):

                if view_box.tracks():
                    view_box.lyrics(True)
                elif view_box.lyrics():
                    view_box.gallery1(True)
                elif view_box.gallery1():
                    view_box.side(True)
                else:
                    view_box.tracks(True)


            if keymaps.test("toggle-columns"):
                view_box.col(True)

            if keymaps.test("toggle-artistinfo"):
                view_box.artist_info(True)

            if keymaps.test("toggle-showcase"):
                view_box.lyrics(True)

            if keymaps.test("toggle-gallery-keycontrol"):
                toggle_gallery_keycontrol()

            if keymaps.test("toggle-show-art"):
                toggle_side_art()

        elif gui.mode == 3:
            if keymaps.test("toggle-minimode"):
                restore_full_mode()
                gui.update += 1

        ab_click = False

        if keymaps.test("new-playlist"):
            new_playlist()

        if keymaps.test("edit-generator"):
            edit_generator_box(pctl.active_playlist_viewing)

        if keymaps.test("new-generator-playlist"):
            new_playlist()
            edit_generator_box(pctl.active_playlist_viewing)

        if keymaps.test("delete-playlist"):
            delete_playlist(pctl.active_playlist_viewing)

        if keymaps.test("delete-playlist-force"):
            delete_playlist(pctl.active_playlist_viewing, force=True)

        if keymaps.test("rename-playlist"):
            if gui.radio_view:
                rename_playlist(pctl.radio_playlist_viewing)
            else:
                rename_playlist(pctl.active_playlist_viewing)
            rename_playlist_box.x = 60 * gui.scale
            rename_playlist_box.y = 60 * gui.scale

        # Transfer click register to menus
        if inp.mouse_click:
            for instance in Menu.instances:
                if instance.active:
                    instance.click()
                    inp.mouse_click = False
                    ab_click = True
            if view_box.active:
                view_box.clicked = True

        if inp.mouse_click and (prefs.show_nag or gui.box_over or radiobox.active or search_over.active or gui.rename_folder_box or gui.rename_playlist_box or rename_track_box.active or view_box.active or trans_edit_box.active): # and not gui.message_box:
            inp.mouse_click = False
            gui.level_2_click = True
        else:
            gui.level_2_click = False

        if track_box and inp.mouse_click:
            w = 540
            h = 240
            x = int(window_size[0] / 2) - int(w / 2)
            y = int(window_size[1] / 2) - int(h / 2)
            if coll([x, y, w, h]):
                inp.mouse_click = False
                gui.level_2_click = True

        if right_click:
            level_2_right_click = True

        if pref_box.enabled:

            if pref_box.inside():
                if inp.mouse_click: # and not gui.message_box:
                    pref_box.click = True
                    inp.mouse_click = False
                if right_click:
                    right_click = False
                    pref_box.right_click = True

                pref_box.scroll = mouse_wheel
                mouse_wheel = 0
            else:
                if inp.mouse_click:
                    pref_box.close()
                if right_click:
                    pref_box.close()
                if pref_box.lock is False:
                    pass


        if right_click and (radiobox.active or rename_track_box.active or gui.rename_playlist_box or gui.rename_folder_box or search_over.active):
            right_click = False

        if mouse_wheel != 0:
            gui.update += 1
        if mouse_down is True:
            gui.update += 1

        if keymaps.test('pagedown'): #key_PGD:
            if len(default_playlist) > 10:
                pctl.playlist_view_position += gui.playlist_view_length - 4
                if pctl.playlist_view_position > len(default_playlist):
                    pctl.playlist_view_position = len(default_playlist) - 2
                gui.pl_update = 1
                playlist_selected = pctl.playlist_view_position
                console.print("DEBUG: Position changed by page key")
                shift_selection.clear()
        if keymaps.test('pageup'):
            if len(default_playlist) > 0:
                pctl.playlist_view_position -= gui.playlist_view_length - 4
                if pctl.playlist_view_position < 0:
                    pctl.playlist_view_position = 0
                gui.pl_update = 1
                playlist_selected = pctl.playlist_view_position
                console.print("DEBUG: Position changed by page key")
                shift_selection.clear()


        if quick_search_mode is False and rename_track_box.active is False and gui.rename_folder_box is False and gui.rename_playlist_box is False and not pref_box.enabled and not radiobox.active:

            if keymaps.test("info-playing"):
                if playlist_selected < len(default_playlist):
                    r_menu_index = pctl.g(default_playlist[playlist_selected]).index
                    track_box = True

            if keymaps.test("info-show"):
                if playlist_selected < len(default_playlist):
                    r_menu_index = pctl.g(default_playlist[playlist_selected]).index
                    track_box = True

            # These need to be disabled when text fields are active
            if not search_over.active and not gui.box_over and not radiobox.active and not gui.rename_folder_box and not rename_track_box.active and not gui.rename_playlist_box and not trans_edit_box.active:
                if keymaps.test("advance"):
                    key_right_press = False
                    pctl.advance()

                if keymaps.test("previous"):
                    key_left_press = False
                    pctl.back()

                if key_a_press and key_ctrl_down:
                    gui.pl_update = 1
                    shift_selection = range(len(default_playlist))

                if keymaps.test("revert"):
                    pctl.revert()

                if keymaps.test("random-track-start"):
                    pctl.advance(rr=True)

                if keymaps.test("vol-down"):
                    if pctl.player_volume > 3:
                        pctl.player_volume -= 3
                    else:
                        pctl.player_volume = 0
                    pctl.set_volume()

                if keymaps.test("vol-up"):
                    pctl.player_volume += 3
                    if pctl.player_volume > 100:
                        pctl.player_volume = 100
                    pctl.set_volume()

                if keymaps.test("shift-down") and len(default_playlist) > 0:
                    gui.pl_update += 1
                    if playlist_selected > len(default_playlist) - 1:
                        playlist_selected = 0

                    if not shift_selection:
                        shift_selection.append(playlist_selected)
                    if playlist_selected < len(default_playlist) - 1:
                        r = playlist_selected
                        playlist_selected += 1
                        if playlist_selected not in shift_selection:
                            shift_selection.append(playlist_selected)
                        else:
                            shift_selection.remove(r)

                if keymaps.test("shift-up") and playlist_selected > -1:
                    gui.pl_update += 1
                    if playlist_selected > len(default_playlist) - 1:
                        playlist_selected = 0

                    if not shift_selection:
                        shift_selection.append(playlist_selected)
                    if playlist_selected < len(default_playlist) - 1:
                        r = playlist_selected
                        playlist_selected -= 1
                        if playlist_selected not in shift_selection:
                            shift_selection.insert(0, playlist_selected)
                        else:
                            shift_selection.remove(r)


                if keymaps.test("toggle-shuffle"):
                    #pctl.random_mode ^= True
                    toggle_random()

                if keymaps.test("goto-playing"):
                    pctl.show_current()
                if keymaps.test("goto-previous"):
                    if pctl.queue_step > 1:
                        pctl.show_current(index=pctl.track_queue[pctl.queue_step - 1])

                if keymaps.test("toggle-repeat"):
                    toggle_repeat()

                if keymaps.test("random-track"):
                    random_track()

                if keymaps.test("random-album"):
                    random_album()

                if keymaps.test('opacity-up'):
                    prefs.window_opacity += .05
                    if prefs.window_opacity > 1:
                        prefs.window_opacity = 1
                    SDL_SetWindowOpacity(t_window, prefs.window_opacity)

                if keymaps.test('opacity-down'):
                    prefs.window_opacity -= .05
                    if prefs.window_opacity < .30:
                        prefs.window_opacity = .30
                    SDL_SetWindowOpacity(t_window, prefs.window_opacity)

                if keymaps.test("seek-forward"):
                    pctl.seek_time(pctl.playing_time + prefs.seek_interval)

                if keymaps.test("seek-back"):
                    pctl.seek_time(pctl.playing_time - prefs.seek_interval)

                if keymaps.test("play"):
                    pctl.play()

                if keymaps.test("stop"):
                    pctl.stop()

                if keymaps.test("pause"):
                    pctl.pause_only()

                if keymaps.test("love-playing"):
                    bar_love(notify=True)

                if keymaps.test("love-selected"):
                    select_love(notify=True)

                if keymaps.test("search-lyrics-selected"):
                    if pctl.selected_ready():
                        track = pctl.g(default_playlist[playlist_selected])
                        if track.lyrics:
                            show_message("Track already has lyrics")
                        else:
                            get_lyric_wiki(track)

                if keymaps.test("substitute-search-selected"):
                    if pctl.selected_ready():
                        show_sub_search(pctl.g(default_playlist[playlist_selected]))

                if keymaps.test("global-search"):
                    activate_search_overlay()

                if keymaps.test("add-to-queue") and pctl.selected_ready():
                    add_selected_to_queue()

                if keymaps.test("clear-queue"):
                    clear_queue()

                if keymaps.test("regenerate-playlist"):
                    regenerate_playlist(pctl.active_playlist_viewing)

        if keymaps.test("cycle-theme"):
            gui.reload_theme = True
            gui.theme_temp_current = -1
            gui.temp_themes.clear()
            theme += 1

        if keymaps.test("cycle-theme-reverse"):
            gui.theme_temp_current = -1
            gui.temp_themes.clear()
            pref_box.devance_theme()

        if keymaps.test("reload-theme"):
            gui.reload_theme = True

    # if mouse_position[1] < 1:
    #     mouse_down = False

    if mouse_down is False:
        scroll_hold = False

    # if focused is True:
    #     mouse_down = False

    if inp.media_key:
        if inp.media_key == 'Play':
            if pctl.playing_state == 0:
                pctl.play()
            else:
                pctl.pause()
        elif inp.media_key == 'Pause':
            pctl.pause_only()
        elif inp.media_key == 'Stop':
            pctl.stop()
        elif inp.media_key == 'Next':
            pctl.advance()
        elif inp.media_key == 'Previous':
            pctl.back()

        elif inp.media_key == 'Rewind':
            pctl.seek_time(pctl.playing_time - 10)
        elif inp.media_key == 'FastForward':
            pctl.seek_time(pctl.playing_time + 10)
        elif inp.media_key == 'Repeat':
            toggle_repeat()
        elif inp.media_key == 'Shuffle':
            toggle_random()

        inp.media_key = ""

    if len(load_orders) > 0:
        loading_in_progress = True
        pctl.after_import_flag = True
        tm.ready("worker")
        if loaderCommand == LC_None:
            for order in load_orders:
                if order.stage == 0:
                    order.traget = order.target.replace('\\', '/')
                    order.stage = 1
                    if os.path.isdir(order.traget):
                        loaderCommand = LC_Folder
                    else:
                        loaderCommand = LC_File
                        if order.traget.endswith('.xspf'):
                            to_got = 'xspf'
                            to_get = 0
                        else:
                            to_got = 1
                            to_get = 1
                    loaderCommandReady = True
                    tm.ready("worker")
                    break

    elif loading_in_progress is True:
        loading_in_progress = False
        pctl.notify_change()

    if loaderCommand == LC_Done:
        loaderCommand = LC_None
        gui.update += 1
        # gui.pl_update = 1
        # loading_in_progress = False

    if update_layout:
        update_layout_do()
        update_layout = False

    # if tauon.worker_save_state and\
    #         not gui.pl_pulse and\
    #         not loading_in_progress and\
    #         not to_scan and\
    #         not plex.scanning and\
    #         not cm_clean_db and\
    #         not lastfm.scanning_friends and\
    #         not move_in_progress:
    #     save_state()
    #     cue_list.clear()
    #     tauon.worker_save_state = False

    # -----------------------------------------------------
    # THEME SWITCHER--------------------------------------------------------------------

    if gui.reload_theme is True:

        gui.pl_update = 1
        theme_files = get_themes()

        if theme > len(theme_files):  # sic
            theme = 0

        if theme > 0:
            theme_number = theme - 1
            try:

                colours.column_colours.clear()
                colours.column_colours_playing.clear()

                theme_item = theme_files[theme_number]

                gui.theme_name = theme_item[1]
                colours.lm = False
                colours.__init__()

                load_theme(colours, theme_item[0])
                print("Applying theme: " + gui.theme_name)

                if colours.lm:
                    info_icon.colour = [60, 60, 60, 255]
                else:
                    info_icon.colour = [61, 247, 163, 255]

                if colours.lm:
                    folder_icon.colour = [255, 190, 80, 255]
                else:
                    folder_icon.colour = [244, 220, 66, 255]

                if colours.lm:
                    settings_icon.colour = [85, 187, 250, 255]
                else:
                    settings_icon.colour = [232, 200, 96, 255]

                if colours.lm:
                    radiorandom_icon.colour = [120, 200, 120, 255]
                else:
                    radiorandom_icon.colour = [153, 229, 133, 255]

            except:
                raise
                show_message("Error loading theme file", "", mode='warning')

        if theme == 0:
            gui.theme_name = "Mindaro"
            print("Applying default theme: Mindaro")
            colours.lm = False
            colours.__init__()
            colours.post_config()

        prefs.theme_name = gui.theme_name

        # print("Theme number: " + str(theme))
        gui.reload_theme = False
        ddt.text_background_colour = colours.playlist_panel_background

    # ---------------------------------------------------------------------------------------------------------
    # GUI DRAWING------
    # print(gui.update)
    # print(gui.lowered)
    if gui.mode == 3:
        gui.pl_update = 0

    if gui.pl_update and not gui.update:
        gui.update = 1

    if gui.update > 0 and not resize_mode:
        if gui.update > 2:
            gui.update = 2

        SDL_SetRenderTarget(renderer, None)
        SDL_SetRenderDrawColor(renderer, colours.top_panel_background[0], colours.top_panel_background[1],
                               colours.top_panel_background[2], colours.top_panel_background[3])
        SDL_RenderClear(renderer)
        SDL_SetRenderTarget(renderer, gui.main_texture)
        SDL_RenderClear(renderer)

        # perf_timer.set()
        gui.update_on_drag = False
        gui.pl_update_on_drag = False

        #mouse_position[0], mouse_position[1] = get_sdl_input.mouse()
        gui.showed_title = False

        if not gui.mouse_in_window and not bottom_bar1.volume_bar_being_dragged and not bottom_bar1.volume_hit and not bottom_bar1.seek_hit:
            mouse_position[0] = -300
            mouse_position[1] = -300

        if gui.clear_image_cache_next:
            gui.clear_image_cache_next -= 1
            album_art_gen.clear_cache()

        fields.clear()
        gui.cursor_want = 0

        gui.layer_focus = 0

        if inp.mouse_click or mouse_wheel or right_click:
            mouse_position[0], mouse_position[1] = get_sdl_input.mouse()

        if inp.mouse_click:
            n_click_time = time.time()
            if n_click_time - click_time < 0.42:
                d_mouse_click = True
            click_time = n_click_time

            # Don't register bottom level click when closing message box
            if gui.message_box and pref_box.enabled and not key_focused and not coll(message_box.get_rect()):
                inp.mouse_click = False
                gui.message_box = False

        # Enable the garbage collecter (since we disabled it during startup)
        if ggc > 0:
            if ggc == 2:
                ggc = 1
            elif ggc == 1:
                ggc = 0
                gbc.enable()
                # print("Enabling garbage collecting")

        if gui.mode == 4:
            launch.render()
        elif gui.mode == 1 or gui.mode == 2:

            ddt.text_background_colour = colours.playlist_panel_background

            # Side Bar Draging----------

            if mouse_down is not True:
                side_drag = False

            rect = (window_size[0] - gui.rspw - 5 * gui.scale, gui.panelY, 12 * gui.scale,
                            window_size[1] - gui.panelY - gui.panelBY)
            fields.add(rect)

            if (coll(rect) or side_drag is True) \
                    and rename_track_box.active is False \
                    and radiobox.active is False \
                    and gui.rename_playlist_box is False \
                    and gui.message_box is False \
                    and pref_box.enabled is False \
                    and track_box is False \
                    and not gui.rename_folder_box \
                    and not Menu.active \
                    and (gui.rsp or album_mode)\
                    and not artist_info_scroll.held \
                    and gui.layer_focus == 0 and gui.show_playlist:

                if side_drag is True:
                    draw_sep_hl = True
                    #gui.update += 1
                    gui.update_on_drag = True

                if inp.mouse_click:
                    side_drag = True
                    gui.side_bar_drag_source = mouse_position[0]
                    gui.side_bar_drag_original = gui.rspw

                if not quick_drag:
                    gui.cursor_want = 1

            # side drag update
            if side_drag:

                offset = gui.side_bar_drag_source - mouse_position[0]

                target = gui.side_bar_drag_original + offset

                # Snap to album mode position if close
                if not album_mode and prefs.side_panel_layout == 1:
                    if abs(target - gui.pref_gallery_w) < 35 * gui.scale:
                        target = gui.pref_gallery_w

                # Reset max ratio if drag drops below ratio width
                if prefs.side_panel_layout == 0:
                    if target < round((window_size[1] - gui.panelY - gui.panelBY) * gui.art_aspect_ratio):
                        gui.art_max_ratio_lock = gui.art_aspect_ratio

                    max_w = round(((window_size[1] - gui.panelY - gui.panelBY - 17 * gui.scale) * gui.art_max_ratio_lock) + 17 * gui.scale)
                    # 17 here is the art box inset value

                else:
                    max_w = window_size[0]

                if not album_mode and target > max_w - 12 * gui.scale:
                    target = max_w
                    gui.rspw = target
                    gui.rsp_full_lock = True

                else:
                    gui.rspw = target
                    gui.rsp_full_lock = False

                if album_mode:
                    pass
                    #gui.rspw = target

                if album_mode and gui.rspw < album_mode_art_size + 50 * gui.scale:
                    target = album_mode_art_size + 50 * gui.scale

                # Prevent side bar getting too small
                if target < 120 * gui.scale:
                    target = 120 * gui.scale

                # Remember size for this view mode
                if not album_mode:
                    gui.pref_rspw = target
                else:
                    gui.pref_gallery_w = target

                update_layout_do()


            # ALBUM GALLERY RENDERING:
            # Gallery view
            # C-AR

            if album_mode:
                try:
                    # Arrow key input
                    if gal_right:
                        gal_right = False
                        gal_jump_select(False, 1)
                        goto_album(playlist_selected)
                        pctl.playlist_view_position = playlist_selected
                        console.print("DEBUG: Position changed by gallery key press")
                        gui.pl_update = 1
                    if gal_down:
                        gal_down = False
                        gal_jump_select(False, row_len)
                        goto_album(playlist_selected, down=True)
                        pctl.playlist_view_position = playlist_selected
                        console.print("DEBUG: Position changed by gallery key press")
                        gui.pl_update = 1
                    if gal_left:
                        gal_left = False
                        gal_jump_select(True, 1)
                        goto_album(playlist_selected)
                        pctl.playlist_view_position = playlist_selected
                        console.print("DEBUG: Position changed by gallery key press")
                        gui.pl_update = 1
                    if gal_up:
                        gal_up = False
                        gal_jump_select(True, row_len)
                        goto_album(playlist_selected)
                        pctl.playlist_view_position = playlist_selected
                        console.print("DEBUG: Position changed by gallery key press")
                        gui.pl_update = 1

                    w = gui.rspw

                    if window_size[0] < 750 * gui.scale:
                        w = window_size[0] - 20 * gui.scale
                        if gui.lsp:
                            w -= gui.lspw

                    x = window_size[0] - w
                    h = window_size[1] - gui.panelY - gui.panelBY

                    if not gui.show_playlist and inp.mouse_click:
                        left = 0
                        if gui.lsp:
                            left = gui.lspw

                        if left < mouse_position[0] < left + 20 * gui.scale and window_size[1] - gui.panelBY > mouse_position[1] > gui.panelY:
                            toggle_album_mode()
                            inp.mouse_click = False
                            mouse_down = False

                    rect = [x, gui.panelY, w, h]
                    ddt.rect(rect, colours.gallery_background)
                    #ddt.rect_r(rect, [255, 0, 0, 200], True)

                    area_x = w + 38 * gui.scale
                    #area_x = w - 40 * gui.scale

                    row_len = int((area_x - album_h_gap) / (album_mode_art_size + album_h_gap))

                    # print(row_len)

                    compact = 40 * gui.scale
                    a_offset = 7 * gui.scale

                    l_area = x
                    r_area = w
                    c_area = r_area // 2 + l_area

                    ddt.text_background_colour = colours.gallery_background

                    line1_colour = colours.gallery_artist_line
                    line2_colour = colours.grey(240)  # colours.side_bar_line1

                    if colours.side_panel_background != colours.gallery_background:
                        line2_colour = [240, 240, 240, 255]
                        line1_colour = alpha_mod([220, 220, 220, 255], 120)

                    if test_lumi(colours.gallery_background) < 0.5 or (prefs.use_card_style and colours.lm):
                        line1_colour = colours.grey(80)
                        line2_colour = colours.grey(40)

                    if row_len == 0:
                        row_len = 1

                    dev = int((r_area - compact) / (row_len + 0))

                    render_pos = 0
                    album_on = 0

                    max_scroll = round((math.ceil((len(album_dex)) / row_len) - 1) * (album_mode_art_size + album_v_gap)) - round(50 * gui.scale)

                    # Mouse wheel scrolling
                    if not search_over.active and not radiobox.active \
                            and mouse_position[0] > window_size[0] - w and gui.panelY < mouse_position[1] < window_size[1] - gui.panelBY:

                        if mouse_wheel != 0:
                            scroll_gallery_hide_timer.set()
                            gui.frame_callback_list.append(TestTimer(0.9))

                        if prefs.gallery_row_scroll:
                            gui.album_scroll_px -= mouse_wheel * (album_mode_art_size + album_v_gap)  # 90
                        else:
                            gui.album_scroll_px -= mouse_wheel * prefs.gallery_scroll_wheel_px

                        if gui.album_scroll_px < round(album_v_slide_value * -1):
                            gui.album_scroll_px = round(album_v_slide_value * -1)
                            if album_dex:
                                gallery_pulse_top.pulse()

                        if gui.album_scroll_px > max_scroll:
                            gui.album_scroll_px = max_scroll
                            if gui.album_scroll_px < round(album_v_slide_value * -1):
                                gui.album_scroll_px = round(album_v_slide_value * -1)


                    rect = (gui.gallery_scroll_field_left, gui.panelY, window_size[0] - gui.gallery_scroll_field_left - 2, h)

                    card_mode = False
                    if prefs.use_card_style and colours.lm and gui.gallery_show_text:
                        card_mode = True

                    rect = (window_size[0] - 40 * gui.scale, gui.panelY, 38 * gui.scale, h)
                    fields.add(rect)

                    # Show scroll area
                    if coll(rect) or gallery_scroll.held or scroll_gallery_hide_timer.get() < 0.9 or gui.album_tab_mode:

                        if gallery_scroll.held:
                            while len(gall_ren.queue) > 2:
                                gall_ren.queue.pop()

                        # Draw power bar button
                        if gui.pt == 0 and gui.power_bar is not None and len(gui.power_bar) > 3:
                            rect = (window_size[0] - (15 + 20) * gui.scale, gui.panelY + 3 * gui.scale, 18 * gui.scale, 24 * gui.scale)
                            fields.add(rect)
                            colour = [255, 255, 255, 35]
                            if colours.lm:
                                colour = [0, 0, 0, 30]
                            if coll(rect) and not gallery_scroll.held:
                                colour = [255, 220, 100, 245]
                                if colours.lm:
                                    colour = [250, 100, 0, 255]
                                if inp.mouse_click:
                                    gui.pt = 1

                            power_bar_icon.render(rect[0] + round(5 * gui.scale), rect[1] + round(3 * gui.scale), colour)

                        # Draw scroll bar
                        if gui.pt == 0:
                            gui.album_scroll_px = gallery_scroll.draw(window_size[0] - 16 * gui.scale, gui.panelY, 15 * gui.scale, window_size[1] - (gui.panelY + gui.panelBY), gui.album_scroll_px + album_v_slide_value, max_scroll + album_v_slide_value, jump_distance=1400 * gui.scale, r_click=right_click, extend_field=15*gui.scale) - album_v_slide_value


                    if last_row != row_len:
                        last_row = row_len

                        if playlist_selected < len(pctl.playing_playlist()):
                            goto_album(playlist_selected)
                        # else:
                        #     goto_album(pctl.playlist_playing_position)

                    extend = 0
                    if card_mode:  # gui.gallery_show_text:
                        extend = 40 * gui.scale

                    # Process inputs first
                    if (inp.mouse_click or right_click or middle_click or mouse_down or mouse_up) and default_playlist:
                        while render_pos < gui.album_scroll_px + window_size[1]:

                            if b_info_bar and render_pos > gui.album_scroll_px + b_info_y:
                                break

                            if render_pos < gui.album_scroll_px - album_mode_art_size - album_v_gap:
                                # Skip row
                                render_pos += album_mode_art_size + album_v_gap
                                album_on += row_len
                            else:
                                # render row
                                y = render_pos - gui.album_scroll_px
                                row_x = 0
                                for a in range(row_len):
                                    if album_on > len(album_dex) - 1:
                                        break

                                    x = (l_area + dev * a) - int(album_mode_art_size / 2) + int(dev / 2) + int(
                                        compact / 2) - a_offset

                                    if album_dex[album_on] > len(default_playlist):
                                        break

                                    rect = (x, y, album_mode_art_size, album_mode_art_size + extend * gui.scale)
                                    # fields.add(rect)
                                    m_in = coll(rect) and gui.panelY < mouse_position[1] < window_size[1] - gui.panelBY

                                    # if m_in:
                                    #     ddt.rect_r((x - 7, y - 7, album_mode_art_size + 14, album_mode_art_size + extend + 55), [80, 80, 80, 80], True)

                                    # Quick drag and drop
                                    if mouse_up and (playlist_hold and m_in) and not side_drag and shift_selection:

                                        info = get_album_info(album_dex[album_on])
                                        if info[1]:

                                            track_position = info[1][0]

                                            if track_position > shift_selection[0]:
                                                track_position = info[1][-1] + 1

                                            ref = []
                                            for item in shift_selection:
                                                ref.append(default_playlist[item])

                                            for item in shift_selection:
                                                default_playlist[item] = 'old'

                                            for item in shift_selection:
                                                default_playlist.insert(track_position, "new")

                                            for b in reversed(range(len(default_playlist))):
                                                if default_playlist[b] == 'old':
                                                    del default_playlist[b]
                                            shift_selection = []
                                            for b in range(len(default_playlist)):
                                                if default_playlist[b] == 'new':
                                                    shift_selection.append(b)
                                                    default_playlist[b] = ref.pop(0)

                                            playlist_selected = shift_selection[0]
                                            gui.pl_update += 1
                                            playlist_hold = False

                                            reload_albums(True)
                                            pctl.notify_change()

                                    elif not side_drag and is_level_zero():

                                        if coll_point(click_location, rect) and gui.panelY < mouse_position[1] < window_size[1] - gui.panelBY:
                                            info = get_album_info(album_dex[album_on])

                                            if m_in and mouse_up and prefs.gallery_single_click:

                                                if is_level_zero() and gui.d_click_ref == album_dex[album_on]:

                                                    if info[0] == 1 and pctl.playing_state == 2:
                                                        pctl.play()
                                                    elif info[0] == 1 and pctl.playing_state > 0:
                                                        pctl.playlist_view_position = album_dex[album_on]
                                                        console.print("DEBUG: Position changed by gallery click")
                                                    else:
                                                        pctl.playlist_view_position = album_dex[album_on]
                                                        console.print("DEBUG: Position changed by gallery click")
                                                        pctl.jump(default_playlist[album_dex[album_on]], album_dex[album_on])

                                                    pctl.show_current()

                                            elif mouse_down and not m_in:
                                                info = get_album_info(album_dex[album_on])
                                                quick_drag = True
                                                if not pl_is_locked(pctl.active_playlist_viewing) or key_shift_down:
                                                    playlist_hold = True
                                                shift_selection = info[1]
                                                gui.pl_update += 1
                                                click_location = [0, 0]

                                    if m_in:

                                        info = get_album_info(album_dex[album_on])
                                        if inp.mouse_click:

                                            if prefs.gallery_single_click:
                                                gui.d_click_ref = album_dex[album_on]

                                            else:

                                                if d_click_timer.get() < 0.5 and gui.d_click_ref == album_dex[album_on]:

                                                    if info[0] == 1 and pctl.playing_state == 2:
                                                        pctl.play()
                                                    elif info[0] == 1 and pctl.playing_state > 0:
                                                        pctl.playlist_view_position = album_dex[album_on]
                                                        console.print("DEBUG: Position changed by gallery click")
                                                    else:
                                                        pctl.playlist_view_position = album_dex[album_on]
                                                        console.print("DEBUG: Position changed by gallery click")
                                                        pctl.jump(default_playlist[album_dex[album_on]], album_dex[album_on])

                                                else:
                                                    gui.d_click_ref = album_dex[album_on]
                                                    d_click_timer.set()


                                                pctl.playlist_view_position = album_dex[album_on]
                                                console.print("DEBUG: Position changed by gallery click")
                                                playlist_selected = album_dex[album_on]
                                                gui.pl_update += 1

                                        elif middle_click:
                                            # Middle click to add album to queue
                                            if key_ctrl_down:
                                                # Add to queue ungrouped
                                                album = get_album_info(album_dex[album_on])[1]
                                                for item in album:
                                                    pctl.force_queue.append(queue_item_gen(default_playlist[item], item, pl_to_id(
                                                        pctl.active_playlist_viewing)))
                                                queue_timer_set(plural=True)
                                                if prefs.stop_end_queue:
                                                    pctl.auto_stop = False
                                            else:
                                                # Add to queue grouped
                                                add_album_to_queue(default_playlist[album_dex[album_on]])

                                        elif right_click:
                                            if pctl.quick_add_target:

                                                pl = id_to_pl(pctl.quick_add_target)
                                                if pl is not None:
                                                    parent = pctl.g(
                                                        default_playlist[album_dex[album_on]]).parent_folder_path
                                                    # remove from target pl
                                                    if default_playlist[album_dex[album_on]] in pctl.multi_playlist[pl][2]:
                                                        for i in reversed(range(len(pctl.multi_playlist[pl][2]))):
                                                            if pctl.g(pctl.multi_playlist[pl][2][i]).parent_folder_path == parent:
                                                                del pctl.multi_playlist[pl][2][i]
                                                    else:
                                                        # add
                                                        for i in range(len(default_playlist)):
                                                            if pctl.g(default_playlist[i]).parent_folder_path == parent:
                                                                pctl.multi_playlist[pl][2].append(default_playlist[i])

                                                reload_albums(True)

                                            else:
                                                playlist_selected = album_dex[album_on]
                                                #playlist_position = playlist_selected
                                                shift_selection = [playlist_selected]
                                                gallery_menu.activate(default_playlist[playlist_selected])
                                                r_menu_position = playlist_selected

                                                shift_selection = []
                                                u = playlist_selected
                                                while u < len(default_playlist) and pctl.master_library[
                                                            default_playlist[u]].parent_folder_path == \
                                                        pctl.master_library[
                                                            default_playlist[playlist_selected]].parent_folder_path:
                                                    shift_selection.append(u)
                                                    u += 1
                                                pctl.render_playlist()

                                    album_on += 1

                                if album_on > len(album_dex):
                                    break
                                render_pos += album_mode_art_size + album_v_gap


                    render_pos = 0
                    album_on = 0
                    album_count = 0

                    if not pref_box.enabled or mouse_wheel != 0:
                        gui.first_in_grid = None

                    # Render album grid
                    while render_pos < gui.album_scroll_px + window_size[1] and default_playlist:

                        if b_info_bar and render_pos > gui.album_scroll_px + b_info_y:
                            break

                        if render_pos < gui.album_scroll_px - album_mode_art_size - album_v_gap:
                            # Skip row
                            render_pos += album_mode_art_size + album_v_gap
                            album_on += row_len
                        else:
                            # render row
                            y = render_pos - gui.album_scroll_px

                            row_x = 0

                            if y > window_size[1] - gui.panelBY - 30 * gui.scale and window_size[1] < 340 * gui.scale:
                                break
                            # if y >

                            for a in range(row_len):

                                if album_on > len(album_dex) - 1:
                                    break

                                x = (l_area + dev * a) - int(album_mode_art_size / 2) + int(dev / 2) + int(
                                    compact / 2) - a_offset

                                if album_dex[album_on] > len(default_playlist):
                                    break

                                track = pctl.master_library[default_playlist[album_dex[album_on]]]

                                info = get_album_info(album_dex[album_on])
                                #info = (0, 0, 0)

                                # rect = (x, y, album_mode_art_size, album_mode_art_size + extend * gui.scale)
                                # fields.add(rect)
                                # m_in = coll(rect) and gui.panelY < mouse_position[1] < window_size[1] - gui.panelBY

                                if gui.first_in_grid is None and y > gui.panelY:  # This marks what track is the first in the grid
                                    gui.first_in_grid = album_dex[album_on]

                                #artisttitle = colours.side_bar_line2
                                #albumtitle = colours.side_bar_line1  # grey(220)

                                if card_mode:
                                    ddt.text_background_colour = colours.grey(250)
                                    drop_shadow.render(x + 3 * gui.scale, y + 3 * gui.scale, album_mode_art_size + 11 * gui.scale, album_mode_art_size + 45 * gui.scale + 13 * gui.scale)
                                    ddt.rect((x, y, album_mode_art_size, album_mode_art_size + 45 * gui.scale),
                                             colours.grey(250))


                                # White background needs extra border
                                if colours.lm and not card_mode:
                                    ddt.rect_a((x - 2, y - 2), (album_mode_art_size + 4, album_mode_art_size + 4),
                                               colours.grey(200))

                                if a == row_len - 1:

                                    gui.gallery_scroll_field_left = max(x + album_mode_art_size, window_size[0] - round(50 * gui.scale))

                                if info[0] == 1 and 0 < pctl.playing_state < 3:
                                    ddt.rect_a((x - 4, y - 4), (album_mode_art_size + 8, album_mode_art_size + 8),
                                               colours.gallery_highlight)
                                    # ddt.rect_a((x, y), (album_mode_art_size, album_mode_art_size),
                                    #            colours.gallery_background, True)

                                # Draw quick add highlight
                                if pctl.quick_add_target:
                                    pl = id_to_pl(pctl.quick_add_target)
                                    if pl is not None and default_playlist[album_dex[album_on]] in pctl.multi_playlist[pl][2]:
                                        c = [110, 233, 90, 255]
                                        if colours.lm:
                                            c = [66, 244, 66, 255]
                                        ddt.rect_a((x - 4, y - 4), (album_mode_art_size + 8, album_mode_art_size + 8),
                                                   c)

                                # Draw transcode highlight
                                if transcode_list and os.path.isdir(prefs.encoder_output):

                                    tr = False

                                    if (encode_folder_name(track) in os.listdir(prefs.encoder_output)):
                                        tr = True
                                    else:
                                        for folder in transcode_list:
                                            if pctl.g(folder[0]).parent_folder_path == track.parent_folder_path:
                                                tr = True
                                                break
                                    if tr:
                                        c = [244, 212, 66, 255]
                                        if colours.lm:
                                            c = [244, 64, 244, 255]
                                        ddt.rect_a((x - 4, y - 4), (album_mode_art_size + 8, album_mode_art_size + 8),
                                                   c)
                                        # ddt.rect_a((x, y), (album_mode_art_size, album_mode_art_size),
                                        #            colours.gallery_background, True)

                                # Draw selection

                                if (gui.album_tab_mode or gallery_menu.active) and info[2] is True:

                                    c = colours.gallery_highlight
                                    c = [c[1], c[2], c[0], c[3]]
                                    ddt.rect_a((x - 4, y - 4), (album_mode_art_size + 8, album_mode_art_size + 8),
                                               c)  #[150, 80, 222, 255]
                                    # ddt.rect_a((x, y), (album_mode_art_size, album_mode_art_size),
                                    #            colours.gallery_background, True)

                                # Draw selection animation
                                if gui.gallery_animate_highlight_on == album_dex[album_on] and gallery_select_animate_timer.get() < 1.5:

                                    t = gallery_select_animate_timer.get()
                                    c = colours.gallery_highlight
                                    if t < 0.2:
                                        a = int(255 * (t / 0.2))
                                    elif t < 0.5:
                                        a = 255
                                    else:
                                        a = int(255 - 255 * (t - 0.5))


                                    c = [c[1], c[2], c[0], a]
                                    ddt.rect_a((x - 5, y - 5), (album_mode_art_size + 10, album_mode_art_size + 10),
                                               c)  #[150, 80, 222, 255]

                                    gui.update += 1


                                # Draw faint outline
                                ddt.rect((x - 1, y - 1, album_mode_art_size + 2, album_mode_art_size + 2),
                                         [255, 255, 255, 11])


                                if gui.album_tab_mode or gallery_menu.active:
                                    if info[2] is False and info[0] != 1 and not colours.lm:
                                        ddt.rect_a((x, y), (album_mode_art_size, album_mode_art_size), [0, 0, 0, 110])
                                        albumtitle = colours.grey(160)

                                else:
                                    if info[0] != 1 and pctl.playing_state != 0 and prefs.dim_art:
                                        ddt.rect_a((x, y), (album_mode_art_size, album_mode_art_size), [0, 0, 0, 110])
                                        albumtitle = colours.grey(160)



                                # Draw blank back colour
                                back_colour = [40, 40, 40, 50]
                                if colours.lm:
                                    back_colour = [10, 10, 10, 15]

                                back_colour = alpha_blend([10, 10, 10, 15], colours.gallery_background)

                                ddt.rect_a((x, y), (album_mode_art_size, album_mode_art_size), back_colour)

                                # Draw album art
                                album_count += 1
                                if (album_count * 1.5) + 10 > gall_ren.limit:
                                    gall_ren.limit = round((album_count * 1.5) + 30)
                                drawn_art = gall_ren.render(track, (x, y))

                                # Determine mouse collision
                                rect = (x, y, album_mode_art_size, album_mode_art_size + extend * gui.scale)
                                m_in = coll(rect) and gui.panelY < mouse_position[1] < window_size[1] - gui.panelBY
                                fields.add(rect)

                                # Draw mouse-over highlight
                                if (not gallery_menu.active and m_in) or (gallery_menu.active and info[2]):
                                    if is_level_zero():
                                        ddt.rect(rect, [255, 255, 255, 10])

                                if drawn_art is False and gui.gallery_show_text is False:

                                    ddt.text((x + int(album_mode_art_size / 2), y + album_mode_art_size - 22 * gui.scale, 2),
                                             pctl.master_library[default_playlist[album_dex[album_on]]].parent_folder_name,
                                             colours.gallery_artist_line,
                                             13,
                                             album_mode_art_size - 15 * gui.scale,
                                             bg=alpha_blend(back_colour, colours.gallery_background))


                                if prefs.art_bg and drawn_art:
                                    rect = SDL_Rect(round(x), round(y), album_mode_art_size, album_mode_art_size)
                                    if rect.y < gui.panelY:
                                        diff = round(gui.panelY - rect.y)
                                        rect.y += diff
                                        rect.h -= diff
                                    elif (rect.y + rect.h) > window_size[1] - gui.panelBY:
                                        diff = round((rect.y + rect.h) - (window_size[1] - gui.panelBY))
                                        rect.h -= diff

                                    if rect.h > 0:
                                        style_overlay.hole_punches.append(rect)

                                # # Drag over highlight
                                # if quick_drag and playlist_hold and mouse_down:
                                #     rect = (x, y, album_mode_art_size, album_mode_art_size + extend * gui.scale)
                                #     m_in = coll(rect) and gui.panelY < mouse_position[1] < window_size[1] - gui.panelBY
                                #     if m_in:
                                #         ddt.rect_a((x, y), (album_mode_art_size, album_mode_art_size), [120, 10, 255, 100], True)

                                if gui.gallery_show_text:
                                    c_index = default_playlist[album_dex[album_on]]

                                    if c_index in album_artist_dict:
                                        pass
                                    else:
                                        i = album_dex[album_on]
                                        if pctl.master_library[default_playlist[i]].album_artist:
                                            album_artist_dict[c_index] = pctl.master_library[default_playlist[i]].album_artist
                                        else:
                                            while i < len(default_playlist) - 1:
                                                if pctl.master_library[default_playlist[i]].parent_folder_name != \
                                                        pctl.master_library[
                                                            default_playlist[album_dex[album_on]]].parent_folder_name:
                                                    album_artist_dict[c_index] = pctl.master_library[
                                                        default_playlist[album_dex[album_on]]].artist
                                                    break
                                                if pctl.master_library[default_playlist[i]].artist != pctl.master_library[
                                                    default_playlist[album_dex[album_on]]].artist:
                                                    album_artist_dict[c_index] = "Various Artists"

                                                    break
                                                i += 1
                                            else:
                                                album_artist_dict[c_index] = pctl.master_library[
                                                    default_playlist[album_dex[album_on]]].artist


                                    line = album_artist_dict[c_index]
                                    line2 = pctl.master_library[default_playlist[album_dex[album_on]]].album


                                    text_align = 0
                                    if prefs.center_gallery_text:
                                        x = x + album_mode_art_size // 2
                                        text_align = 2
                                    elif card_mode:
                                        x += round(6 * gui.scale)

                                    if card_mode:

                                        if line2 == "":

                                            ddt.text((x, y + album_mode_art_size + 8 * gui.scale, text_align),
                                                     line,
                                                     line1_colour,
                                                     310,
                                                     album_mode_art_size - 18 * gui.scale,
                                                     )
                                        else:

                                            ddt.text((x, y + album_mode_art_size + 7 * gui.scale, text_align),
                                                     line2,
                                                     line2_colour,
                                                     311,
                                                     album_mode_art_size - 18 * gui.scale,
                                                     )

                                            ddt.text((x, y + album_mode_art_size + (10 + 14) * gui.scale, text_align),
                                                     line,
                                                     line1_colour,
                                                     10,
                                                     album_mode_art_size - 18 * gui.scale,
                                                     )
                                    else:
                                        if line2 == "":

                                            ddt.text((x, y + album_mode_art_size + 9 * gui.scale, text_align),
                                                     line,
                                                     line1_colour,
                                                     311,
                                                     album_mode_art_size - 5 * gui.scale,
                                                     )
                                        else:

                                            ddt.text((x, y + album_mode_art_size + 8 * gui.scale, text_align),
                                                     line2,
                                                     line2_colour,
                                                     212,
                                                     album_mode_art_size,
                                                     )

                                            ddt.text((x, y + album_mode_art_size + (10 + 14) * gui.scale, text_align),
                                                     line,
                                                     line1_colour,
                                                     311,
                                                     album_mode_art_size - 5 * gui.scale,
                                                     )

                                album_on += 1

                            if album_on > len(album_dex):
                                break
                            render_pos += album_mode_art_size + album_v_gap


                    # POWER TAG BAR --------------

                    if gui.pt > 0: #gui.pt > 0 or (gui.power_bar is not None and len(gui.power_bar) > 1):

                        top = gui.panelY
                        run_y = top + 1

                        hot_r = (window_size[0] - 47 * gui.scale, top, 45 * gui.scale, h)
                        fields.add(hot_r)

                        if gui.pt == 0:  # mouse moves in
                            if coll(hot_r) and window_is_focused():
                                gui.pt_on.set()
                                gui.pt = 1
                        elif gui.pt == 1:  # wait then trigger if stays, reset if goes out
                            if not coll(hot_r):
                                gui.pt = 0
                            elif gui.pt_on.get() > 0.2:
                                gui.pt = 2

                                off = 0
                                for item in gui.power_bar:
                                    item.ani_timer.force_set(off)
                                    off -= 0.005

                        elif gui.pt == 2: # wait to turn off

                            if coll(hot_r):
                                gui.pt_off.set()
                            if gui.pt_off.get() > 0.6 and not lightning_menu.active:
                                gui.pt = 3

                                off = 0
                                for item in gui.power_bar:
                                    item.ani_timer.force_set(off)
                                    off -= 0.01


                        done = True
                        # Animate tages on
                        if gui.pt == 2:
                            for item in gui.power_bar:
                                t = item.ani_timer.get()
                                if t < 0:
                                    break
                                if t > 0.2:
                                    item.peak_x = 9 * gui.scale
                                else:
                                    item.peak_x = (t / 0.2) * 9 * gui.scale

                        # Animate tags off
                        if gui.pt == 3:
                            for item in gui.power_bar:
                                t = item.ani_timer.get()
                                if t < 0:
                                    done = False
                                    break
                                if t > 0.2:
                                    item.peak_x = 0
                                else:
                                    item.peak_x = 9 * gui.scale - ((t / 0.2) * 9 * gui.scale)
                                    done = False
                            if done:
                                gui.pt = 0
                                gui.update += 1

                        # Keep draw loop running while on
                        if gui.pt > 0:
                            gui.update = 2


                        # Draw tags

                        block_h = round(27 * gui.scale)
                        block_gap = 1 * gui.scale
                        if gui.scale == 1.25:
                            block_gap = 1

                        if coll(hot_r) or gui.pt > 0:

                            for i, item in enumerate(gui.power_bar):

                                if run_y + block_h > top + h:
                                    break


                                rect = [window_size[0] - item.peak_x, run_y, 7 * gui.scale, block_h]
                                i_rect = [window_size[0] - 36 * gui.scale, run_y, 34 * gui.scale, block_h]
                                fields.add(i_rect)

                                if (coll(i_rect) or (lightning_menu.active and lightning_menu.reference
                                == item)) and item.peak_x == 9 * gui.scale:

                                    if not lightning_menu.active or lightning_menu.reference == item or right_click:

                                        minx = 100 * gui.scale
                                        maxx = minx * 2

                                        ww = ddt.get_text_w(item.name, 213)

                                        w = max(minx, ww)
                                        w = min(maxx, w)

                                        ddt.rect((rect[0] - w - 25 * gui.scale, run_y, w + 26 * gui.scale, block_h),
                                                 [230, 230, 230, 255])
                                        ddt.text((rect[0] - 10 * gui.scale, run_y + 5 * gui.scale, 1), item.name, [5, 5, 5, 255], 213, w, bg=[230, 230, 230, 255])

                                        if inp.mouse_click:
                                            goto_album(item.position)
                                        if right_click:
                                            lightning_menu.activate(item, position=(window_size[0] - 180 * gui.scale, rect[1] + rect[3] + 5 * gui.scale))
                                        if middle_click:
                                            path_stem_to_playlist(item.path, item.name)

                                ddt.rect(rect, item.colour)
                                run_y += block_h + block_gap

                    gallery_pulse_top.render(window_size[0] - gui.rspw, gui.panelY, gui.rspw - round(16 * gui.scale), 20 * gui.scale)
                except:
                    print("Gallery render error!")
                # END POWER BAR ------------------------

            # End of gallery view
            # --------------------------------------------------------------------------
            # Main Playlist:
            if len(load_orders) > 0:

                for i, order in enumerate(load_orders):
                    if order.stage == 2:
                        target_pl = 0

                        # Sort the tracks by track number
                        sort_track_2(None, order.tracks)

                        for p, playlist in enumerate(pctl.multi_playlist):
                            if playlist[6] == order.playlist:
                                target_pl = p
                                break
                        else:
                            del load_orders[i]
                            print("Error: Target playlist lost")
                            break


                        if order.replace_stem:
                            for ii, id in reversed(list(enumerate(pctl.multi_playlist[target_pl][2]))):
                                pfp = pctl.g(id).parent_folder_path
                                if pfp.startswith(order.target):
                                    if pfp.rstrip("/") == order.target.rstrip("/") or \
                                            (len(pfp) > len(order.target) and pfp[len(order.target.rstrip("/"))] == "/"):
                                        del pctl.multi_playlist[target_pl][2][ii]

                        # print(order.tracks)
                        if order.playlist_position is not None:
                            #print(order.playlist_position)
                            pctl.multi_playlist[target_pl][2][order.playlist_position:order.playlist_position] = order.tracks
                        #else:

                        else:
                            pctl.multi_playlist[target_pl][2] += order.tracks

                        pctl.update_shuffle_pool(pctl.multi_playlist[target_pl][6], order.tracks)



                        gui.update += 2
                        gui.pl_update += 2
                        if order.notify and gui.message_box and len(load_orders) == 1:
                            show_message(_("Rescan folders complete."), mode='done')
                        reload()
                        tree_view_box.clear_target_pl(target_pl)

                        if order.play and order.tracks:

                            for p, plst in enumerate(pctl.multi_playlist):
                                if order.tracks[0] in plst[2]:
                                    target_pl = p
                                    break

                            switch_playlist(target_pl)

                            pctl.active_playlist_playing = pctl.active_playlist_viewing

                            # If already in playlist, delete latest add
                            if "Default" == pctl.multi_playlist[target_pl][0]:
                                if default_playlist.count(order.tracks[0]) > 1:
                                    for q in reversed(range(len(default_playlist))):
                                        if default_playlist[q] == order.tracks[0]:
                                            del default_playlist[q]
                                            break


                            pctl.jump(order.tracks[0], pl_position=default_playlist.index(order.tracks[0]))

                            pctl.show_current(True, True, True, True, True)

                        del load_orders[i]

                        # Are there more orders for this playlist?
                        # If not, decide on a name for the playlist
                        for item in load_orders:
                            if item.playlist == order.playlist:
                                break
                        else:

                            if "New Playlist" in pctl.multi_playlist[target_pl][0]:
                                auto_name_pl(target_pl)

                            if prefs.auto_sort:
                                if pctl.multi_playlist[target_pl][9]:
                                    show_message("Auto sort skipped because playlist is locked.")
                                else:
                                    print("Auto sorting")
                                    standard_sort(target_pl)
                                    year_sort(target_pl)


                        if not load_orders:
                            loading_in_progress = False
                            pctl.notify_change()
                            gui.auto_play_import = False
                            album_artist_dict.clear()
                        break



            if gui.show_playlist:

                # playlist hit test
                if coll((
                gui.playlist_left, gui.playlist_top, gui.plw, window_size[1] - gui.panelY - gui.panelBY)) and not drag_mode and (
                        inp.mouse_click or mouse_wheel != 0 or right_click or middle_click or mouse_up or mouse_down):
                    gui.pl_update = 1

                if gui.combo_mode and mouse_wheel != 0:
                    gui.pl_update = 1

                # MAIN PLAYLIST
                # C-PR

                top = gui.panelY
                if gui.artist_info_panel:
                    top += gui.artist_panel_height

                if gui.set_mode and not gui.set_bar:
                    left = 0
                    if gui.lsp:
                        left = gui.lspw
                    rect = [left, top, gui.plw, 12 * gui.scale]
                    if right_click and coll(rect):
                        set_menu_hidden.activate()
                        right_click = False

                width = gui.plw
                if gui.set_bar and gui.set_mode:
                    left = 0
                    if gui.lsp:
                        left = gui.lspw

                    if gui.tracklist_center_mode:
                        left = gui.tracklist_inset_left - round(20 * gui.scale)
                        width = gui.tracklist_inset_width + round(20 * gui.scale)

                    rect = [left, top, width, gui.set_height]
                    start = left + 16 * gui.scale
                    run = 0
                    in_grip = False

                    if not mouse_down and gui.set_hold != -1:
                        gui.set_hold = -1

                    for h, item in enumerate(gui.pl_st):
                        box = (start + run, rect[1], item[1], rect[3])
                        grip = (start + run, rect[1], 3 * gui.scale, rect[3])
                        m_grip = (grip[0] - 4 * gui.scale, grip[1], grip[2] + 8 * gui.scale, grip[3])
                        l_grip = (grip[0] + 9 * gui.scale, grip[1], box[2] - 14 * gui.scale, grip[3])
                        fields.add(m_grip)

                        if coll(l_grip):
                            if mouse_up and gui.set_label_hold != -1:
                                if point_distance(mouse_position, gui.set_label_point) < 8 * gui.scale:
                                    sort_direction = 0
                                    if h != gui.column_d_click_on or gui.column_d_click_timer.get() > 2.5:
                                        gui.column_d_click_timer.set()
                                        gui.column_d_click_on = h

                                        sort_direction = 1

                                        gui.column_sort_ani_direction = 1
                                        gui.column_sort_ani_x = start + run + item[1]

                                    elif gui.column_d_click_on == h:
                                        gui.column_d_click_on= -1
                                        gui.column_d_click_timer.force_set(10)

                                        sort_direction = -1

                                        gui.column_sort_ani_direction = -1
                                        gui.column_sort_ani_x = start + run + item[1]

                                    if sort_direction:

                                        if gui.pl_st[h][0] in {"Starline", "Rating", "❤", "P", "S", "Time", "Date"}:
                                            sort_direction *= -1

                                        if sort_direction == 1:
                                            sort_ass(h)
                                        else:
                                            sort_ass(h, True)
                                        gui.column_sort_ani_timer.set()

                                else:
                                    gui.column_d_click_on = -1
                                    if h != gui.set_label_hold:
                                        dest = h
                                        if dest > gui.set_label_hold:
                                            dest += 1
                                        temp = gui.pl_st[gui.set_label_hold]
                                        gui.pl_st[gui.set_label_hold] = "old"
                                        gui.pl_st.insert(dest, temp)
                                        gui.pl_st.remove("old")

                                        gui.pl_update = 1
                                        gui.set_label_hold = -1
                                        # print("MOVE")
                                        break

                                    gui.set_label_hold = -1

                            if inp.mouse_click:
                                gui.set_label_hold = h
                                gui.set_label_point = copy.deepcopy(mouse_position)
                            if right_click:
                                set_menu.activate(h)

                        if h != 0:
                            if coll(m_grip):
                                in_grip = True
                                if inp.mouse_click:

                                    gui.set_hold = h
                                    gui.set_point = mouse_position[0]
                                    gui.set_old = gui.pl_st[h - 1][1]

                            if mouse_down and gui.set_hold == h:
                                gui.pl_st[h - 1][1] = gui.set_old + (mouse_position[0] - gui.set_point)
                                if gui.pl_st[h - 1][1] < 25:
                                    gui.pl_st[h - 1][1] = 25

                                gui.update = 1
                                #gui.pl_update = 1

                                total = 0
                                for i in range(len(gui.pl_st) - 1):
                                    total += gui.pl_st[i][1]

                                wid = gui.plw - round(16 * gui.scale)
                                if gui.tracklist_center_mode:
                                    wid = gui.tracklist_highlight_width - round(16 * gui.scale)
                                gui.pl_st[len(gui.pl_st) - 1][1] = wid - total

                        run += item[1]

                    if not mouse_down:
                        gui.set_label_hold = -1
                    # print(in_grip)
                    if gui.set_label_hold == -1:
                        if in_grip and not x_menu.active and not view_menu.active and not tab_menu.active and not set_menu.active:
                            gui.cursor_want = 1
                        if gui.set_hold != -1:
                            gui.cursor_want = 1
                            gui.pl_update_on_drag = True


                # heart field test
                if gui.heart_fields:
                    for field in gui.heart_fields:
                        fields.add(field, update_playlist_call)

                if gui.pl_update > 0:
                    gui.rendered_playlist_position = playlist_view_position

                    gui.pl_update -= 1
                    if gui.combo_mode:
                        if gui.radio_view:
                            radio_view.render()
                        elif gui.showcase_mode:
                            showcase.render()

                        # else:
                        #     combo_pl_render.full_render()
                    else:
                        gui.heart_fields.clear()
                        playlist_render.full_render()

                else:
                    if gui.combo_mode:
                        if gui.radio_view:
                            radio_view.render()
                        elif gui.showcase_mode:
                            showcase.render()
                        # else:
                        #     combo_pl_render.cache_render()
                    else:
                        playlist_render.cache_render()

                if not gui.set_bar and gui.set_mode and not gui.combo_mode:
                    width = gui.plw
                    left = 0
                    if gui.lsp:
                        left = gui.lspw
                    if gui.tracklist_center_mode:
                        left = gui.tracklist_highlight_left
                        width = gui.tracklist_highlight_width
                    rect = [left, top, width, gui.set_height // 2.5]
                    fields.add(rect)
                    gui.delay_frame(0.26)

                    if coll(rect) and gui.bar_hover_timer.get() > 0.25:
                        ddt.rect(rect, colours.column_bar_background)
                        if inp.mouse_click:
                            gui.set_bar = True
                            update_layout_do()
                    if not coll(rect):
                        gui.bar_hover_timer.set()

                if gui.set_bar and gui.set_mode and not gui.combo_mode:

                    x = 0
                    if gui.lsp:
                        x = gui.lspw

                    width = gui.plw

                    if gui.tracklist_center_mode:
                        x = gui.tracklist_highlight_left
                        width = gui.tracklist_highlight_width

                    rect = [x, top, width, gui.set_height]


                    c_bar_background = colours.column_bar_background

                    # if colours.lm:
                    #     c_bar_background = [235, 110, 160, 255]

                    if gui.tracklist_center_mode:
                        ddt.rect((0, top, window_size[0], gui.set_height), c_bar_background)
                    else:
                        ddt.rect(rect, c_bar_background)

                    start = x + 16 * gui.scale
                    c_width = width - 16 * gui.scale

                    run = 0


                    for i, item in enumerate(gui.pl_st):

                        # if run > rect[2] - 55 * gui.scale:
                        #     break


                        wid = item[1]

                        if run + wid > c_width:
                            wid = c_width - run

                        if run > c_width - 22 * gui.scale:
                            break

                        # if run > c_width - 20 * gui.scale:
                        #     run = run - 20 * gui.scale

                        wid = max(0, wid)

                        #ddt.rect_r((run, 40, wid, 10), [255, 0, 0, 100])
                        box = (start + run, rect[1], wid, rect[3])

                        grip = (start + run, rect[1], 3 * gui.scale, rect[3])

                        bg = c_bar_background

                        if coll(box) and gui.set_label_hold != -1:
                            bg = [39, 39, 39, 255]

                        if i == gui.set_label_hold:
                            bg = [22, 22, 22, 255]

                        ddt.rect(box, bg)
                        ddt.rect(grip, colours.column_grip)

                        line = _(item[0])
                        ddt.text_background_colour = bg

                        # # Remove columns if positioned out of view
                        # if box[0] + 10 * gui.scale > start + (gui.plw - 25 * gui.scale):
                        #
                        #     if box[0] + 10 * gui.scale > start + gui.plw:
                        #         del gui.pl_st[i]
                        #
                        #     i += 1
                        #     while i < len(gui.pl_st):
                        #         del gui.pl_st[i]
                        #         i += 1
                        #
                        #     break
                        if line == "❤":
                            heart_row_icon.render(box[0] + 9 * gui.scale, top + 8 * gui.scale, colours.column_bar_text)
                        else:
                            ddt.text((box[0] + 10 * gui.scale, top + 4 * gui.scale), line, colours.column_bar_text, 312, bg=bg, max_w=box[2] - 25 * gui.scale)


                        run += box[2]

                    t = gui.column_sort_ani_timer.get()
                    if t < 0.30:
                        gui.update += 1
                        x = round(gui.column_sort_ani_x - 22 * gui.scale)
                        p = t / 0.30

                        if gui.column_sort_ani_direction == 1:
                            y = top + 8 * p + 3 * gui.scale
                            gui.column_sort_down_icon.render(x, round(y), [255, 255, 255, 90])
                        else:
                            p = 1 - p
                            y = top + 8 * p + 2 * gui.scale
                            gui.column_sort_up_icon.render(x, round(y), [255, 255, 255, 90])


                # Switch Vis:
                if right_click and coll((window_size[0] - 150 * gui.scale - gui.offset_extra, 0, 140 * gui.scale , gui.panelY)) and not gui.top_bar_mode2:
                    vis_menu.activate(None, (window_size[0] - 150 * gui.scale, 30 * gui.scale))

                #edge_playlist.render(gui.playlist_left, gui.panelY, gui.plw, 2 * gui.scale)

                bottom_playlist2.render(gui.playlist_left, window_size[1] - gui.panelBY, gui.plw, 25 * gui.scale, bottom=True)
                # --------------------------------------------
                # ALBUM ART

                # Right side panel drawing

                if gui.rsp and not album_mode:
                    gui.showing_l_panel = False
                    target_track = pctl.show_object()

                    if middle_click:
                        if coll((window_size[0] - gui.rspw, gui.panelY, gui.rspw, window_size[1] - gui.panelY - gui.panelBY)):

                            if (target_track and target_track.lyrics and prefs.show_lyrics_side) or \
                                (prefs.show_lyrics_side and prefs.prefer_synced_lyrics and target_track is not None and timed_lyrics_ren.generate(target_track)):

                                prefs.show_lyrics_side ^= True
                                prefs.side_panel_layout = 1
                            else:

                                if prefs.side_panel_layout == 0:

                                    if (target_track and target_track.lyrics and not prefs.show_lyrics_side) or \
                                            (prefs.prefer_synced_lyrics and target_track is not None and timed_lyrics_ren.generate(target_track)):
                                        prefs.show_lyrics_side = True
                                        prefs.side_panel_layout = 1
                                    else:
                                        prefs.side_panel_layout = 1
                                else:
                                    prefs.side_panel_layout = 0

                    if prefs.show_lyrics_side and prefs.prefer_synced_lyrics and target_track is not None and timed_lyrics_ren.generate(target_track):

                        if prefs.show_side_lyrics_art_panel:
                            l_panel_h = round(200 * gui.scale)
                            l_panel_y = window_size[1] - (gui.panelBY + l_panel_h)
                            gui.showing_l_panel = True

                            if not prefs.lyric_metadata_panel_top:
                                timed_lyrics_ren.render(target_track.index, (window_size[0] - gui.rspw) + 9 * gui.scale, gui.panelY + 25 * gui.scale, side_panel=True, w=gui.rspw, h=window_size[1] - gui.panelY - gui.panelBY - l_panel_h)
                                meta_box.l_panel(window_size[0] - gui.rspw, l_panel_y, gui.rspw, l_panel_h, target_track)
                            else:
                                timed_lyrics_ren.render(target_track.index, (window_size[0] - gui.rspw) + 9 * gui.scale, gui.panelY + 25 * gui.scale + l_panel_h, side_panel=True, w=gui.rspw, h=window_size[1] - gui.panelY - gui.panelBY - l_panel_h)
                                meta_box.l_panel(window_size[0] - gui.rspw, gui.panelY, gui.rspw, l_panel_h, target_track)
                        else:
                            timed_lyrics_ren.render(target_track.index, (window_size[0] - gui.rspw) + 9 * gui.scale, gui.panelY + 25 * gui.scale, side_panel=True, w=gui.rspw, h=window_size[1] - gui.panelY - gui.panelBY)

                            if right_click and coll((window_size[0] - gui.rspw,  gui.panelY + 25 * gui.scale, gui.rspw, window_size[1] - (gui.panelBY + gui.panelY))):
                                center_info_menu.activate(target_track)

                    elif prefs.show_lyrics_side and target_track is not None and target_track.lyrics != "" and gui.rspw > 192 * gui.scale:

                        if prefs.show_side_lyrics_art_panel:
                            l_panel_h = round(200 * gui.scale)
                            l_panel_y = window_size[1] - (gui.panelBY + l_panel_h)
                            gui.showing_l_panel = True

                            if not prefs.lyric_metadata_panel_top:
                                meta_box.lyrics(window_size[0] - gui.rspw, gui.panelY, gui.rspw, window_size[1] - gui.panelY - gui.panelBY - l_panel_h, target_track)
                                meta_box.l_panel(window_size[0] - gui.rspw, l_panel_y, gui.rspw, l_panel_h, target_track)
                            else:
                                meta_box.lyrics(window_size[0] - gui.rspw, gui.panelY + l_panel_h, gui.rspw, window_size[1] - (gui.panelY + gui.panelBY + l_panel_h), target_track)

                                meta_box.l_panel(window_size[0] - gui.rspw, gui.panelY, gui.rspw, l_panel_h, target_track, top_border=False)
                        else:
                            meta_box.lyrics(window_size[0] - gui.rspw, gui.panelY, gui.rspw, window_size[1] - gui.panelY - gui.panelBY, target_track)

                    elif prefs.side_panel_layout == 0:

                        boxw = gui.rspw
                        boxh = gui.rspw

                        if prefs.show_side_art:

                            meta_box.draw(window_size[0] - gui.rspw, gui.panelY + boxh, gui.rspw,
                                          window_size[1] - gui.panelY - gui.panelBY - boxh, track=target_track)

                            boxh = min(boxh, window_size[1] - gui.panelY - gui.panelBY)

                            art_box.draw(window_size[0] - gui.rspw, gui.panelY, boxw, boxh, target_track=target_track)

                        else:
                            meta_box.draw(window_size[0] - gui.rspw, gui.panelY, gui.rspw,
                                          window_size[1] - gui.panelY - gui.panelBY, track=target_track)

                    elif prefs.side_panel_layout == 1:

                        h = window_size[1] - (gui.panelY + gui.panelBY)
                        x = window_size[0] - gui.rspw
                        y = gui.panelY
                        w = gui.rspw

                        ddt.rect((x, y, w, h), colours.side_panel_background)
                        #test_auto_lyrics(target_track)
                        # Draw lyrics if avaliable
                        if prefs.show_lyrics_side and target_track and target_track.lyrics != "": # and not prefs.show_side_art:
                            #meta_box.lyrics(x, y, w, h, target_track)
                            if right_click and coll((x, y, w, h)) and target_track:
                                center_info_menu.activate(target_track)
                        else:

                            box_wide_w = round(w * 0.98)
                            boxx = round(min(h * 0.7, w * 0.9))
                            boxy = round(min(h * 0.7, w * 0.9))

                            bx = (x + w // 2) - (boxx // 2)
                            bx_wide = (x + w // 2) - (box_wide_w // 2)
                            by = round(h * 0.1)

                            bby = by + boxy

                            # We want the text in the center, but slightly raised when area is large
                            text_y = y + by + boxy + ((h - bby) // 2) - 44 * gui.scale - round((h - bby - 94 * gui.scale) * 0.08)

                            small_mode = False
                            if window_size[1] < 550 * gui.scale:
                                small_mode = True
                                text_y = y + by + boxy + ((h - bby) // 2) - 38 * gui.scale

                            text_x = x + w // 2

                            if prefs.show_side_art:
                                gui.art_drawn_rect = None
                                default_border = (bx, by, boxx, boxy)
                                coll_border = default_border

                                art_box.draw(bx_wide, by, box_wide_w, boxy, target_track=target_track, tight_border=True, default_border=default_border)

                                if gui.art_drawn_rect:
                                    coll_border = gui.art_drawn_rect

                                if right_click and coll((x, y, w, h)) and not coll(coll_border):
                                    if is_level_zero(include_menus=False) and target_track:
                                        center_info_menu.activate(target_track)

                            else:
                                text_y = y + round(h * 0.40)
                                if right_click and coll((x, y, w, h)) and target_track:
                                    center_info_menu.activate(target_track)

                            ww = w - 25 * gui.scale

                            gui.showed_title = True

                            if target_track:
                                ddt.text_background_colour = colours.side_panel_background

                                if pctl.playing_state == 3 and not radiobox.dummy_track.title:
                                    title = pctl.tag_meta
                                else:
                                    title = target_track.title
                                    if not title:
                                        title = clean_string(target_track.filename)

                                if small_mode:
                                    ddt.text((text_x, text_y - 15 * gui.scale, 2), target_track.artist,
                                             colours.side_bar_line1, 315, max_w=ww)

                                    ddt.text((text_x, text_y + 12 * gui.scale, 2), title, colours.side_bar_line1, 216,
                                             max_w=ww)

                                    line = " | ".join(
                                        filter(None, (target_track.album, target_track.date, target_track.genre)))
                                    ddt.text((text_x, text_y + 35 * gui.scale, 2), line, colours.side_bar_line2, 313,
                                             max_w=ww)

                                else:
                                    ddt.text((text_x, text_y - 15 * gui.scale, 2), target_track.artist, colours.side_bar_line1, 317, max_w=ww)

                                    ddt.text((text_x, text_y + 17 * gui.scale, 2), title, colours.side_bar_line1, 218, max_w=ww)

                                    line = " | ".join(filter(None, (target_track.album, target_track.date, target_track.genre)))
                                    ddt.text((text_x, text_y + 45 * gui.scale, 2), line, colours.side_bar_line2, 314, max_w=ww)


                # Seperation Line Drawing
                if gui.rsp:

                    # Draw Highlight when mouse over
                    if draw_sep_hl:
                        ddt.line(window_size[0] - gui.rspw + 1 * gui.scale, gui.panelY + 1 * gui.scale,
                                  window_size[0] - gui.rspw + 1 * gui.scale,
                                  window_size[1] - 50 * gui.scale, [100, 100, 100, 70])
                        draw_sep_hl = False


            if (gui.artist_info_panel and not gui.combo_mode) and not (window_size[0] < 750 * gui.scale and album_mode):
                artist_info_box.draw(gui.playlist_left, gui.panelY, gui.plw, gui.artist_panel_height)


            if gui.lsp and not gui.combo_mode:

                # left side panel

                h_estimate = ((playlist_box.tab_h + playlist_box.gap) * gui.scale * len(pctl.multi_playlist)) + 13 * gui.scale

                full = (window_size[1] - (gui.panelY + gui.panelBY))
                half = int(round((full / 2)))

                pl_box_h = full

                panel_rect = (0, gui.panelY, gui.lspw, pl_box_h)
                fields.add(panel_rect)

                if gui.force_side_on_drag and not quick_drag and not coll(panel_rect):
                    gui.force_side_on_drag = False
                    update_layout_do()

                if quick_drag and not coll_point(gui.drag_source_position_persist, panel_rect) and not point_proximity_test(gui.drag_source_position, mouse_position, 10 * gui.scale):
                    gui.force_side_on_drag = True
                    if mouse_up:
                        update_layout_do()


                if prefs.left_panel_mode == "folder view" and not gui.force_side_on_drag:
                    tree_view_box.render(0, gui.panelY, gui.lspw, pl_box_h)
                elif prefs.left_panel_mode == "artist list" and not gui.force_side_on_drag:
                    artist_list_box.render(*panel_rect)
                else:

                    preview_queue = False
                    if quick_drag and coll(panel_rect) and not pctl.force_queue and prefs.show_playlist_list and prefs.hide_queue:
                        preview_queue = True

                    if pctl.force_queue or preview_queue or not prefs.hide_queue:

                        if h_estimate < half:
                            pl_box_h = h_estimate
                        else:
                            pl_box_h = half

                        if preview_queue:
                            pl_box_h = int(round((full * 5 / 6)))

                    if not prefs.left_panel_mode == "queue":

                        playlist_box.draw(0, gui.panelY, gui.lspw, pl_box_h)
                    else:
                        pl_box_h = 0

                    if pctl.force_queue or preview_queue or not prefs.show_playlist_list or not prefs.hide_queue:

                        queue_box.draw(0, gui.panelY + pl_box_h, gui.lspw, full - pl_box_h)
                    elif prefs.left_panel_mode == "queue":
                        text = _("Queue is Empty")
                        rect = (0, gui.panelY + pl_box_h, gui.lspw, full - pl_box_h)
                        ddt.rect(rect, colours.queue_background)
                        ddt.text_background_colour = colours.queue_background
                        ddt.text((0 + (gui.lspw // 2), gui.panelY + pl_box_h + 15 * gui.scale, 2), text, alpha_mod(colours.index_text, 200), 212)

            # ------------------------------------------------
            # Scroll Bar

            # if not scroll_enable:
            top = gui.panelY
            if gui.artist_info_panel:
                top += gui.artist_panel_height

            edge_top = top
            if gui.set_bar and gui.set_mode:
                edge_top += gui.set_height
            edge_playlist2.render(gui.playlist_left, edge_top, gui.plw, 25 * gui.scale)

            width = 15 * gui.scale


            x = 0
            if gui.lsp:  # Move left so it sits over panel divide

                x = gui.lspw - 1 * gui.scale
                if not gui.set_mode:
                    width = 11 * gui.scale
            if gui.set_mode and prefs.left_align_album_artist_title:
                width = 11 * gui.scale

            # x = gui.plw
            # width = round(14 * gui.scale)
            # if gui.lsp:
            #     x += gui.lspw
            # x -= width

            gui.scroll_hide_box = (
                x + 1 if not gui.maximized else x, top, 28 * gui.scale, window_size[1] - gui.panelBY - top)

            fields.add(gui.scroll_hide_box)
            if scroll_hide_timer.get() < 0.9 or (coll(
                    gui.scroll_hide_box) or scroll_hold or quick_search_mode) and \
                    not menu_is_open() and \
                    not pref_box.enabled and \
                    not gui.rename_playlist_box \
                    and gui.layer_focus == 0 and gui.show_playlist and not search_over.active:

                scroll_opacity = 255

                if not gui.combo_mode:
                    sy = 31 * gui.scale
                    ey = window_size[1] - (30 + 22) * gui.scale

                    if len(default_playlist) < 50:
                        sbl = 85 * gui.scale
                        if len(default_playlist) == 0:
                            sbp = top
                    else:
                        sbl = 105 * gui.scale

                    fields.add((x + 2 * gui.scale, sbp, 20 * gui.scale, sbl))
                    if coll((x, top, 28 * gui.scale, ey - top)) and (
                            mouse_down or right_click) \
                            and coll_point(click_location, (x, top, 28 * gui.scale, ey - top)):

                        gui.pl_update = 1
                        if right_click:

                            sbp = mouse_position[1] - int(sbl / 2)
                            if sbp + sbl > ey:
                                sbp = ey - sbl
                            elif sbp < top:
                                sbp = top
                            per = (sbp - top) / (ey - top - sbl)
                            pctl.playlist_view_position = int(len(default_playlist) * per)
                            console.print("DEBUG: Position set by scroll bar (right click)")
                            if pctl.playlist_view_position < 0:
                                pctl.playlist_view_position = 0

                            # if playlist_position == len(default_playlist):
                            #     print("END")

                        # elif mouse_position[1] < sbp:
                        #     pctl.playlist_view_position -= 2
                        # elif mouse_position[1] > sbp + sbl:
                        #     pctl.playlist_view_position += 2
                        elif inp.mouse_click:

                            if mouse_position[1] < sbp:
                                gui.scroll_direction = -1
                            elif mouse_position[1] > sbp + sbl:
                                gui.scroll_direction = 1
                            else:
                                # p_y = pointer(c_int(0))
                                # p_x = pointer(c_int(0))
                                # SDL_GetGlobalMouseState(p_x, p_y)
                                get_sdl_input.mouse_capture_want = True

                                scroll_hold = True
                                #scroll_point = p_y.contents.value  # mouse_position[1]
                                scroll_point = mouse_position[1]
                                scroll_bpoint = sbp
                        else:
                            #gui.update += 1
                            if sbp < mouse_position[1] < sbp + sbl:
                                gui.scroll_direction = 0
                            pctl.playlist_view_position += gui.scroll_direction * 2
                            console.print("DEBUG: Position set by scroll bar (slide)")
                            if pctl.playlist_view_position < 0:
                                pctl.playlist_view_position = 0
                            if pctl.playlist_view_position > len(default_playlist):
                                pctl.playlist_view_position = len(default_playlist)

                            if sbp + sbl > ey:
                                sbp = ey - sbl
                            elif sbp < top:
                                sbp = top

                    if not mouse_down:
                        scroll_hold = False

                    if scroll_hold and not inp.mouse_click:
                        gui.pl_update = 1
                        # p_y = pointer(c_int(0))
                        # p_x = pointer(c_int(0))
                        # SDL_GetGlobalMouseState(p_x, p_y)
                        get_sdl_input.mouse_capture_want = True

                        sbp = mouse_position[1] - (scroll_point - scroll_bpoint)
                        if sbp + sbl > ey:
                            sbp = ey - sbl
                        elif sbp < top:
                            sbp = top
                        per = (sbp - top) / (ey - top - sbl)
                        pctl.playlist_view_position = int(len(default_playlist) * per)
                        console.print("DEBUG: Position set by scroll bar (drag)")


                    else:
                        if len(default_playlist) > 0:
                            per = pctl.playlist_view_position / len(default_playlist)
                            sbp = int((ey - top - sbl) * per) + top + 1

                    bg = [255, 255, 255, 6]
                    fg = colours.scroll_colour

                    if colours.lm:
                        bg = [200, 200, 200, 100]
                        fg = [100, 100, 100, 200]

                    ddt.rect_a((x, top), (width + 1 * gui.scale, window_size[1] - top - gui.panelBY), bg)
                    ddt.rect_a((x + 1, sbp), (width, sbl), alpha_mod(fg, scroll_opacity))

                    if (coll((x + 2 * gui.scale, sbp, 20 * gui.scale, sbl)) and mouse_position[
                        0] != 0) or scroll_hold:
                        ddt.rect_a((x + 1 * gui.scale, sbp), (width, sbl), [255, 255, 255, 19])

            # NEW TOP BAR
            # C-TBR

            if gui.mode == 1:

                top_panel.render()

            # RENDER EXTRA FRAME DOUBLE
            if colours.lm:
                if gui.lsp and not gui.combo_mode and not gui.compact_artist_list:
                    ddt.rect((0 + gui.lspw - 6 * gui.scale, gui.panelY, 6 * gui.scale,
                              int(round((window_size[1] - gui.panelY - gui.panelBY)))), colours.grey(200))
                    ddt.rect((0 + gui.lspw - 5 * gui.scale, gui.panelY - 1, 4 * gui.scale,
                              int(round((window_size[1] - gui.panelY - gui.panelBY))) + 1), colours.grey(245))
                if gui.rsp and gui.show_playlist:
                    w = window_size[0] - gui.rspw
                    ddt.rect((w - round(3 * gui.scale), gui.panelY, 6 * gui.scale,
                              int(round((window_size[1] - gui.panelY - gui.panelBY)))), colours.grey(200))
                    ddt.rect((w - round(2 * gui.scale), gui.panelY - 1, 4 * gui.scale,
                              int(round((window_size[1] - gui.panelY - gui.panelBY))) + 1), colours.grey(245))
                if gui.queue_frame_draw is not None:
                    if gui.lsp:
                        ddt.rect((0, gui.queue_frame_draw, gui.lspw - 6 * gui.scale, 6 * gui.scale), colours.grey(200))
                        ddt.rect((0, gui.queue_frame_draw + 1 * gui.scale, gui.lspw - 5 * gui.scale, 4 * gui.scale),
                                 colours.grey(250))

                    gui.queue_frame_draw = None

            # BOTTOM BAR!
            # C-BB

            ddt.text_background_colour = colours.bottom_panel_colour

            if prefs.shuffle_lock:
                bottom_bar_ao1.render()
            else:
                bottom_bar1.render()

            if prefs.art_bg and not prefs.bg_showcase_only:
                style_overlay.display()
                # if key_shift_down:
                #     ddt.rect_r(gui.seek_bar_rect,
                #                alpha_mod([150, 150, 150 ,255], 20), True)
                #     ddt.rect_r(gui.volume_bar_rect,
                #                alpha_mod(colours.volume_bar_fill, 100), True)

            style_overlay.hole_punches.clear()

            if gui.set_mode:
                if rename_track_box.active is False \
                        and radiobox.active is False \
                        and gui.rename_playlist_box is False \
                        and gui.message_box is False \
                        and pref_box.enabled is False \
                        and track_box is False \
                        and not gui.rename_folder_box \
                        and not Menu.active \
                        and not artist_info_scroll.held:

                    columns_tool_tip.render()
                else:
                    columns_tool_tip.show = False


            # Overlay GUI ----------------------

            if gui.rename_playlist_box:
                rename_playlist_box.render()

            if gui.preview_artist:

                border = round(4 * gui.scale)
                ddt.rect((gui.preview_artist_location[0] - border,
                          gui.preview_artist_location[1] - border,
                          artist_preview_render.size[0] + border * 2,
                          artist_preview_render.size[0] + border * 2), (20, 20, 20, 255))

                artist_preview_render.draw(gui.preview_artist_location[0], gui.preview_artist_location[1])
                if inp.mouse_click or right_click or mouse_wheel:
                    gui.preview_artist = ""

            if track_box:
                if inp.key_return_press or right_click or key_esc_press or inp.backspace_press or keymaps.test("quick-find"):
                    track_box = False

                    inp.key_return_press = False

                if gui.level_2_click:
                    inp.mouse_click = True
                gui.level_2_click = False

                tc = pctl.master_library[r_menu_index]

                w = round(540 * gui.scale)
                h = round(240 * gui.scale)
                comment_mode = 0

                if len(tc.comment) > 0:
                    h += 22 * gui.scale
                    if window_size[0] > 599:
                        w += 25 * gui.scale
                    if ddt.get_text_w(tc.comment, 12) > 330 * gui.scale or "\n" in tc.comment:
                        h += 80 * gui.scale
                        if window_size[0] > 599:
                            w += 30 * gui.scale
                        comment_mode = 1

                x = round((window_size[0] / 2) - (w / 2))
                y = round((window_size[1] / 2) - (h / 2))

                x1 = int(x + 18 * gui.scale)
                x2 = int(x + 98 * gui.scale)

                value_font_a = 312
                value_font = 12

                # if key_shift_down:
                #     value_font = 12
                key_colour_off = colours.box_text_label #colours.grey_blend_bg(90)
                key_colour_on = colours.box_title_text
                value_colour = colours.box_sub_text
                path_colour = alpha_mod(value_colour, 240)

                # if colours.lm:
                #     key_colour_off = colours.grey(80)
                #     key_colour_on = colours.grey(120)
                #     value_colour = colours.grey(50)
                #     path_colour = colours.grey(70)

                ddt.rect_a((x - 3 * gui.scale, y - 3 * gui.scale), (w + 6 * gui.scale, h + 6 * gui.scale),
                           colours.box_border)
                ddt.rect_a((x, y), (w, h), colours.box_background)
                ddt.text_background_colour = colours.box_background

                if inp.mouse_click and not coll([x, y, w, h]):
                    track_box = False

                else:
                    art_size = int(115 * gui.scale)

                    # if not tc.is_network: # Don't draw album art if from network location for better performance
                    if comment_mode == 1:
                        album_art_gen.display(tc, (int(x + w - 135 * gui.scale), int(y + 105 * gui.scale)), (art_size, art_size)) # Mirror this size in auto theme #mark2233
                    else:
                        album_art_gen.display(tc, (int(x + w - 135 * gui.scale), int(y + h - 135 * gui.scale)), (art_size, art_size))

                    y -= int(24 * gui.scale)
                    y1 = int(y + (40 * gui.scale))

                    ext_rect = [x + w - round(38 * gui.scale), y + round(44 * gui.scale), round(38 * gui.scale), round(12 * gui.scale)]

                    line = tc.file_ext
                    ex_colour = [130, 130, 130, 255]

                    if line in format_colours:
                        ex_colour = format_colours[line]

                    # Spotify icon rendering
                    if line == "SPTY":
                        colour = [30, 215, 96, 255]
                        h, l, s = rgb_to_hls(colour[0], colour[1], colour[2])

                        rect = (x + w - round(35 * gui.scale), y + round(30 * gui.scale), round(30 * gui.scale), round(30 * gui.scale))
                        fields.add(rect)
                        if coll(rect):
                            l += 0.1
                            gui.cursor_want = 3

                            if inp.mouse_click:
                                url = tc.misc.get("spotify-album-url")
                                if url is None:
                                    url = tc.misc.get("spotify-track-url")
                                if url:
                                    webbrowser.open(url, new=2, autoraise=True)

                        colour = hls_to_rgb(h, l, s)

                        gui.spot_info_icon.render(x + w - round(33 * gui.scale), y + round(35 * gui.scale), colour)

                    # Codec tag rendering
                    else:
                        ddt.rect(ext_rect, ex_colour)
                        colour = alpha_blend([10, 10, 10, 235], ex_colour)
                        if colour_value(ex_colour) < 180:
                            colour = alpha_blend([200, 200, 200, 235], ex_colour)
                        ddt.text((int(x + w - 35 * gui.scale), round(y + 41 * gui.scale)), line, colour, 211, bg=ex_colour)

                        if tc.is_cue:
                            ext_rect[1] += 16 * gui.scale
                            colour = [218, 222, 73, 255]
                            if tc.is_embed_cue:
                                colour = [252, 199, 55, 255]
                            ddt.rect(ext_rect, colour)
                            ddt.text((int(x + w - 35 * gui.scale), int(y + (41 + 16) * gui.scale)), "CUE", alpha_blend([10, 10, 10, 235], colour), 211, bg=colour)


                    rect = [x1, y1 + int(2 * gui.scale), 450 * gui.scale, 14 * gui.scale]
                    fields.add(rect)
                    if coll(rect):
                        ddt.text((x1, y1), _("Title"), key_colour_on, 212)
                        if inp.mouse_click:
                            show_message(_("Copied text to clipboard"))
                            copy_to_clipboard(tc.title)
                            inp.mouse_click = False
                    else:
                        ddt.text((x1, y1), _("Title"), key_colour_off, 212)
                        #
                    q = ddt.text((x2, y1 - int(2 * gui.scale)), tc.title
                                 , value_colour, 314, max_w=w - 170 * gui.scale)

                    if coll(rect):
                        ex_tool_tip(x2 + 185 * gui.scale, y1, q, tc.title, 314)

                    y1 += int(16 * gui.scale)


                    rect = [x1, y1 + (2 * gui.scale), 450 * gui.scale, 14 * gui.scale]
                    fields.add(rect)
                    if coll(rect):
                        ddt.text((x1, y1), _("Artist"), key_colour_on, 212)
                        if inp.mouse_click:
                            show_message(_("Copied text to clipboard"))
                            copy_to_clipboard(tc.artist)
                            inp.mouse_click = False
                    else:
                        ddt.text((x1, y1), _("Artist"), key_colour_off, 212)

                    q = ddt.text((x2, y1 - (1 * gui.scale)), tc.artist,
                                 value_colour, value_font_a, max_w=390 * gui.scale)

                    if coll(rect):
                        ex_tool_tip(x2 + 185 * gui.scale, y1, q, tc.artist, value_font_a)

                    y1 += int(16 * gui.scale)

                    rect = [x1, y1 + (2 * gui.scale), 450 * gui.scale, 14 * gui.scale]
                    fields.add(rect)
                    if coll(rect):
                        ddt.text((x1, y1), _("Album"), key_colour_on, 212)
                        if inp.mouse_click:
                            show_message(_("Copied text to clipboard"))
                            copy_to_clipboard(tc.album)
                            inp.mouse_click = False
                    else:
                        ddt.text((x1, y1), _("Album"), key_colour_off, 212)

                    q = ddt.text((x2, y1 - 1 * gui.scale), tc.album,
                                 value_colour,
                                 value_font_a, max_w=390*gui.scale)

                    if coll(rect):
                        ex_tool_tip(x2 + 185 * gui.scale, y1, q, tc.album, value_font_a)

                    y1 += int(26 * gui.scale)

                    rect = [x1, y1, 450 * gui.scale, 16 * gui.scale]
                    fields.add(rect)
                    if coll(rect):
                        ddt.text((x1, y1), _("Path"), key_colour_on, 212)
                        if inp.mouse_click:
                            show_message(_("Copied text to clipboard"))
                            copy_to_clipboard(tc.fullpath)
                            inp.mouse_click = False
                    else:
                        ddt.text((x1, y1), _("Path"), key_colour_off, 212)

                    q = ddt.text((x2, y1 - int(3 * gui.scale)), clean_string(tc.fullpath),
                                 path_colour, 210, max_w=425*gui.scale)

                    if coll(rect):
                        gui.frame_callback_list.append(TestTimer(0.71))
                        if track_box_path_tool_timer.get() > 0.7:
                            ex_tool_tip(x2 + 185 * gui.scale, y1, q, clean_string(tc.fullpath), 210)
                    else:
                        track_box_path_tool_timer.set()

                    y1 += int(15 * gui.scale)

                    if tc.samplerate != 0:
                        ddt.text((x1, y1), _("Samplerate"), key_colour_off, 212, max_w=70*gui.scale)

                        line = str(tc.samplerate) + " Hz"


                        off = ddt.text((x2, y1), line, value_colour, value_font)

                        if tc.bit_depth > 0:
                            line = str(tc.bit_depth) + " bit"
                            ddt.text((x2 + off + 9 * gui.scale, y1), line, value_colour, 311)

                    y1 += int(15 * gui.scale)

                    if tc.bitrate not in (0, "", "0"):
                        ddt.text((x1, y1), _("Bitrate"), key_colour_off, 212, max_w=70*gui.scale)
                        line = str(tc.bitrate)
                        if tc.file_ext in ('FLAC', 'OPUS', 'APE', 'WV'):
                            line = "~" + line
                        line += " kbps"
                        ddt.text((x2, y1), line, value_colour, 312)

                    # -----------
                    if tc.artist != tc.album_artist != "":
                        x += int(170 * gui.scale)
                        rect = [x + 7 * gui.scale, y1 + (2 * gui.scale), 220 * gui.scale, 14 * gui.scale]
                        fields.add(rect)
                        if coll(rect):
                            ddt.text((x + (8 + 75) * gui.scale, y1, 1), _("Album Artist"), key_colour_on, 212)
                            if inp.mouse_click:
                                show_message(_("Copied text to clipboard"))
                                copy_to_clipboard(tc.album_artist)
                                inp.mouse_click = False
                        else:
                            ddt.text((x + (8 + 75) * gui.scale, y1, 1), _("Album Artist"), key_colour_off, 212)

                        q = ddt.text((x + (8 + 88) * gui.scale, y1), tc.album_artist,
                                     value_colour, value_font, max_w=120 * gui.scale)
                        if coll(rect):
                            ex_tool_tip(x2 + 185 * gui.scale, y1, q, tc.album_artist, value_font)

                        x -= int(170 * gui.scale)

                    y1 += int(15 * gui.scale)

                    rect = [x1, y1, 150 * gui.scale, 16 * gui.scale]
                    fields.add(rect)
                    if coll(rect):
                        ddt.text((x1, y1), _("Duration"), key_colour_on, 212)
                        if inp.mouse_click:
                            copy_to_clipboard(time.strftime('%M:%S', time.gmtime(tc.length)).lstrip("0"))
                            show_message(_("Copied text to clipboard"))
                            inp.mouse_click = False
                    else:
                        ddt.text((x1, y1), _("Duration"), key_colour_off, 212)
                    line = time.strftime('%M:%S', time.gmtime(tc.length))
                    ddt.text((x2, y1), line, value_colour, value_font)

                    # -----------
                    if tc.track_total not in ("", "0"):
                        x += int(170 * gui.scale)
                        line = str(tc.track_number) + _(" of ") + str(
                            tc.track_total)
                        ddt.text((x + (8 + 75) * gui.scale, y1, 1), _("Track"), key_colour_off, 212)
                        ddt.text((x + (8 + 88) * gui.scale, y1), line,
                                 value_colour, value_font)
                        x -= int(170 * gui.scale)

                    y1 += int(15 * gui.scale)
                    #print(tc.size)
                    if tc.size != 0:
                        ddt.text((x1, y1), _("File size"), key_colour_off, 212, max_w=70*gui.scale)
                        ddt.text((x2, y1), get_filesize_string(tc.size),
                                 value_colour, value_font)

                    # -----------
                    if tc.disc_total not in ("", "0", 0):
                        x += int(170 * gui.scale)
                        line = str(tc.disc_number) + _(" of ") + str(
                            tc.disc_total)
                        ddt.text((x + (8 + 75) * gui.scale, y1, 1), _("Disc"), key_colour_off, 212)
                        ddt.text((x + (8 + 88) * gui.scale, y1), line,
                                 value_colour, value_font)
                        x -= int(170 * gui.scale)

                    y1 += int(23 * gui.scale)

                    rect = [x1, y1 + (2 * gui.scale), 150 * gui.scale, 14 * gui.scale]
                    fields.add(rect)
                    if coll(rect):
                        ddt.text((x1, y1), _("Genre"), key_colour_on, 212)
                        if inp.mouse_click:
                            show_message(_("Copied text to clipboard"))
                            copy_to_clipboard(tc.genre)
                            inp.mouse_click = False
                    else:
                        ddt.text((x1, y1), _("Genre"), key_colour_off, 212)
                    ddt.text((x2, y1), tc.genre, value_colour,
                             value_font, max_w=290 * gui.scale)

                    y1 += int(15 * gui.scale)

                    rect = [x1, y1 + (2 * gui.scale), 150 * gui.scale, 14 * gui.scale]
                    fields.add(rect)
                    if coll(rect):
                        ddt.text((x1, y1), _("Date"), key_colour_on, 212)
                        if inp.mouse_click:
                            show_message(_("Copied text to clipboard"))
                            copy_to_clipboard(tc.date)
                            inp.mouse_click = False
                    else:
                        ddt.text((x1, y1), _("Date"), key_colour_off, 212)
                    ddt.text((x2, y1), d_date_display(tc),
                             value_colour, value_font)


                    if tc.composer and tc.composer != tc.artist:
                        x += int(170 * gui.scale)
                        rect = [x + 7 * gui.scale, y1 + (2 * gui.scale), 220 * gui.scale, 14 * gui.scale]
                        fields.add(rect)
                        if coll(rect):
                            ddt.text((x + (8 + 75) * gui.scale, y1, 1), _("Composer"), key_colour_on, 212)
                            if inp.mouse_click:
                                show_message(_("Copied text to clipboard"))
                                copy_to_clipboard(tc.album_artist)
                                inp.mouse_click = False
                        else:
                            ddt.text((x + (8 + 75) * gui.scale, y1, 1), _("Composer"), key_colour_off, 212)
                        q = ddt.text((x + (8 + 88) * gui.scale, y1), tc.composer,
                                     value_colour, value_font, max_w=120 * gui.scale)
                        if coll(rect):
                            ex_tool_tip(x2 + 185 * gui.scale, y1, q, tc.composer, value_font_a)

                        x -= int(170 * gui.scale)

                    y1 += int(23 * gui.scale)

                    total = star_store.get(r_menu_index)

                    ratio = 0

                    if total > 0 and pctl.master_library[
                        r_menu_index].length > 1:
                        ratio = total / (tc.length - 1)

                    ddt.text((x1, y1), _("Play count"), key_colour_off, 212, max_w=70*gui.scale)
                    ddt.text((x2, y1), str(int(ratio)), value_colour, value_font)

                    y1 += int(15 * gui.scale)

                    rect = [x1, y1, 150, 14]

                    if coll(rect) and key_shift_down and mouse_wheel != 0:
                        star_store.add(r_menu_index, 60 * mouse_wheel)

                    line = time.strftime('%H:%M:%S',
                                         time.gmtime(total))

                    ddt.text((x1, y1), _("Play time"), key_colour_off, 212, max_w=70*gui.scale)
                    ddt.text((x2, y1), str(line), value_colour, value_font)

                    # -------
                    if tc.lyrics != "":

                        if draw.button(_("Lyrics"), x1 + 200 * gui.scale, y1 - 10 * gui.scale):
                            prefs.show_lyrics_showcase = True
                            track_box = False
                            enter_showcase_view(track_id=r_menu_index)
                            inp.mouse_click = False

                    if len(tc.comment) > 0:
                        y1 += 20 * gui.scale
                        rect = [x1, y1 + (2 * gui.scale), 60 * gui.scale, 14 * gui.scale]
                        #ddt.rect_r((x2, y1, 335, 10), [255, 20, 20, 255])
                        fields.add(rect)
                        if coll(rect):
                            ddt.text((x1, y1), _("Comment"), key_colour_on, 212)
                            if inp.mouse_click:
                                show_message(_("Copied text to clipboard"))
                                copy_to_clipboard(tc.comment)
                                inp.mouse_click = False
                        else:
                            ddt.text((x1, y1), _("Comment"), key_colour_off, 212)
                        # ddt.draw_text((x1, y1), "Comment", key_colour_off, 12)

                        if "\n" not in tc.comment and ('http://' in tc.comment or 'www.' in tc.comment or 'https://' in tc.comment) and ddt.get_text_w(
                                tc.comment, 12) < 335 * gui.scale:

                            link_pa = draw_linked_text((x2, y1), tc.comment, value_colour, 12)
                            link_rect = [x + 98 * gui.scale + link_pa[0], y1 - 2 * gui.scale, link_pa[1], 20 * gui.scale]

                            fields.add(link_rect)
                            if coll(link_rect):
                                if not inp.mouse_click:
                                    gui.cursor_want = 3
                                if inp.mouse_click:
                                    webbrowser.open(link_pa[2], new=2, autoraise=True)
                                    track_box = True

                        elif comment_mode == 1:
                            ddt.text((x + 18 * gui.scale, y1 + 18 * gui.scale, 4, w - 36 * gui.scale, 90 * gui.scale), tc.comment, value_colour, 12)
                        else:
                            ddt.text((x2, y1), tc.comment, value_colour, 12)

            if draw_border and not gui.mode == 3:

                tool_rect = [window_size[0] - 110 * gui.scale, 2, 95 * gui.scale, 45 * gui.scale]
                if prefs.left_window_control:
                    tool_rect[0] = 0
                fields.add(tool_rect)
                if not gui.top_bar_mode2 or coll(tool_rect):
                    draw_window_tools()

                if not fullscreen and not gui.maximized:
                    draw_window_border()


            fader.render()
            if pref_box.enabled:
                # rect = [0, 0, window_size[0], window_size[1]]
                # ddt.rect_r(rect, [0, 0, 0, 90], True)
                pref_box.render()

            if gui.rename_folder_box:

                if gui.level_2_click:
                    inp.mouse_click = True

                gui.level_2_click = False

                w = 500 * gui.scale
                h = 127 * gui.scale
                x = int(window_size[0] / 2) - int(w / 2)
                y = int(window_size[1] / 2) - int(h / 2)

                ddt.rect_a((x - 2 * gui.scale, y - 2 * gui.scale), (w + 4 * gui.scale, h + 4 * gui.scale),
                           colours.box_border)
                ddt.rect_a((x, y), (w, h), colours.box_background)

                ddt.text_background_colour = colours.box_background

                if key_esc_press or ((inp.mouse_click or right_click or level_2_right_click) and not coll((x, y, w, h))):
                    gui.rename_folder_box = False

                p = ddt.text((x + 10 * gui.scale, y + 9 * gui.scale,), _("Folder Modification"), colours.box_title_text, 213)

                if rename_folder.text != prefs.rename_folder_template and draw.button(_("Default"),
                                                                                      x + (300 - 63) * gui.scale,
                                                                                      y + 11 * gui.scale,
                                                                                      70 * gui.scale):
                    rename_folder.text = prefs.rename_folder_template

                rename_folder.draw(x + 14 * gui.scale, y + 41 * gui.scale, colours.box_input_text, width=300)

                ddt.rect_s((x + 8 * gui.scale, y + 38 * gui.scale, 300 * gui.scale, 22 * gui.scale), colours.box_text_border, 1 * gui.scale)

                if draw.button(_("Rename"), x + (8 + 300 + 10) * gui.scale, y + 38 * gui.scale, 80 * gui.scale,
                               tooltip=_("Renames the physical folder based on the template")) or inp.level_2_enter:
                    rename_parent(rename_index, rename_folder.text)
                    gui.rename_folder_box = False
                    inp.mouse_click = False

                text = _("Trash")
                tt = _("Moves folder to system trash")
                if key_shift_down:
                    text = _("Delete")
                    tt = _("Physically deletes folder from disk")
                if draw.button(text, x + (8 + 300 + 10) * gui.scale, y + 11 * gui.scale, 80 * gui.scale,
                               text_highlight_colour=colours.grey(255), background_highlight_colour=[180, 60, 60, 255],
                               press=mouse_up, tooltip=tt):
                    if key_shift_down:
                        delete_folder(rename_index, True)
                    else:
                        delete_folder(rename_index)
                    gui.rename_folder_box = False
                    inp.mouse_click = False

                if move_folder_up(rename_index):
                    if draw.button(_("Raise"), x + 408 * gui.scale, y + 38 * gui.scale, 80 * gui.scale,
                                   tooltip=_("Moves folder up 2 levels and deletes the old container folder")):
                        move_folder_up(rename_index, True)
                        inp.mouse_click = False

                to_clean = clean_folder(rename_index)
                if to_clean > 0:
                    if draw.button("Clean (" + str(to_clean) + ")", x + 408 * gui.scale, y + 11 * gui.scale,
                                   80 * gui.scale, tooltip=_("Deletes some unnecessary files from folder")):
                        clean_folder(rename_index, True)
                        inp.mouse_click = False

                ddt.text((x + 10 * gui.scale, y + 65 * gui.scale,), "PATH", colours.box_text_label, 212)
                line = os.path.dirname(pctl.master_library[rename_index].parent_folder_path.rstrip("\\/")).replace("\\", "/") + "/"
                line = right_trunc(line, 12, 420 * gui.scale)
                line = clean_string(line)
                ddt.text((x + 60 * gui.scale, y + 65 * gui.scale,), line, colours.grey(220), 211)

                ddt.text((x + 10 * gui.scale, y + 83 * gui.scale), "OLD", colours.box_text_label, 212)
                line = pctl.master_library[rename_index].parent_folder_name
                line = clean_string(line)
                ddt.text((x + 60 * gui.scale, y + 83 * gui.scale), line, colours.grey(220), 211, max_w=420 * gui.scale)

                ddt.text((x + 10 * gui.scale, y + 101 * gui.scale), "NEW", colours.box_text_label, 212)
                line = parse_template2(rename_folder.text, pctl.master_library[rename_index])
                ddt.text((x + 60 * gui.scale, y + 101 * gui.scale), line, colours.grey(220), 211, max_w=420 * gui.scale)


            if rename_track_box.active:
                rename_track_box.render()

            if sub_lyrics_box.active:
                sub_lyrics_box.render()

            if export_playlist_box.active:
                export_playlist_box.render()

            if trans_edit_box.active:
                trans_edit_box.render()

            if radiobox.active:
                radiobox.render()

            if gui.message_box:
                message_box.render()
            if prefs.show_nag:
                nagbox.draw()

            # SEARCH
            # if key_ctrl_down and key_v_press:

            #     search_over.active = True

            search_over.render()

            if keymaps.test("quick-find") and quick_search_mode is False:
                if not search_over.active and not gui.box_over:
                    quick_search_mode = True
                if search_clear_timer.get() > 3:
                    search_text.text = ""
                input_text = ""
            elif (keymaps.test("quick-find") or (
                        key_esc_press and len(editline) == 0)) or inp.mouse_click and quick_search_mode is True:
                quick_search_mode = False
                search_text.text = ""

            # if (key_backslash_press or (key_ctrl_down and key_f_press)) and quick_search_mode is False:
            #     if not search_over.active:
            #         quick_search_mode = True
            #     if search_clear_timer.get() > 3:
            #         search_text.text = ""
            #     input_text = ""
            # elif ((key_backslash_press or (key_ctrl_down and key_f_press)) or (
            #             key_esc_press and len(editline) == 0)) or input.mouse_click and quick_search_mode is True:
            #     quick_search_mode = False
            #     search_text.text = ""

            if quick_search_mode is True:

                rect2 = [0, window_size[1] - 85 * gui.scale, 420 * gui.scale, 25 * gui.scale]
                rect = [0, window_size[1] - 125 * gui.scale, 420 * gui.scale, 65 * gui.scale]
                rect[0] = int(window_size[0] / 2) - int(rect[2] / 2)
                rect2[0] = rect[0]

                ddt.rect((rect[0] - 2, rect[1] - 2, rect[2] + 4, rect[3] + 4), colours.box_border)  #[220, 100, 5, 255]
                #ddt.rect_r((rect[0], rect[1], rect[2], rect[3]), [255,120,5,255], True)

                ddt.text_background_colour = colours.box_background
                #ddt.text_background_colour = [255,120,5,255]
                #ddt.text_background_colour = [220,100,5,255]
                ddt.rect(rect, colours.box_background)

                if len(input_text) > 0:
                    search_index = -1

                if inp.backspace_press and search_text.text == "":
                    quick_search_mode = False

                if len(search_text.text) == 0:
                    gui.search_error = False

                if len(search_text.text) != 0 and search_text.text[0] == '/':
                    # if "/love" in search_text.text:
                    #     line = "last.fm loved tracks from user. Format: /love <username>"
                    # else:
                    line = "Folder filter mode. Enter path segment."
                    ddt.text((rect[0] + 23 * gui.scale, window_size[1] - 87 * gui.scale), line, (220, 220, 220, 100), 312)
                else:
                    line = "UP / DOWN to navigate. SHIFT + RETURN for new playlist."
                    if len(search_text.text) == 0:
                        line = "Quick find"
                    ddt.text((rect[0] + int(rect[2] / 2), window_size[1] - 87 * gui.scale, 2), line,
                             colours.box_text_label, 312)

                    # ddt.draw_text((rect[0] + int(rect[2] / 2), window_size[1] - 118 * gui.scale, 2), "Find",
                    #           colours.grey(90), 214)

                # if len(pctl.track_queue) > 0:

                    # if input_text == 'A':
                    #     search_text.text = pctl.playing_object().artist
                    #     input_text = ""

                if gui.search_error:
                    ddt.rect([rect[0], rect[1], rect[2], 30 * gui.scale], [180, 40, 40, 255])
                    ddt.text_background_colour = [180, 40, 40, 255] #alpha_blend([255,0,0,25], ddt.text_background_colour)
                # if input.backspace_press:
                #     gui.search_error = False

                search_text.draw(rect[0] + 8 * gui.scale, rect[1] + 6 * gui.scale, colours.grey(250), font=213)

                if (key_shift_down or (len(search_text.text) > 0 and search_text.text[0] == '/')) and inp.key_return_press:
                    inp.key_return_press = False
                    playlist = []
                    if len(search_text.text) > 0:
                        if search_text.text[0] == '/':

                            if search_text.text.lower() == "/random" or search_text.text.lower() == "/shuffle":
                                gen_500_random(pctl.active_playlist_viewing)
                            elif search_text.text.lower() == "/top" or search_text.text.lower() == "/most":
                                gen_top_100(pctl.active_playlist_viewing)
                            elif search_text.text.lower() == "/length" or search_text.text.lower() == "/duration"\
                                    or search_text.text.lower() == "/len":
                                gen_sort_len(pctl.active_playlist_viewing)
                            else:

                                if search_text.text[-1] == "/":
                                    tt_title = search_text.text.replace('/', "")
                                else:
                                    search_text.text = search_text.text.replace('/', "")
                                    tt_title = search_text.text
                                search_text.text = search_text.text.lower()
                                for item in default_playlist:
                                    if search_text.text in pctl.master_library[item].parent_folder_path.lower():
                                        playlist.append(item)
                                if len(playlist) > 0:

                                    pctl.multi_playlist.append(pl_gen(title=tt_title,
                                                                      playlist=copy.deepcopy(playlist)))
                                    switch_playlist(len(pctl.multi_playlist) - 1)

                        else:
                            search_terms = search_text.text.lower().split()
                            for item in default_playlist:
                                tr = pctl.g(item)
                                line = " ".join([tr.title, tr.artist, tr.album, tr.fullpath, tr.composer, tr.comment, tr.album_artist]).lower()

                                # if prefs.diacritic_search and all([ord(c) < 128 for c in search_text.text]):
                                #     line = str(unidecode(line))

                                if all(word in line for word in search_terms):
                                    playlist.append(item)
                            if len(playlist) > 0:
                                pctl.multi_playlist.append(pl_gen(title="Search Results",
                                                                  playlist=copy.deepcopy(playlist)))
                                pctl.gen_codes[pl_to_id(len(pctl.multi_playlist) - 1)] = "s\"" + pctl.multi_playlist[pctl.active_playlist_viewing][0] + "\" f\"" + search_text.text + "\""
                                switch_playlist(len(pctl.multi_playlist) - 1)
                        search_text.text = ""
                        quick_search_mode = False

                if (len(input_text) > 0 and not gui.search_error) or key_down_press is True or inp.backspace_press\
                        or gui.force_search:

                    gui.pl_update = 1

                    if gui.force_search:
                        search_index = 0

                    if inp.backspace_press:
                        search_index = 0

                    if len(search_text.text) > 0 and search_text.text[0] != "/":
                        oi = search_index

                        while search_index < len(default_playlist) - 1:
                            search_index += 1
                            if search_index > len(default_playlist) - 1:
                                search_index = 0

                            search_terms = search_text.text.lower().split()
                            tr = pctl.g(default_playlist[search_index])
                            line = " ".join([tr.title, tr.artist, tr.album, tr.fullpath, tr.composer, tr.comment, tr.album_artist]).lower()

                            # if prefs.diacritic_search and all([ord(c) < 128 for c in search_text.text]):
                            #     line = str(unidecode(line))

                            if all(word in line for word in search_terms):

                                playlist_selected = search_index
                                if len(default_playlist) > 10 and search_index > 10:
                                    pctl.playlist_view_position = search_index - 7
                                    console.print("DEBUG: Position changed by search")
                                else:
                                    pctl.playlist_view_position = 0

                                if gui.combo_mode:
                                    pctl.show_selected()
                                gui.search_error = False

                                break

                        else:
                            search_index = oi
                            if len(input_text) > 0 or gui.force_search:
                                gui.search_error = True
                            if key_down_press:
                                bottom_playlist2.pulse()

                        gui.force_search = False

                if key_up_press is True \
                        and not key_shiftr_down \
                        and not key_shift_down \
                        and not key_ctrl_down\
                        and not key_rctrl_down\
                        and not key_meta\
                        and not key_lalt\
                        and not key_ralt:

                    gui.pl_update = 1
                    oi = search_index

                    while search_index > 1:
                        search_index -= 1
                        if search_index > len(default_playlist) - 1:
                            search_index = len(default_playlist) - 1
                        search_terms = search_text.text.lower().split()
                        line = pctl.master_library[default_playlist[search_index]].title.lower() + \
                            pctl.master_library[default_playlist[search_index]].artist.lower() \
                            + pctl.master_library[default_playlist[search_index]].album.lower() + \
                            pctl.master_library[default_playlist[search_index]].filename.lower()

                        if prefs.diacritic_search and all([ord(c) < 128 for c in search_text.text]):
                            line = str(unidecode(line))

                        if all(word in line for word in search_terms):

                            playlist_selected = search_index
                            if len(default_playlist) > 10 and search_index > 10:
                                pctl.playlist_view_position = search_index - 7
                                console.print("DEBUG: Position changed by search")
                            else:
                                pctl.playlist_view_position = 0
                            if gui.combo_mode:
                                pctl.show_selected()
                            break
                    else:
                        search_index = oi

                        edge_playlist2.pulse()

                if inp.key_return_press is True and search_index > -1:
                    gui.pl_update = 1
                    pctl.jump(default_playlist[search_index], search_index)
                    if album_mode:
                        goto_album(pctl.playlist_playing_position)
                    quick_search_mode = False
                    search_clear_timer.set()

            elif not search_over.active:

                if key_up_press and ((
                        not key_shiftr_down \
                        and not key_shift_down \
                        and not key_ctrl_down\
                        and not key_rctrl_down\
                        and not key_meta\
                        and not key_lalt\
                        and not key_ralt) or (keymaps.test("shift-up"))):


                    pctl.show_selected()
                    gui.pl_update = 1

                    if not keymaps.test("shift-up"):
                        if playlist_selected > 0:
                            playlist_selected -= 1
                        shift_selection = []

                    if pctl.playlist_view_position > 0 and playlist_selected < pctl.playlist_view_position + 2:
                        pctl.playlist_view_position -= 1
                        console.print("DEBUG: Position changed by key up")

                        scroll_hide_timer.set()
                        gui.frame_callback_list.append(TestTimer(0.9))

                    if playlist_selected > len(default_playlist):
                        playlist_selected = len(default_playlist)


                if playlist_selected < len(default_playlist) and ((key_down_press and \
                        not key_shiftr_down \
                        and not key_shift_down \
                        and not key_ctrl_down\
                        and not key_rctrl_down\
                        and not key_meta\
                        and not key_lalt\
                        and not key_ralt) or keymaps.test("shift-down")):


                    pctl.show_selected()
                    gui.pl_update = 1

                    if not keymaps.test("shift-down"):
                        if playlist_selected < len(default_playlist) - 1:
                            playlist_selected += 1
                        shift_selection = []

                    if pctl.playlist_view_position < len(
                            default_playlist) and playlist_selected > pctl.playlist_view_position + gui.playlist_view_length - 3 - gui.row_extra:
                        pctl.playlist_view_position += 1
                        console.print("DEBUG: Position changed by key down")

                        scroll_hide_timer.set()
                        gui.frame_callback_list.append(TestTimer(0.9))

                    if playlist_selected < 0:
                        playlist_selected = 0

                if inp.key_return_press and not pref_box.enabled and not radiobox.active and not trans_edit_box.active:
                    gui.pl_update = 1
                    if playlist_selected > len(default_playlist) - 1:
                        playlist_selected = 0
                        shift_selection = []
                    if default_playlist:
                        pctl.jump(default_playlist[playlist_selected], playlist_selected)
                        if album_mode:
                            goto_album(pctl.playlist_playing_position)


        elif gui.mode == 3:

            if (key_shift_down and inp.mouse_click) or middle_click:
                if prefs.mini_mode_mode == 4:
                    prefs.mini_mode_mode = 1
                    window_size[0] = int(330 * gui.scale)
                    window_size[1] = int(330 * gui.scale)
                    SDL_SetWindowMinimumSize(t_window, window_size[0], window_size[1])
                    SDL_SetWindowSize(t_window, window_size[0], window_size[1])
                else:
                    prefs.mini_mode_mode = 4
                    window_size[0] = int(320 * gui.scale)
                    window_size[1] = int(90 * gui.scale)
                    SDL_SetWindowMinimumSize(t_window, window_size[0], window_size[1])
                    SDL_SetWindowSize(t_window, window_size[0], window_size[1])

            if prefs.mini_mode_mode == 4:
                mini_mode2.render()
            else:
                mini_mode.render()

        t = toast_love_timer.get()
        if t < 1.8 and gui.toast_love_object is not None:
            track = gui.toast_love_object

            ww = 0
            if gui.lsp:
                ww = gui.lspw

            rect = (ww + 5 * gui.scale, gui.panelY + 5 * gui.scale, 235 * gui.scale, 39 * gui.scale)
            fields.add(rect)

            if coll(rect):
                toast_love_timer.force_set(10)
            else:
                ddt.rect(grow_rect(rect, 2 * gui.scale), colours.box_border)
                ddt.rect(rect, colours.queue_card_background)

                # fqo = copy.copy(pctl.force_queue[-1])

                ddt.text_background_colour = colours.queue_card_background

                if gui.toast_love_added:
                    text = _("Loved track")
                    heart_notify_icon.render(rect[0] + 9 * gui.scale, rect[1] + 8 * gui.scale, [250, 100, 100, 255])
                else:
                    text = _("Un-Loved track")
                    heart_notify_break_icon.render(rect[0] + 9 * gui.scale, rect[1] + 7 * gui.scale, [150, 150, 150, 255])



                ddt.text_background_colour = colours.queue_card_background
                ddt.text((rect[0] + 42 * gui.scale, rect[1] + 3 * gui.scale), text, colours.box_text, 313)
                ddt.text((rect[0] + 42 * gui.scale, rect[1] + 20 * gui.scale), f"{track.track_number}. {track.artist} - {track.title}".strip(".- "), colours.box_text_label, 13, max_w=rect[2] - 50 * gui.scale)


        t = queue_add_timer.get()
        if t < 2.5 and gui.toast_queue_object:
            track = pctl.g(gui.toast_queue_object[0])

            ww = 0
            if gui.lsp:
                ww = gui.lspw
            if search_over.active:
                ww = window_size[0] // 2 - (215 * gui.scale // 2)

            rect = (ww + 5 * gui.scale, gui.panelY + 5 * gui.scale, 215 * gui.scale, 39 * gui.scale)
            fields.add(rect)

            if coll(rect):
                queue_add_timer.force_set(10)
            elif len(pctl.force_queue) > 0:

                fqo = copy.copy(pctl.force_queue[-1])

                ddt.rect(grow_rect(rect, 2 * gui.scale), colours.box_border)
                ddt.rect(rect, colours.queue_card_background)

                ddt.text_background_colour = colours.queue_card_background
                top_text = "Track"
                if gui.queue_toast_plural:
                    top_text = "Album"
                    fqo[3] = 1
                if pctl.force_queue[-1][3] == 1:
                    top_text = "Album"

                queue_box.draw_card(rect[0] - 8 * gui.scale, 0, 160 * gui.scale, 210 * gui.scale, rect[1] + 1 * gui.scale, track, fqo, True, False)

                ddt.text_background_colour = colours.queue_card_background
                ddt.text((rect[0] + rect[2] - 50 * gui.scale, rect[1] + 3 * gui.scale, 2), f"{top_text} added", colours.box_text_label, 11)
                ddt.text((rect[0] + rect[2] - 50 * gui.scale, rect[1] + 15 * gui.scale, 2), "to queue", colours.box_text_label, 11)


        t = toast_mode_timer.get()
        if t < 0.98:

            wid = ddt.get_text_w(gui.mode_toast_text, 313)
            wid = max(round(68 * gui.scale), wid)

            ww = round(7 * gui.scale)
            if gui.lsp and not gui.combo_mode:
                ww += gui.lspw

            rect = (ww + 8 * gui.scale, gui.panelY + 15 * gui.scale, wid + 20 * gui.scale, 25 * gui.scale)
            fields.add(rect)

            if coll(rect):
                toast_mode_timer.force_set(10)
            else:
                ddt.rect(grow_rect(rect, round(2 * gui.scale)), colours.grey(60))
                ddt.rect(rect, colours.queue_card_background)

                ddt.text_background_colour = colours.queue_card_background
                ddt.text((rect[0] + (rect[2] // 2), rect[1] + 4 * gui.scale, 2), gui.mode_toast_text, colours.grey(230), 313)

        # Render Menus-------------------------------
        for instance in Menu.instances:
            instance.render()

        if view_box.active:
            view_box.render()


        tool_tip.render()
        tool_tip2.render()



        if console.show:
            rect = (20 * gui.scale, 40 * gui.scale, 475 * gui.scale, 170 * gui.scale)
            ddt.rect(rect, [0, 0, 0, 245])

            yy = rect[3] + 15 * gui.scale
            u = False
            for item in reversed(console.messages):
                message = item[0]
                if yy < rect[1] + 5 * gui.scale:
                    break

                fade = 255
                if item[3].get() > 2:
                    fade = 200
                else:
                    u = True

                text_colour = [60, 255, 80, fade]
                if item[1] == 5:
                    text_colour = [255, 40, 90, fade]

                w = ddt.text((rect[0] + 10 * gui.scale, yy), item[2].strftime('%H:%M:%S'), [255, 80, 160, fade], 311, rect[2] - 60 * gui.scale)
                ddt.text((w + rect[0] + 17 * gui.scale, yy), message, text_colour, 311, rect[2] - 60 * gui.scale)
                yy -= 14 * gui.scale
            if u:
                gui.delay_frame(5)

            if draw.button("Copy", rect[0] + rect[2] - 55 * gui.scale, rect[1] + rect[3] - 30 * gui.scale):

                text = ""
                for item in console.messages[-50:]:
                    text += item[2].strftime('%H:%M:%S') + " " + item[0] + "\n"
                copy_to_clipboard(text)
                show_message("Lines copied to clipboard", mode="done")

        if gui.cursor_is != gui.cursor_want:

            gui.cursor_is = gui.cursor_want

            if gui.cursor_is == 0:
                SDL_SetCursor(cursor_standard)
            elif gui.cursor_is == 1:
                SDL_SetCursor(cursor_shift)
            elif gui.cursor_is == 2:
                SDL_SetCursor(cursor_text)
            elif gui.cursor_is == 3:
                SDL_SetCursor(cursor_hand)
            elif gui.cursor_is == 4:
                SDL_SetCursor(cursor_br_corner)
            elif gui.cursor_is == 8:
                SDL_SetCursor(cursor_right_side)
            elif gui.cursor_is == 9:
                SDL_SetCursor(cursor_top_side)
            elif gui.cursor_is == 10:
                SDL_SetCursor(cursor_left_side)
            elif gui.cursor_is == 11:
                SDL_SetCursor(cursor_bottom_side)

        get_sdl_input.test_capture_mouse()
        get_sdl_input.mouse_capture_want = False



        # # Quick view
        # quick_view_box.render()

        # Drag icon next to cursor
        if quick_drag and mouse_down and not point_proximity_test(gui.drag_source_position, mouse_position, 15 * gui.scale):
            i_x, i_y = get_sdl_input.mouse()
            gui.drag_source_position = (0, 0)

            block_size = round(10 * gui.scale)
            x_offset = round(20 * gui.scale)
            y_offset = round(1 * gui.scale)

            if len(shift_selection) == 1:  # Single track
                ddt.rect((i_x + x_offset, i_y + y_offset, block_size, block_size), [160, 140, 235, 240])
            else:
                if key_ctrl_down:  # Add to queue undrouped
                    small_block = round(6 * gui.scale)
                    spacing = round(2 * gui.scale)
                    ddt.rect((i_x + x_offset, i_y + y_offset, small_block, small_block), [160, 140, 235, 240])
                    ddt.rect((i_x + x_offset + spacing + small_block, i_y + y_offset, small_block, small_block),
                             [160, 140, 235, 240])
                    ddt.rect((i_x + x_offset, i_y + y_offset + spacing + small_block, small_block, small_block),
                             [160, 140, 235, 240])
                    ddt.rect((i_x + x_offset + spacing + small_block, i_y + y_offset + spacing + small_block,
                              small_block, small_block), [160, 140, 235, 240])
                    ddt.rect((i_x + x_offset, i_y + y_offset + spacing + small_block + spacing + small_block,
                              small_block, small_block), [160, 140, 235, 240])
                    ddt.rect((i_x + x_offset + spacing + small_block,
                              i_y + y_offset + spacing + small_block + spacing + small_block,
                              small_block, small_block), [160, 140, 235, 240])

                else:  # Multiple tracks
                    long_block = round(25 * gui.scale)
                    ddt.rect((i_x + x_offset, i_y + y_offset, block_size, long_block), [160, 140, 235, 240])

            #gui.update += 1
            gui.update_on_drag = True

        # Drag pl tab next to cursor
        if (playlist_box.drag) and mouse_down and not point_proximity_test(gui.drag_source_position, mouse_position, 10 * gui.scale):
            i_x, i_y = get_sdl_input.mouse()
            gui.drag_source_position = (0, 0)
            ddt.rect((i_x + 20 * gui.scale, i_y + 3 * gui.scale, int(50 * gui.scale), int(15 * gui.scale)),
                     [50, 50, 50, 225])
            #ddt.rect_r((i_x + 20 * gui.scale, i_y + 1 * gui.scale, int(60 * gui.scale), int(15 * gui.scale)), [240, 240, 240, 255], True)
            #ddt.draw_text((i_x + 75 * gui.scale, i_y - 0 * gui.scale, 1), pctl.multi_playlist[playlist_box.drag_on][0], [30, 30, 30, 255], 212, bg=[240, 240, 240, 255])
        if radio_view.drag and not point_proximity_test(radio_view.click_point, mouse_position, round(4 * gui.scale)):
            ddt.rect((
                mouse_position[0] + round(8 * gui.scale), mouse_position[1] - round(8 * gui.scale), 48 * gui.scale,
                14 * gui.scale), colours.grey(70))
        if (gui.set_label_hold != -1) and mouse_down:

            gui.update_on_drag = True

            if not point_proximity_test(gui.set_label_point, mouse_position, 3):
                i_x, i_y = get_sdl_input.mouse()
                gui.set_label_point = (0, 0)

                w = ddt.get_text_w(gui.pl_st[gui.set_label_hold][0], 212)
                w = max(w, 45 * gui.scale)
                ddt.rect((i_x + 25 * gui.scale, i_y + 1 * gui.scale, w + int(20 * gui.scale), int(15 * gui.scale)),
                         [240, 240, 240, 255])
                ddt.text((i_x + 25 * gui.scale + w + int(20 * gui.scale) - 4 * gui.scale, i_y - 0 * gui.scale, 1), gui.pl_st[gui.set_label_hold][0], [30, 30, 30, 255], 212, bg=[240, 240, 240, 255])


        input_text = ""
        gui.update -= 1

        #print("FRAME " + str(core_timer.get()))
        if gui.update > 1:
            gui.update = 1
        gui.present = True

        SDL_SetRenderTarget(renderer, None)
        SDL_RenderCopy(renderer, gui.main_texture, None, gui.tracklist_texture_rect)

        if gui.turbo:
            gui.level_update = True


    # if gui.vis == 1 and pctl.playing_state != 1 and gui.level_peak != [0, 0] and gui.turbo:
    #
    #     # print(gui.level_peak)
    #     gui.time_passed = gui.level_time.hit()
    #     if gui.time_passed > 1:
    #         gui.time_passed = 0
    #     while gui.time_passed > 0.01:
    #         gui.level_peak[1] -= 0.5
    #         if gui.level_peak[1] < 0:
    #             gui.level_peak[1] = 0
    #         gui.level_peak[0] -= 0.5
    #         if gui.level_peak[0] < 0:
    #             gui.level_peak[0] = 0
    #         gui.time_passed -= 0.020
    #
    #     gui.level_update = True

    if gui.level_update is True and not resize_mode and not gui.mode == 3:
        gui.level_update = False

        SDL_SetRenderTarget(renderer, None)
        if not gui.present:

            SDL_RenderCopy(renderer, gui.main_texture, None, gui.tracklist_texture_rect)
            gui.present = True

        if gui.vis == 3:
            # Scrolling spectrogram

            # if not vis_update:
            #     print("No UPDATE " + str(random.randint(1,50)))
            if len(gui.spec2_buffers) > 0 and gui.spec2_timer.get() > 0.04:
                #gui.spec2_timer.force_set(gui.spec2_timer.get() - 0.04)
                gui.spec2_timer.set()
                vis_update = True


            if len(gui.spec2_buffers) > 0 and vis_update:
                vis_update = False

                SDL_SetRenderTarget(renderer, gui.spec2_tex)
                for i, value in enumerate(gui.spec2_buffers[0]):
                    ddt.rect([gui.spec2_position, i, 1, 1],
                             [min(255, prefs.spec2_base[0] + int(value * prefs.spec2_multiply[0])),
                              min(255, prefs.spec2_base[1] + int(value * prefs.spec2_multiply[1])),
                              min(255, prefs.spec2_base[2] + int(value * prefs.spec2_multiply[2])),
                              255])

                del gui.spec2_buffers[0]


                gui.spec2_position += 1

                if gui.spec2_position > gui.spec2_w - 1:
                    gui.spec2_position = 0


                SDL_SetRenderTarget(renderer, None)


            #
            # else:
            #     print("animation stall" + str(random.randint(1, 10)))

            if prefs.spec2_scroll:

                gui.spec2_source.x = 0
                gui.spec2_source.y = 0
                gui.spec2_source.w = gui.spec2_position
                gui.spec2_dest.x = gui.spec2_rec.x + gui.spec2_rec.w - gui.spec2_position
                gui.spec2_dest.w = gui.spec2_position
                SDL_RenderCopy(renderer, gui.spec2_tex, gui.spec2_source, gui.spec2_dest)

                gui.spec2_source.x = gui.spec2_position
                gui.spec2_source.y = 0
                gui.spec2_source.w = gui.spec2_rec.w - gui.spec2_position
                gui.spec2_dest.x = gui.spec2_rec.x
                gui.spec2_dest.w = gui.spec2_rec.w - gui.spec2_position
                SDL_RenderCopy(renderer, gui.spec2_tex, gui.spec2_source, gui.spec2_dest)

            else:

                SDL_RenderCopy(renderer, gui.spec2_tex, None, gui.spec2_rec)

            if pref_box.enabled:
                ddt.rect((gui.spec2_rec.x, gui.spec2_rec.y, gui.spec2_rec.w, gui.spec2_rec.h), [0, 0, 0, 90])

        if gui.vis == 4 and gui.draw_vis4_top:

            showcase.render_vis(True)
            # gui.level_update = False


        if gui.vis == 2 and gui.spec is not None:

            # Standard spectrum visualiser

            if gui.update_spec == 0 and pctl.playing_state != 2:
                if vis_decay_timer.get() > 0.007:  # Controls speed of decay after stop
                    vis_decay_timer.set()
                    for i in range(len(gui.spec)):
                        if gui.s_spec[i] > 0:
                            if gui.spec[i] > 0:
                                gui.spec[i] -= 1
                            gui.level_update = True
                else:
                    gui.level_update = True

            if vis_rate_timer.get() > 0.027:  # Limit the change rate #to 60 fps
                vis_rate_timer.set()

                if spec_smoothing and pctl.playing_state > 0:

                    for i in range(len(gui.spec)):
                        if gui.spec[i] > gui.s_spec[i]:
                            gui.s_spec[i] += 1
                            if abs(gui.spec[i] - gui.s_spec[i]) > 4:
                                gui.s_spec[i] += 1
                            if abs(gui.spec[i] - gui.s_spec[i]) > 6:
                                gui.s_spec[i] += 1
                            if abs(gui.spec[i] - gui.s_spec[i]) > 8:
                                gui.s_spec[i] += 1

                        elif gui.spec[i] == gui.s_spec[i]:
                            pass
                        elif gui.spec[i] < gui.s_spec[i] > 0:
                            gui.s_spec[i] -= 1
                            if abs(gui.spec[i] - gui.s_spec[i]) > 4:
                                gui.s_spec[i] -= 1
                            if abs(gui.spec[i] - gui.s_spec[i]) > 6:
                                gui.s_spec[i] -= 1
                            if abs(gui.spec[i] - gui.s_spec[i]) > 8:
                                gui.s_spec[i] -= 1

                    if pctl.playing_state == 0 and check_equal(gui.s_spec):
                        gui.level_update = True
                        time.sleep(0.008)
                else:
                    gui.s_spec = gui.spec
            else:
                pass

            if not gui.test:

                SDL_SetRenderTarget(renderer, gui.spec1_tex)

                # ddt.rect_r(gui.spec_rect, colours.top_panel_background, True)
                ddt.rect((0, 0, gui.spec_w, gui.spec_h), colours.vis_bg)

                # xx = 0
                gui.bar.x = 0
                on = 0

                SDL_SetRenderDrawColor(renderer, colours.vis_colour[0],
                                       colours.vis_colour[1], colours.vis_colour[2],
                                       colours.vis_colour[3])

                for item in gui.s_spec:

                    if on > 19:
                        break
                    on += 1

                    item -= 1

                    if item < 1:
                        gui.bar.x += round(4 * gui.scale)
                        continue

                    if item > 20:
                        item = 20

                    if gui.scale >= 2:
                        item = round(item * gui.scale)

                    gui.bar.y = 0 + gui.spec_h - item
                    gui.bar.h = item

                    SDL_RenderFillRect(renderer, gui.bar)

                    gui.bar.x += round(4 * gui.scale)

                if pref_box.enabled:
                    ddt.rect((0, 0, gui.spec_w, gui.spec_h), [0, 0, 0, 90])

                SDL_SetRenderTarget(renderer, None)
                SDL_RenderCopy(renderer, gui.spec1_tex, None, gui.spec1_rec)

        if gui.vis == 1:

            if prefs.backend == 2 or True:
                if pctl.playing_state == 1 or pctl.playing_state == 3:
                    #gui.level_update = True
                    while tauon.level_train and tauon.level_train[0][0] < time.time():

                        l = tauon.level_train[0][1]
                        r = tauon.level_train[0][2]

                        if r > gui.level_peak[0]:
                            gui.level_peak[0] = r
                        if l > gui.level_peak[1]:
                            gui.level_peak[1] = l

                        del tauon.level_train[0]

                else:
                    tauon.level_train.clear()

            SDL_SetRenderTarget(renderer, gui.spec_level_tex)

            x = window_size[0] - 20 * gui.scale - gui.offset_extra
            y = gui.level_y
            w = gui.level_w
            s = gui.level_s

            y = 0

            gui.spec_level_rec.x = round(x - 70 * gui.scale)
            ddt.rect_a((0, 0), (79 * gui.scale, 18 * gui.scale), colours.grey(10))

            x = round(gui.level_ww - 9 * gui.scale)
            y = 10 * gui.scale

            if prefs.backend == 2 or True:
                if (gui.level_peak[0] > 0 or gui.level_peak[1] > 0):
                    #gui.level_update = True
                    if pctl.playing_time < 1:
                        gui.delay_frame(0.032)

                    if pctl.playing_state == 1 or pctl.playing_state == 3:
                        t = gui.level_decay_timer.hit()
                        decay = 14 * t
                        gui.level_peak[1] -= decay
                        gui.level_peak[0] -= decay
                    elif pctl.playing_state == 0 or pctl.playing_state == 2:
                        gui.level_update = True
                        time.sleep(0.016)
                        t = gui.level_decay_timer.hit()
                        decay = 16 * t
                        gui.level_peak[1] -= decay
                        gui.level_peak[0] -= decay


            for t in range(12):

                if gui.level_peak[0] < t:
                    met = False
                else:
                    met = True
                if gui.level_peak[0] < 0.2:
                    met = False

                if gui.level_meter_colour_mode == 1:

                    if not met:
                        cc = [15, 10, 20, 255]
                    else:
                        cc = colorsys.hls_to_rgb(0.68 + (t * 0.015), 0.4, 0.7)
                        cc = (int(cc[0] * 255), int(cc[1] * 255), int(cc[2] * 255), 255)

                elif gui.level_meter_colour_mode == 2:

                    if not met:
                        cc = [11, 11, 13, 255]
                    else:
                        cc = colorsys.hls_to_rgb(0.63 - (t * 0.015), 0.4, 0.7)
                        cc = (int(cc[0] * 255), int(cc[1] * 255), int(cc[2] * 255), 255)

                elif gui.level_meter_colour_mode == 3:

                    if not met:
                        cc = [12, 6, 0, 255]
                    else:
                        cc = colorsys.hls_to_rgb(0.11 - (t * 0.010), 0.4, 0.7 + (t * 0.02))
                        cc = (int(cc[0] * 255), int(cc[1] * 255), int(cc[2] * 255), 255)

                elif gui.level_meter_colour_mode == 4:

                    if not met:
                        cc = [10, 10, 10, 255]
                    else:
                        cc = colorsys.hls_to_rgb(0.3 - (t * 0.03), 0.4, 0.7 + (t * 0.02))
                        cc = (int(cc[0] * 255), int(cc[1] * 255), int(cc[2] * 255), 255)

                else:

                    if t < 7:
                        cc = colours.level_green
                        if met is False:
                            cc = colours.level_1_bg
                    elif t < 10:
                        cc = colours.level_yellow
                        if met is False:
                            cc = colours.level_2_bg
                    else:
                        cc = colours.level_red
                        if met is False:
                            cc = colours.level_3_bg
                if gui.level > 0 and pctl.playing_state > 0:
                    pass
                ddt.rect_a(((x - (w * t) - (s * t)), y), (w, w), cc)

            y -= 7 * gui.scale
            for t in range(12):

                if gui.level_peak[1] < t:
                    met = False
                else:
                    met = True
                if gui.level_peak[1] < 0.2:
                    met = False

                if gui.level_meter_colour_mode == 1:

                    if not met:
                        cc = [15, 10, 20, 255]
                    else:
                        cc = colorsys.hls_to_rgb(0.68 + (t * 0.015), 0.4, 0.7)
                        cc = (int(cc[0] * 255), int(cc[1] * 255), int(cc[2] * 255), 255)

                elif gui.level_meter_colour_mode == 2:

                    if not met:
                        cc = [11, 11, 13, 255]
                    else:
                        cc = colorsys.hls_to_rgb(0.63 - (t * 0.015), 0.4, 0.7)
                        cc = (int(cc[0] * 255), int(cc[1] * 255), int(cc[2] * 255), 255)

                elif gui.level_meter_colour_mode == 3:

                    if not met:
                        cc = [12, 6, 0, 255]
                    else:
                        cc = colorsys.hls_to_rgb(0.11 - (t * 0.010), 0.4, 0.7 + (t * 0.02))
                        cc = (int(cc[0] * 255), int(cc[1] * 255), int(cc[2] * 255), 255)

                elif gui.level_meter_colour_mode == 4:

                    if not met:
                        cc = [10, 10, 10, 255]
                    else:
                        cc = colorsys.hls_to_rgb(0.3 - (t * 0.03), 0.4, 0.7 + (t * 0.02))
                        cc = (int(cc[0] * 255), int(cc[1] * 255), int(cc[2] * 255), 255)

                else:

                    if t < 7:
                        cc = colours.level_green
                        if met is False:
                            cc = colours.level_1_bg
                    elif t < 10:
                        cc = colours.level_yellow
                        if met is False:
                            cc = colours.level_2_bg
                    else:
                        cc = colours.level_red
                        if met is False:
                            cc = colours.level_3_bg

                if gui.level > 0 and pctl.playing_state > 0:
                    pass
                ddt.rect_a(((x - (w * t) - (s * t)), y), (w, w), cc)

            SDL_SetRenderTarget(renderer, None)
            SDL_RenderCopy(renderer, gui.spec_level_tex, None, gui.spec_level_rec)


    if gui.present:
        SDL_SetRenderTarget(renderer, None)
        SDL_RenderPresent(renderer)

        gui.present = False

    # -------------------------------------------------------------------------------------------
    # Misc things to update every tick

    # # Broadcast control
    if pctl.broadcast_active and pctl.broadcast_time > pctl.master_library[
            pctl.broadcast_index].length:
        pctl.advance_broadcast()
    #
    # # elif pctl.join_broadcast and pctl.broadcast_active:
    # #     pctl.broadcast_index = pctl.track_queue[pctl.queue_step]
    # #     pctl.broadcast_time = pctl.playing_time
    #
    if pctl.broadcast_active and round(pctl.broadcast_time) != pctl.broadcast_last_time:
        pctl.broadcast_last_time = round(pctl.broadcast_time)

        pctl.broadcast_update_train.append((pctl.broadcast_index, pctl.broadcast_time, time.time()))
        if len(pctl.broadcast_update_train) > 10:
            del pctl.broadcast_update_train[0]

        for id, value in tauon.chunker.clients.items():
            if time.time() - value[1] > 5:
                del tauon.chunker.clients[id]
                break

        gui.update += 1

    # if pctl.broadcast_active and pctl.broadcast_time == 0:
    #     gui.pl_update = 1

    # Update d-bus metadata on Linux
    if (pctl.playing_state == 1 or pctl.playing_state == 3) and pctl.mpris is not None:
        pctl.mpris.update_progress()

    # GUI time ticker update
    if (pctl.playing_state == 1 or pctl.playing_state == 3) and gui.lowered is False:
        if int(pctl.playing_time) != int(pctl.last_playing_time):
            pctl.last_playing_time = pctl.playing_time
            bottom_bar1.seek_time = pctl.playing_time
            if not prefs.power_save or window_is_focused():
                gui.update = 1

    # Auto save play times to disk
    if pctl.total_playtime - time_last_save > 600:
        print("Auto save playtime")
        try:
            pickle.dump(star_store.db, open(user_directory + "/star.p", "wb"))
        except PermissionError:
            show_message("Permission error encountered while writing database", "error")
        time_last_save = pctl.total_playtime

    # Always render at least one frame per minute (to avoid SDL bugs I guess)
    if min_render_timer.get() > 60:
        min_render_timer.set()
        gui.pl_update = 1
        gui.update += 1

    # Save power if the window is minimized
    if gui.lowered:
        time.sleep(0.2)

if spot_ctl.playing:
    spot_ctl.control("stop")

# Send scrobble if pending
if lfm_scrobbler.queue and not lfm_scrobbler.running:
    lfm_scrobbler.start_queue()
    print("Sending scrobble before close...")

if gui.mode < 3:
    old_window_position = get_window_position()

SDL_DestroyWindow(t_window)

pctl.playerCommand = "unload"
pctl.playerCommandReady = True
pctl.broadcast_active = False

if prefs.reload_play_state and pctl.playing_state in (1, 2):
    print("Saving play state...")
    prefs.reload_state = (pctl.playing_state, pctl.playing_time)

pickle.dump(star_store.db, open(user_directory + "/star.p", "wb"))
pickle.dump(album_star_store.db, open(user_directory + "/album-star.p", "wb"))

gui.gallery_positions[pl_to_id(pctl.active_playlist_viewing)] = gui.album_scroll_px
save_state()

date = datetime.date.today()
pickle.dump(star_store.db, open(user_directory + "/star.p.backup", "wb"))
pickle.dump(star_store.db, open(user_directory + "/star.p.backup" + str(date.month), "wb"))

if tauon.stream_proxy.download_running:
    print("Stopping stream...")
    tauon.stream_proxy.stop()
    time.sleep(2)

try:
    tm.player_lock.release()
except:
    pass

try:
    tauon.radio_server.server_close()
except:
    pass

if system == "windows" or msys:
    tray.stop()
else:
    if de_notify_support:
        try:
            song_notification.close()
            g_tc_notify.close()
            Notify.uninit()
        except:
            print("uninit notification error")

if macos:
    try:
        tap.stop()
    except:
        pass

try:
    fp.close()
except:
    print("No lock object to close")

SDL_DestroyTexture(gui.main_texture)
SDL_DestroyTexture(gui.tracklist_texture)
SDL_DestroyTexture(gui.spec2_tex)
SDL_DestroyTexture(gui.spec1_tex)
SDL_DestroyTexture(gui.spec_level_tex)
ddt.clear_text_cache()
clear_img_cache(False)

IMG_Quit()
SDL_QuitSubSystem(SDL_INIT_EVERYTHING)
SDL_Quit()
#print("SDL unloaded")

exit_timer = Timer()
exit_timer.set()

if not tauon.quick_close:
    while tm.check_playback_running() or lfm_scrobbler.running:
        time.sleep(0.2)
        lfm_scrobbler.running = False
        if exit_timer.get() > 11:
            print("Unload timeout")
            break

if tauon.sleep_lock is not None:
    del tauon.sleep_lock
if tauon.shutdown_lock is not None:
    del tauon.shutdown_lock
if tauon.play_lock is not None:
    del tauon.play_lock

print("bye")

