Добавлены настройки расширения, введена поддержка локализации, изменены и адаптированы иконки приложений
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
@ -12,7 +13,61 @@ from jinja2 import Environment, FileSystemLoader
|
||||
import config
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
class TextStyle:
|
||||
PURPLE = '\033[95m'
|
||||
CYAN = '\033[96m'
|
||||
DARKCYAN = '\033[36m'
|
||||
BLUE = '\033[94m'
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
RED = '\033[91m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
||||
END = '\033[0m'
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
bold = (TextStyle.BOLD, TextStyle.END)
|
||||
print('%sProgram Name:%s\tYT Music Live' % bold)
|
||||
print('%sVersion:%s\t1.0.0' % bold)
|
||||
print('%sAuthor:%s\t\tcsasq' % bold)
|
||||
print('%sEmail:%s\t\tcsasq@csasq.ru' % bold)
|
||||
print('%sWebsite:%s\thttps://csasq.ru/' % bold)
|
||||
print('%sLicense:%s\tCreative Commons Attribution-NonCommercial 4.0 International' % bold)
|
||||
print()
|
||||
print("%sYou are free to:%s" % bold)
|
||||
print("\t%sShare%s — copy and redistribute the material in any medium or format." % bold)
|
||||
print("\t%sAdapt%s — remix, transform, and build upon the material." % bold)
|
||||
print('\tThe licensor cannot revoke these freedoms as long as you follow the license terms.')
|
||||
print("%sUnder the following terms:%s" % bold)
|
||||
print("\t%sAttribution%s — You must give appropriate credit , provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use." % bold)
|
||||
print('\t%sNonCommercial%s — You may not use the material for commercial purposes.' % bold)
|
||||
print('\t%sNo additional restrictions%s — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.' % bold)
|
||||
print()
|
||||
print('%sSee the legal code:%s' % bold)
|
||||
print('https://creativecommons.org/licenses/by-nc/4.0/')
|
||||
print()
|
||||
print('%sSee the Git repository:%s' % bold)
|
||||
print('https://git.csasq.ru/csasq/yt-music-live')
|
||||
print()
|
||||
print('Application startup complete (Press CTRL+C to quit)')
|
||||
print('Widget is available at %shttp://%s:%d/overlay%s' % (
|
||||
TextStyle.BOLD,
|
||||
config.Main.host,
|
||||
config.Main.port,
|
||||
TextStyle.END,
|
||||
))
|
||||
yield
|
||||
print('Application shutdown complete')
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
openapi_url=None,
|
||||
docs_url=None,
|
||||
redoc_url=None,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
app.mount(
|
||||
path='/static',
|
||||
app=StaticFiles(
|
||||
@ -51,42 +106,16 @@ class ConnectionManager:
|
||||
|
||||
|
||||
overlay_manager = ConnectionManager()
|
||||
plugin_manager = ConnectionManager()
|
||||
|
||||
|
||||
@app.options(
|
||||
path='/api/v1/overlay',
|
||||
)
|
||||
async def function():
|
||||
return Response(
|
||||
headers={
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': '*',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
},
|
||||
status_code=200,
|
||||
)
|
||||
server_manager = ConnectionManager()
|
||||
|
||||
|
||||
@app.get(
|
||||
path='/overlay',
|
||||
)
|
||||
async def function(
|
||||
justify_content: str = Query(
|
||||
default='end',
|
||||
alias='justify-content',
|
||||
),
|
||||
align_items: str = Query(
|
||||
default='end',
|
||||
alias='align-items',
|
||||
),
|
||||
):
|
||||
async def _():
|
||||
template = env.get_template('overlay.jinja2')
|
||||
return HTMLResponse(
|
||||
content=await template.render_async(
|
||||
justify_content=justify_content,
|
||||
align_items=align_items,
|
||||
),
|
||||
content=await template.render_async(),
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
@ -94,7 +123,7 @@ async def function(
|
||||
@app.get(
|
||||
path='/image',
|
||||
)
|
||||
async def function(
|
||||
async def _(
|
||||
src: str = Query(
|
||||
default=...,
|
||||
),
|
||||
@ -125,27 +154,41 @@ async def function(
|
||||
)
|
||||
|
||||
|
||||
@app.options(
|
||||
path='/api/v1/overlay',
|
||||
)
|
||||
async def _():
|
||||
return Response(
|
||||
headers={
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': '*',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
|
||||
@app.websocket('/ws/v1/overlay')
|
||||
async def function(
|
||||
async def _(
|
||||
websocket: WebSocket,
|
||||
):
|
||||
await overlay_manager.connect(websocket)
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_json()
|
||||
await plugin_manager.broadcast(data)
|
||||
await server_manager.broadcast(data)
|
||||
except WebSocketDisconnect:
|
||||
overlay_manager.disconnect(websocket)
|
||||
|
||||
|
||||
@app.websocket('/ws/v1/plugin')
|
||||
async def function(
|
||||
@app.websocket('/ws/v1/server')
|
||||
async def _(
|
||||
websocket: WebSocket,
|
||||
):
|
||||
await plugin_manager.connect(websocket)
|
||||
await server_manager.connect(websocket)
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_json()
|
||||
await overlay_manager.broadcast(data)
|
||||
except WebSocketDisconnect:
|
||||
plugin_manager.disconnect(websocket)
|
||||
server_manager.disconnect(websocket)
|
||||
|
BIN
server/icon.ico
BIN
server/icon.ico
Binary file not shown.
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 7.3 KiB |
@ -1,3 +1,9 @@
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "csasq"
|
||||
__license__ = "CC BY-NC 4.0"
|
||||
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
|
||||
import uvicorn
|
||||
@ -12,6 +18,7 @@ if __name__ == '__main__':
|
||||
app=app,
|
||||
host=config.Main.host,
|
||||
port=config.Main.port,
|
||||
log_level=logging.WARNING,
|
||||
)
|
||||
except InterruptedError:
|
||||
shutil.rmtree(config.Main.images_directory)
|
||||
|
@ -1,41 +1,50 @@
|
||||
const colorThief = new ColorThief();
|
||||
const musicElement = document.querySelector('#music');
|
||||
const progressBarElement = musicElement.querySelector('#music > .progress-bar');
|
||||
const titleElement = progressBarElement.querySelector('#music > .progress-bar > .data > .title');
|
||||
const artistsElement = progressBarElement.querySelector('#music > .progress-bar > .data > .artists');
|
||||
const coverElement = progressBarElement.querySelector('#music > .progress-bar > .cover');
|
||||
const locales = {
|
||||
playing_now_title: {
|
||||
en: 'Playing Now',
|
||||
ru: 'Сейчас играет',
|
||||
},
|
||||
}
|
||||
|
||||
const colorThief = new ColorThief()
|
||||
const musicElement = document.querySelector('#music')
|
||||
const titleElement = musicElement.querySelector('#music > .title')
|
||||
const musicProgressBarElement = musicElement.querySelector('#music > .progress-bar')
|
||||
const musicTitleElement = musicProgressBarElement.querySelector('#music > .progress-bar > .data > .title')
|
||||
const musicArtistsElement = musicProgressBarElement.querySelector('#music > .progress-bar > .data > .artists')
|
||||
const musicCoverElement = musicProgressBarElement.querySelector('#music > .progress-bar > .cover')
|
||||
|
||||
const handlers = {
|
||||
'target': (attributes) => {
|
||||
const target = document.getElementById(attributes.id);
|
||||
target.style.setProperty('--progress-size', `${attributes.progress}`);
|
||||
const target = document.getElementById(attributes.id)
|
||||
target.style.setProperty('--progress-size', `${attributes.progress}`)
|
||||
},
|
||||
'music': (attributes) => {
|
||||
titleElement.textContent = attributes.title;
|
||||
artistsElement.textContent = attributes.artists;
|
||||
musicElement.style.setProperty('--progress-size', `${(1 - attributes.progress) * 100}%`);
|
||||
const url = new URL(location);
|
||||
url.pathname = '/image';
|
||||
const urlSearchParams = new URLSearchParams();
|
||||
urlSearchParams.set('src', attributes.image);
|
||||
url.search = urlSearchParams.toString();
|
||||
coverElement.src = url;
|
||||
const colors = colorThief.getPalette(coverElement, 2);
|
||||
progressBarElement.style.setProperty('--primary-color', `rgb(${colors[0][0]}, ${colors[0][1]}, ${colors[0][2]})`);
|
||||
progressBarElement.style.setProperty('--secondary-color', `rgb(${colors[1][0]}, ${colors[1][1]}, ${colors[1][2]})`);
|
||||
musicElement.classList.remove('hidden');
|
||||
titleElement.textContent = locales.playing_now_title[attributes.locale] || locales.playing_now_title.en
|
||||
musicTitleElement.textContent = attributes.title
|
||||
musicArtistsElement.textContent = attributes.artists
|
||||
musicElement.style.setProperty('--progress-size', `${(1 - attributes.progress) * 100}%`)
|
||||
const url = new URL(location)
|
||||
url.pathname = '/image'
|
||||
const urlSearchParams = new URLSearchParams()
|
||||
urlSearchParams.set('src', attributes.image)
|
||||
url.search = urlSearchParams.toString()
|
||||
musicCoverElement.src = url
|
||||
const colors = colorThief.getPalette(musicCoverElement, 2)
|
||||
musicProgressBarElement.style.setProperty('--primary-color', `rgb(${colors[0][0]}, ${colors[0][1]}, ${colors[0][2]})`)
|
||||
musicProgressBarElement.style.setProperty('--secondary-color', `rgb(${colors[1][0]}, ${colors[1][1]}, ${colors[1][2]})`)
|
||||
musicElement.classList.remove('hidden')
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const connectWebSocket = () => {
|
||||
const ws = new WebSocket('ws://localhost:8000/ws/v1/overlay');
|
||||
const ws = new WebSocket('ws://localhost:8000/ws/v1/overlay')
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
handlers[data.type](data.attributes);
|
||||
};
|
||||
const data = JSON.parse(event.data)
|
||||
handlers[data.type](data.attributes)
|
||||
}
|
||||
|
||||
ws.onclose = () => connectWebSocket();
|
||||
};
|
||||
ws.onclose = () => connectWebSocket()
|
||||
}
|
||||
|
||||
connectWebSocket();
|
||||
connectWebSocket()
|
||||
|
@ -23,6 +23,8 @@
|
||||
html {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -2,22 +2,16 @@
|
||||
<html lang="ru-RU">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<title>YT Music Live</title>
|
||||
<link rel="stylesheet" href="/static/styles/main.css" />
|
||||
<style>
|
||||
html {
|
||||
justify-content: {{ justify_content }};
|
||||
align-items: {{ align_items }};
|
||||
}
|
||||
</style>
|
||||
<script src="/static/scripts/color-thief.umd.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<div id="music" class="hidden">
|
||||
<span class="title">Сейчас играет</span>
|
||||
<span class="title"></span>
|
||||
<div class="progress-bar">
|
||||
<img class="cover" />
|
||||
<img class="cover" alt="" />
|
||||
<div class="data">
|
||||
<span class="title"></span>
|
||||
<span class="artists"></span>
|
||||
|
Reference in New Issue
Block a user