commit
a7c7027073
@ -0,0 +1,9 @@
|
||||
{
|
||||
"cSpell.ignoreWords": [
|
||||
"mpris",
|
||||
"playerctl",
|
||||
"stty",
|
||||
"trackid",
|
||||
"xesam"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@ -0,0 +1,60 @@
|
||||
import os
|
||||
import time
|
||||
import structures
|
||||
from select import select
|
||||
import sys
|
||||
|
||||
from modules import renderer
|
||||
|
||||
|
||||
focus = 0
|
||||
|
||||
while True:
|
||||
# Get the terminal size
|
||||
rows, columns = os.popen('stty size', 'r').read().split()
|
||||
rows, columns = int(rows), int(columns)
|
||||
sources = os.popen("playerctl -l -s", "r").read().splitlines()
|
||||
processed_sources = []
|
||||
for source in sources:
|
||||
metadata = os.popen(f"playerctl metadata -p {source} -s", "r").read().splitlines()
|
||||
status = os.popen(f'playerctl status -p {source} -s', 'r').read().strip()
|
||||
watched = float(os.popen(f'playerctl position -p {source} -s', 'r').read().strip() or "0")
|
||||
metadata_dict = {}
|
||||
for line in metadata:
|
||||
split = line.split()
|
||||
key = split[1]
|
||||
value = " ".join(split[2:]) if len(split) > 2 else None
|
||||
metadata_dict[key] = value
|
||||
if "mpris:length" in metadata_dict:
|
||||
metadata_dict["mpris:length"] = round(float(metadata_dict.get("mpris:length", 1)) / 10**6)
|
||||
watched = round(watched)
|
||||
|
||||
if "xesam:url" in metadata_dict:
|
||||
# Get everything after the last slash
|
||||
metadata_dict["xesam:url"] = metadata_dict["xesam:url"].split("/")[-1]
|
||||
title = ["xesam:title", "xesam:url"]
|
||||
source_title = "Unknown Title"
|
||||
for t in title:
|
||||
if t in metadata_dict:
|
||||
source_title = metadata_dict[t]
|
||||
break
|
||||
artist = ["xesam:artist", "xesam:albumArtist", "vlc:publisher"]
|
||||
source_artist = None
|
||||
for a in artist:
|
||||
if a in metadata_dict:
|
||||
source_artist = metadata_dict[a]
|
||||
break
|
||||
processed_sources.append(structures.Source(
|
||||
source=source.split(".")[0],
|
||||
name=source_title,
|
||||
artist=source_artist,
|
||||
length=metadata_dict.get('mpris:length', 1),
|
||||
watched=watched,
|
||||
status=status
|
||||
))
|
||||
focus = min(max(focus, 0), len(processed_sources) - 1) if processed_sources else -1
|
||||
|
||||
processed_sources = sorted(processed_sources, key=lambda source: source.source)
|
||||
out = renderer.render(rows, columns, processed_sources, focus) or {}
|
||||
|
||||
time.sleep(0.25 * out.get("delay_multiplier", 1))
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,118 @@
|
||||
import structures
|
||||
|
||||
|
||||
styles = {
|
||||
"block triangle": " ",
|
||||
"block circle": " ",
|
||||
"block square": "█ ██ 🮈-",
|
||||
"triangle": "|=|> | "
|
||||
}
|
||||
|
||||
|
||||
def seconds_to_time(seconds):
|
||||
hours = f"{seconds // (60 * 60):02}"
|
||||
minutes = f"{seconds // 60:02}"
|
||||
seconds = f"{seconds % 60:02}"
|
||||
all_units = [seconds, minutes, hours]
|
||||
all_units = [unit for i, unit in enumerate(all_units) if int(unit) != 0 or i <= 1]
|
||||
return ":".join(reversed(all_units))
|
||||
|
||||
|
||||
def standard_progress(width, padding, source: structures.Source | None, options):
|
||||
width = width - 2 * padding
|
||||
playing = source.status == "Playing"
|
||||
style = styles[options.get("bar_style", "block triangle")]
|
||||
if options.get("bar_style", "block triangle").startswith("block "):
|
||||
background = "\033[42m" if playing else "\033[41m"
|
||||
background_accent = "\033[106m" if playing else "\033[105m"
|
||||
text_colour = "\033[32m" if playing else "\033[31m"
|
||||
text_accent = "\033[36m" if playing else "\033[35m"
|
||||
background_readable_text = "\033[97m"
|
||||
else:
|
||||
background = ""
|
||||
background_accent = ""
|
||||
text_colour = "\033[32m" if playing else "\033[31m"
|
||||
text_accent = "\033[36m" if playing else "\033[35m"
|
||||
background_readable_text = text_accent
|
||||
clear = "\033[0m"
|
||||
|
||||
timestamp = seconds_to_time(source.watched)
|
||||
duration = seconds_to_time(source.length)
|
||||
remaining = "-" + seconds_to_time(source.length - source.watched)
|
||||
|
||||
full_timestamp = f"{remaining if options['negative_duration'] else timestamp} / {duration}"
|
||||
|
||||
extra_padding = style[6] != "-"
|
||||
remaining_width = width - len(full_timestamp) - 3 - (2 if extra_padding else 0)
|
||||
progress = round(source.watched_percent * remaining_width)
|
||||
finished = progress == remaining_width
|
||||
full_timestamp = f" {full_timestamp} " if extra_padding else f"{full_timestamp}"
|
||||
|
||||
bar = text_accent + style[0] + background_accent + background_readable_text + full_timestamp + text_accent + background + style[2]
|
||||
bar += clear + background + text_colour + (style[1] * progress) + clear + text_colour + ("" if finished else style[3])
|
||||
bar += style[4] * (remaining_width - progress - 1) + text_colour + style[3 if finished else 5]
|
||||
return " " * padding + bar + clear + " " * padding
|
||||
|
||||
|
||||
def custom_progress(width, padding, source: structures.Source, options):
|
||||
return ""
|
||||
|
||||
|
||||
def standard_details(width, padding, source: structures.Source, options):
|
||||
width = width - 2 * padding
|
||||
playing = source.status == "Playing"
|
||||
style = styles[options.get("bar_style", "block triangle")]
|
||||
colours = options["colours"]
|
||||
if options.get("bar_style", "block triangle").startswith("block "):
|
||||
blocked = True
|
||||
background = colours["playing_highlight"] if playing else colours["paused_highlight"]
|
||||
background_accent = colours["playing_accent"] if playing else colours["paused_accent"]
|
||||
secondary_background = colours["details_highlight"]
|
||||
text_colour = colours["playing_text"] if playing else colours["paused_text"]
|
||||
text_accent = colours["playing_text_accent"] if playing else colours["paused_text_accent"]
|
||||
secondary_accent = colours["details_text_accent"]
|
||||
background_readable_text = colours["background_readable_text"]
|
||||
secondary_text_colour = colours["details_text"]
|
||||
else:
|
||||
blocked = False
|
||||
background = ""
|
||||
background_accent = ""
|
||||
secondary_background = ""
|
||||
text_colour = colours["playing_text"] if playing else colours["paused_text"]
|
||||
text_accent = colours["playing_text_accent"] if playing else colours["paused_text_accent"]
|
||||
secondary_accent = ""
|
||||
background_readable_text = text_accent
|
||||
secondary_text_colour = text_accent
|
||||
clear = "\033[0m"
|
||||
remaining_width = width - 5
|
||||
items = [getattr(source, i) for i in options["details_bar"] if getattr(source, i)]
|
||||
pad_string = lambda string: " " + string + " "
|
||||
if len(items) == "0":
|
||||
return " " * width
|
||||
string_width = len(items[0]) + 2
|
||||
bar = text_accent + style[0] + background_accent + background_readable_text + pad_string(items[0])
|
||||
if len(items) == 1:
|
||||
bar += clear + text_accent + style[2]
|
||||
else:
|
||||
bar += text_accent + secondary_background + style[3]
|
||||
for i, item in enumerate(items[1:]):
|
||||
bar += secondary_text_colour + pad_string(item)
|
||||
string_width += len(item) + 2
|
||||
if i != len(items) - 2:
|
||||
bar += style[5 if blocked else 3]
|
||||
else:
|
||||
bar += clear + (secondary_accent if blocked else text_accent) + style[2]
|
||||
return " " * padding + bar + clear + " " * padding
|
||||
|
||||
|
||||
def custom_details(width, padding, source: structures.Source, options):
|
||||
return ""
|
||||
|
||||
|
||||
def progress(width, padding, source: structures.Source, options):
|
||||
# return custom_progress(width, padding, source, options)
|
||||
return standard_progress(width, padding, source, options)
|
||||
|
||||
def details(width, padding, source: structures.Source, options):
|
||||
# return custom_details(width, padding, source, options)
|
||||
return standard_details(width, padding, source, options)
|
||||
@ -0,0 +1,62 @@
|
||||
import structures
|
||||
|
||||
from modules import bar
|
||||
from modules import tabs
|
||||
|
||||
|
||||
config = {
|
||||
"header_align": "left",
|
||||
"padding": 2,
|
||||
"gap": 1,
|
||||
"bar_style": "block triangle",
|
||||
"tab_style": "block",
|
||||
"negative_duration": True,
|
||||
"colours": {
|
||||
"paused_highlight": "\033[41m",
|
||||
"playing_highlight": "\033[42m",
|
||||
"unknown_highlight": "\033[100m",
|
||||
"details_highlight": "\033[103m",
|
||||
"paused_accent": "\033[105m",
|
||||
"playing_accent": "\033[106m",
|
||||
"unknown_accent": "\033[101m",
|
||||
"paused_text": "\033[31m",
|
||||
"playing_text": "\033[32m",
|
||||
"unknown_text": "\033[97m",
|
||||
"details_text": "\033[90m",
|
||||
"paused_text_accent": "\033[35m",
|
||||
"playing_text_accent": "\033[36m",
|
||||
"unknown_text_accent": "\033[97m",
|
||||
"details_text_accent": "\033[93m",
|
||||
"background_readable_text": "\033[97m",
|
||||
"tab_highlight_inactive": "\033[100m",
|
||||
"tab_highlight_inactive_text": "\033[97m",
|
||||
"tab_highlight_selected": "\033[107m",
|
||||
"tab_highlight_selected_text": "\033[090m"
|
||||
},
|
||||
"details_bar": ["name", "artist", "status"]
|
||||
}
|
||||
|
||||
def render(rows: int, columns: int, sources: list[structures.Source], focus):
|
||||
out = {}
|
||||
padding = " " * config["padding"]
|
||||
allowed_width = columns - 2 * config["padding"]
|
||||
if focus < 0:
|
||||
focus_source = structures.Source(
|
||||
source="N/A",
|
||||
name="No applications playing audio",
|
||||
artist=None,
|
||||
length=1,
|
||||
watched=0,
|
||||
status="Unknown"
|
||||
)
|
||||
out["delay_multiplier"] = 4
|
||||
else:
|
||||
focus_source = sources[focus]
|
||||
out["delay_multiplier"] = 1 if focus_source.status == "Playing" else 2
|
||||
|
||||
print("\n" * rows)
|
||||
print(tabs.generate(columns, len(padding), sources, focus, config))
|
||||
print()
|
||||
print(bar.progress(columns, len(padding), focus_source, config))
|
||||
print(bar.details(columns, len(padding), focus_source, config))
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
import structures
|
||||
|
||||
def standard_tabs(width, padding, sources: list[structures.Source], active_index, options):
|
||||
tabs = [source.source.capitalize() for source in sources]
|
||||
if len(tabs) == 0:
|
||||
tabs = ["No applications playing audio"]
|
||||
active_index = 0
|
||||
colours = options["colours"]
|
||||
out = ""
|
||||
style = options.get("tab_style", "block")
|
||||
if style == "block":
|
||||
inactive = colours["tab_highlight_inactive"] + colours["tab_highlight_inactive_text"]
|
||||
active = colours["tab_highlight_selected"] + colours["tab_highlight_selected_text"]
|
||||
out += inactive + " " * options["padding"]
|
||||
tab_width = 0
|
||||
for i, tab in enumerate(tabs):
|
||||
if i == active_index:
|
||||
out += active
|
||||
out += f" {tab} " + inactive + " " * options["gap"]
|
||||
tab_width += len(tab) + 2 + options["gap"]
|
||||
out += inactive
|
||||
# Add extra space to the end
|
||||
out += " " * (width - tab_width - padding)
|
||||
else:
|
||||
out += " " * padding
|
||||
tab_width = 0
|
||||
for i, tab in enumerate(tabs):
|
||||
if i == active_index:
|
||||
out += colours["tab_highlight_selected_text"]
|
||||
else:
|
||||
out += colours["tab_highlight_inactive_text"]
|
||||
out += f" [{tab}] "
|
||||
tab_width += len(tab) + 4
|
||||
return out + "\033[0m"
|
||||
|
||||
|
||||
def custom_tabs(width, padding, sources: list[structures.Source], active_index, options):
|
||||
return " " * width
|
||||
|
||||
|
||||
def generate(width, padding, sources: list[structures.Source], active_index, options):
|
||||
# return custom_tabs(width, padding, sources, active_index, options)
|
||||
return standard_tabs(width, padding, sources, active_index, options)
|
||||
@ -0,0 +1,16 @@
|
||||
class Source:
|
||||
def __init__(self,
|
||||
source: str = None,
|
||||
name: str = None,
|
||||
artist: str = None,
|
||||
length: int = None,
|
||||
watched: int = None,
|
||||
status: str = None
|
||||
):
|
||||
self.source = (source or "Unknown Source").strip()
|
||||
self.name = (name or "N/A").strip()
|
||||
self.artist = artist
|
||||
self.length = length or 1
|
||||
self.watched = watched or 0
|
||||
self.watched_percent = min(max(self.watched / self.length, 0), 1)
|
||||
self.status = status or "Playing"
|
||||
Loading…
Reference in new issue