servicing
This commit is contained in:
parent
b2dc8c5edb
commit
872271fc6f
@ -280,6 +280,10 @@ class MusicAppFactory(AppFactory):
|
|||||||
except Api.MisusedApi as e:
|
except Api.MisusedApi as e:
|
||||||
return web.json_response(e.json(), status=404)
|
return web.json_response(e.json(), status=404)
|
||||||
|
|
||||||
|
@routes.get('/whaturl/')
|
||||||
|
async def whaturl(request: web.Request) -> web.StreamResponse:
|
||||||
|
return web.json_response(str(request.url))
|
||||||
|
|
||||||
|
|
||||||
class AppContext:
|
class AppContext:
|
||||||
def __init__(self, mainservice: MainService) -> None:
|
def __init__(self, mainservice: MainService) -> None:
|
||||||
|
@ -3,19 +3,20 @@ from typing import Callable
|
|||||||
|
|
||||||
from v6d3music.core.default_effects import *
|
from v6d3music.core.default_effects import *
|
||||||
from v6d3music.core.mainservice import MainService
|
from v6d3music.core.mainservice import MainService
|
||||||
from v6d3music.core.yt_audios import yt_audios
|
|
||||||
from v6d3music.utils.assert_admin import assert_admin
|
from v6d3music.utils.assert_admin import assert_admin
|
||||||
from v6d3music.utils.catch import catch
|
from v6d3music.utils.catch import *
|
||||||
from v6d3music.utils.effects_for_preset import effects_for_preset
|
from v6d3music.utils.effects_for_preset import *
|
||||||
from v6d3music.utils.options_for_effects import options_for_effects
|
from v6d3music.utils.options_for_effects import *
|
||||||
from v6d3music.utils.presets import allowed_presets
|
from v6d3music.utils.presets import allowed_presets
|
||||||
|
|
||||||
from v6d2ctx.at_of import AtOf
|
from v6d2ctx.at_of import AtOf
|
||||||
from v6d2ctx.context import Context, Explicit, command_type
|
from v6d2ctx.context import Context, Explicit, command_type
|
||||||
from v6d2ctx.lock_for import lock_for
|
from v6d2ctx.lock_for import lock_for
|
||||||
|
|
||||||
|
__all__ = ('get_of',)
|
||||||
|
|
||||||
def get_of(mainservice: MainService, defaulteffects: DefaultEffects) -> Callable[[str], command_type]:
|
|
||||||
|
def get_of(mainservice: MainService) -> Callable[[str], command_type]:
|
||||||
at_of: AtOf[str, command_type] = AtOf()
|
at_of: AtOf[str, command_type] = AtOf()
|
||||||
at, of = at_of()
|
at, of = at_of()
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ def get_of(mainservice: MainService, defaulteffects: DefaultEffects) -> Callable
|
|||||||
if len(ctx.message.attachments) > 1:
|
if len(ctx.message.attachments) > 1:
|
||||||
raise Explicit('no more than one attachment')
|
raise Explicit('no more than one attachment')
|
||||||
args = [ctx.message.attachments[0].url] + args
|
args = [ctx.message.attachments[0].url] + args
|
||||||
async for audio in yt_audios(mainservice.caching, defaulteffects, ctx, args):
|
async for audio in mainservice.yt_audios(ctx, args):
|
||||||
queue.append(audio)
|
queue.append(audio)
|
||||||
await ctx.reply('done')
|
await ctx.reply('done')
|
||||||
|
|
||||||
@ -136,12 +137,12 @@ def get_of(mainservice: MainService, defaulteffects: DefaultEffects) -> Callable
|
|||||||
case ['none']:
|
case ['none']:
|
||||||
effects = None
|
effects = None
|
||||||
case []:
|
case []:
|
||||||
await ctx.reply(f'current default effects: {defaulteffects.get(ctx.guild.id)}')
|
await ctx.reply(f'current default effects: {mainservice.defaulteffects.get(ctx.guild.id)}')
|
||||||
return
|
return
|
||||||
case _:
|
case _:
|
||||||
raise Explicit('misformatted')
|
raise Explicit('misformatted')
|
||||||
assert_admin(ctx.member)
|
assert_admin(ctx.member)
|
||||||
await defaulteffects.set(ctx.guild.id, effects)
|
await mainservice.defaulteffects.set(ctx.guild.id, effects)
|
||||||
await ctx.reply(f'effects set to `{effects}`')
|
await ctx.reply(f'effects set to `{effects}`')
|
||||||
|
|
||||||
@at('repeat')
|
@at('repeat')
|
||||||
@ -155,14 +156,13 @@ def get_of(mainservice: MainService, defaulteffects: DefaultEffects) -> Callable
|
|||||||
raise Explicit('misformatted')
|
raise Explicit('misformatted')
|
||||||
assert_admin(ctx.member)
|
assert_admin(ctx.member)
|
||||||
queue = await mainservice.context(ctx, create=False, force_play=False).queue()
|
queue = await mainservice.context(ctx, create=False, force_play=False).queue()
|
||||||
if not queue.queue:
|
queue.repeat(n)
|
||||||
raise Explicit('empty queue')
|
|
||||||
if n > 99:
|
@at('shuffle')
|
||||||
raise Explicit('too long')
|
async def shuffle(ctx: Context, args: list[str]):
|
||||||
audio = queue.queue[0]
|
assert_admin(ctx.member)
|
||||||
for _ in range(n):
|
queue = await mainservice.context(ctx, create=False, force_play=False).queue()
|
||||||
queue.queue.insert(1, audio.copy())
|
queue.shuffle()
|
||||||
queue.update_sources()
|
|
||||||
|
|
||||||
@at('branch')
|
@at('branch')
|
||||||
async def branch(ctx: Context, args: list[str]):
|
async def branch(ctx: Context, args: list[str]):
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
import string
|
import string
|
||||||
from typing import Any, Optional
|
|
||||||
|
|
||||||
from v6d2ctx.context import Context, Explicit, escape
|
|
||||||
|
|
||||||
from v6d3music.core.real_url import real_url
|
from v6d3music.core.real_url import real_url
|
||||||
from v6d3music.core.caching import Caching
|
from v6d3music.core.ytaservicing import *
|
||||||
from v6d3music.core.ytaudio import YTAudio
|
from v6d3music.core.ytaudio import YTAudio
|
||||||
|
from v6d3music.utils.argctx import InfoCtx
|
||||||
from v6d3music.utils.assert_admin import assert_admin
|
from v6d3music.utils.assert_admin import assert_admin
|
||||||
from v6d3music.utils.options_for_effects import options_for_effects
|
from v6d3music.utils.options_for_effects import options_for_effects
|
||||||
from v6d3music.utils.presets import allowed_effects
|
from v6d3music.utils.presets import allowed_effects
|
||||||
from v6d3music.utils.argctx import InfoCtx
|
|
||||||
|
from v6d2ctx.context import Context, Explicit, escape
|
||||||
|
|
||||||
|
|
||||||
async def create_ytaudio(
|
async def create_ytaudio(
|
||||||
caching: Caching, ctx: Context, it: InfoCtx
|
servicing: YTAServicing, ctx: Context, it: InfoCtx
|
||||||
) -> YTAudio:
|
) -> YTAudio:
|
||||||
assert ctx.member is not None
|
assert ctx.member is not None
|
||||||
if it.effects:
|
if it.effects:
|
||||||
@ -25,8 +24,8 @@ async def create_ytaudio(
|
|||||||
else:
|
else:
|
||||||
options = None
|
options = None
|
||||||
return YTAudio(
|
return YTAudio(
|
||||||
caching,
|
servicing,
|
||||||
await real_url(caching, it.info['url'], False, it.tor),
|
await real_url(servicing.caching, it.info['url'], False, it.tor),
|
||||||
it.info['url'],
|
it.info['url'],
|
||||||
f'{escape(it.info.get("title", "unknown"))} `Rby` {ctx.member}',
|
f'{escape(it.info.get("title", "unknown"))} `Rby` {ctx.member}',
|
||||||
options,
|
options,
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import discord
|
import discord
|
||||||
from v6d3music.core.caching import Caching
|
from v6d3music.core.ytaservicing import *
|
||||||
from v6d3music.core.queueaudio import QueueAudio
|
from v6d3music.core.queueaudio import *
|
||||||
from v6d3music.utils.assert_admin import assert_admin
|
from v6d3music.utils.assert_admin import assert_admin
|
||||||
|
|
||||||
from ptvp35 import *
|
from ptvp35 import *
|
||||||
from v6d2ctx.context import Explicit
|
from v6d2ctx.context import Explicit
|
||||||
|
|
||||||
|
__all__ = ('MainAudio',)
|
||||||
|
|
||||||
|
|
||||||
class MainAudio(discord.PCMVolumeTransformer):
|
class MainAudio(discord.PCMVolumeTransformer):
|
||||||
def __init__(self, db: DbConnection, queue: QueueAudio, volume: float):
|
def __init__(self, db: DbConnection, queue: QueueAudio, volume: float):
|
||||||
@ -23,5 +25,5 @@ class MainAudio(discord.PCMVolumeTransformer):
|
|||||||
await self.db.set(member.guild.id, volume)
|
await self.db.set(member.guild.id, volume)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def create(cls, caching: Caching, db: DbConnection, queues: DbConnection, guild: discord.Guild) -> 'MainAudio':
|
async def create(cls, servicing: YTAServicing, db: DbConnection, queues: DbConnection, guild: discord.Guild) -> 'MainAudio':
|
||||||
return cls(db, await QueueAudio.create(caching, queues, guild), volume=db.get(guild.id, 0.2))
|
return cls(db, await QueueAudio.create(servicing, queues, guild), volume=db.get(guild.id, 0.2))
|
||||||
|
@ -1,22 +1,33 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
|
from typing import AsyncIterable, TypeVar
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from v6d3music.config import myroot
|
from v6d3music.config import myroot
|
||||||
from v6d3music.core.caching import Caching
|
from v6d3music.core.caching import *
|
||||||
from v6d3music.core.mainaudio import MainAudio
|
from v6d3music.core.default_effects import *
|
||||||
from v6d3music.core.queueaudio import QueueAudio
|
from v6d3music.core.mainaudio import *
|
||||||
|
from v6d3music.core.queueaudio import *
|
||||||
|
from v6d3music.core.ystate import *
|
||||||
|
from v6d3music.core.ytaservicing import *
|
||||||
|
from v6d3music.core.ytaudio import *
|
||||||
|
from v6d3music.processing.pool import *
|
||||||
|
from v6d3music.utils.argctx import *
|
||||||
|
|
||||||
from ptvp35 import *
|
from ptvp35 import *
|
||||||
from v6d2ctx.context import Context, Explicit
|
from v6d2ctx.context import Context, Explicit
|
||||||
from v6d2ctx.lock_for import lock_for
|
from v6d2ctx.lock_for import lock_for
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
class MainService:
|
class MainService:
|
||||||
def __init__(self, client: discord.Client) -> None:
|
def __init__(self, defaulteffects: DefaultEffects, client: discord.Client) -> None:
|
||||||
|
self.defaulteffects = defaulteffects
|
||||||
self.client = client
|
self.client = client
|
||||||
self.mains: dict[discord.Guild, MainAudio] = {}
|
self.mains: dict[discord.Guild, MainAudio] = {}
|
||||||
|
self.restore_lock = asyncio.Lock()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def raw_vc_for_member(member: discord.Member) -> discord.VoiceClient:
|
async def raw_vc_for_member(member: discord.Member) -> discord.VoiceClient:
|
||||||
@ -51,16 +62,18 @@ class MainService:
|
|||||||
return self.descriptor(create=create, force_play=force_play).context(ctx)
|
return self.descriptor(create=create, force_play=force_play).context(ctx)
|
||||||
|
|
||||||
async def create(self, guild: discord.Guild) -> MainAudio:
|
async def create(self, guild: discord.Guild) -> MainAudio:
|
||||||
return await MainAudio.create(self.caching, self.__volumes, self.queues, guild)
|
return await MainAudio.create(self.__servicing, self.__volumes, self.__queues, guild)
|
||||||
|
|
||||||
async def __aenter__(self) -> 'MainService':
|
async def __aenter__(self) -> 'MainService':
|
||||||
async with AsyncExitStack() as es:
|
async with AsyncExitStack() as es:
|
||||||
self.__volumes = await es.enter_async_context(DbFactory(myroot / 'volume.db', kvfactory=KVJson()))
|
self.__volumes = await es.enter_async_context(DbFactory(myroot / 'volume.db', kvfactory=KVJson()))
|
||||||
self.queues = await es.enter_async_context(DbFactory(myroot / 'queue.db', kvfactory=KVJson()))
|
self.__queues = await es.enter_async_context(DbFactory(myroot / 'queue.db', kvfactory=KVJson()))
|
||||||
self.caching = await es.enter_async_context(Caching())
|
self.__caching = await es.enter_async_context(Caching())
|
||||||
|
self.__pool = await es.enter_async_context(Pool(5))
|
||||||
|
self.__servicing = YTAServicing(self.__caching, self.__pool)
|
||||||
self.__vcs_restored: asyncio.Future[None] = asyncio.Future()
|
self.__vcs_restored: asyncio.Future[None] = asyncio.Future()
|
||||||
self.__es = es.pop_all()
|
|
||||||
self.__save_task = asyncio.create_task(self.save_daemon())
|
self.__save_task = asyncio.create_task(self.save_daemon())
|
||||||
|
self.__es = es.pop_all()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
@ -83,10 +96,10 @@ class MainService:
|
|||||||
if vc.is_playing():
|
if vc.is_playing():
|
||||||
if vc.guild is not None and vc.channel is not None:
|
if vc.guild is not None and vc.channel is not None:
|
||||||
vcs.append((vc.guild.id, vc.channel.id, vc.is_paused()))
|
vcs.append((vc.guild.id, vc.channel.id, vc.is_paused()))
|
||||||
self.queues.set_nowait('vcs', vcs)
|
self.__queues.set_nowait('vcs', vcs)
|
||||||
|
|
||||||
async def save_commit(self) -> None:
|
async def save_commit(self) -> None:
|
||||||
await self.queues.commit()
|
await self.__queues.commit()
|
||||||
|
|
||||||
async def _save_all(self, delay: bool, save_playing: bool) -> None:
|
async def _save_all(self, delay: bool, save_playing: bool) -> None:
|
||||||
await self.save_queues(delay)
|
await self.save_queues(delay)
|
||||||
@ -152,7 +165,7 @@ class MainService:
|
|||||||
print(f'vc restored {vcgid} {vccid}')
|
print(f'vc restored {vcgid} {vccid}')
|
||||||
|
|
||||||
async def restore_vcs(self) -> None:
|
async def restore_vcs(self) -> None:
|
||||||
vcs: list[tuple[int, int, bool]] = self.queues.get('vcs', [])
|
vcs: list[tuple[int, int, bool]] = self.__queues.get('vcs', [])
|
||||||
try:
|
try:
|
||||||
tasks = []
|
tasks = []
|
||||||
for vcgid, vccid, vc_is_paused in vcs:
|
for vcgid, vccid, vc_is_paused in vcs:
|
||||||
@ -163,10 +176,16 @@ class MainService:
|
|||||||
self.__vcs_restored.set_result(None)
|
self.__vcs_restored.set_result(None)
|
||||||
|
|
||||||
async def restore(self) -> None:
|
async def restore(self) -> None:
|
||||||
async with lock_for('vcs_restored', '...'):
|
async with self.restore_lock:
|
||||||
if not self.__vcs_restored.done():
|
if not self.__vcs_restored.done():
|
||||||
await self.restore_vcs()
|
await self.restore_vcs()
|
||||||
|
|
||||||
|
async def yt_audios(self, ctx: Context, args: list[str]) -> AsyncIterable[YTAudio]:
|
||||||
|
assert ctx.guild is not None
|
||||||
|
argctx = ArgCtx(self.defaulteffects.get(ctx.guild.id), args)
|
||||||
|
async for audio in YState(self.__servicing, self.__pool, ctx, argctx.sources).iterate():
|
||||||
|
yield audio
|
||||||
|
|
||||||
|
|
||||||
class MainDescriptor:
|
class MainDescriptor:
|
||||||
def __init__(self, service: MainService, *, create: bool, force_play: bool) -> None:
|
def __init__(self, service: MainService, *, create: bool, force_play: bool) -> None:
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import random
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
from typing import MutableSequence
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from v6d3music.core.caching import Caching
|
from v6d2ctx.context import Explicit
|
||||||
|
from v6d3music.core.ytaservicing import *
|
||||||
from v6d3music.core.ytaudio import YTAudio
|
from v6d3music.core.ytaudio import YTAudio
|
||||||
from v6d3music.utils.assert_admin import assert_admin
|
from v6d3music.utils.assert_admin import assert_admin
|
||||||
from v6d3music.utils.fill import FILL
|
from v6d3music.utils.fill import FILL
|
||||||
|
|
||||||
from ptvp35 import *
|
from ptvp35 import *
|
||||||
|
|
||||||
|
__all__ = ('QueueAudio',)
|
||||||
|
|
||||||
|
|
||||||
PRE_SET_LENGTH = 24
|
PRE_SET_LENGTH = 24
|
||||||
|
|
||||||
|
|
||||||
@ -30,12 +36,12 @@ class QueueAudio(discord.AudioSource):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def respawned(caching: Caching, db: DbConnection, guild: discord.Guild) -> list[YTAudio]:
|
async def respawned(servicing: YTAServicing, db: DbConnection, guild: discord.Guild) -> list[YTAudio]:
|
||||||
respawned = []
|
respawned = []
|
||||||
try:
|
try:
|
||||||
for audio_respawn in db.get(guild.id, []):
|
for audio_respawn in db.get(guild.id, []):
|
||||||
try:
|
try:
|
||||||
respawned.append(await YTAudio.respawn(caching, guild, audio_respawn))
|
respawned.append(await YTAudio.respawn(servicing, guild, audio_respawn))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('audio respawn failed', e)
|
print('audio respawn failed', e)
|
||||||
raise
|
raise
|
||||||
@ -44,8 +50,8 @@ class QueueAudio(discord.AudioSource):
|
|||||||
return respawned
|
return respawned
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def create(cls, caching: Caching, db: DbConnection, guild: discord.Guild) -> 'QueueAudio':
|
async def create(cls, servicing: YTAServicing, db: DbConnection, guild: discord.Guild) -> 'QueueAudio':
|
||||||
return cls(db, guild, await cls.respawned(caching, db, guild))
|
return cls(db, guild, await cls.respawned(servicing, db, guild))
|
||||||
|
|
||||||
async def save(self, delay: bool) -> None:
|
async def save(self, delay: bool) -> None:
|
||||||
hybernated = []
|
hybernated = []
|
||||||
@ -143,3 +149,41 @@ class QueueAudio(discord.AudioSource):
|
|||||||
import random
|
import random
|
||||||
audios = list(self.queue)
|
audios = list(self.queue)
|
||||||
return [await audio.pubjson(member) for audio, _ in zip(audios, range(limit))]
|
return [await audio.pubjson(member) for audio, _ in zip(audios, range(limit))]
|
||||||
|
|
||||||
|
def repeat(self, n: int) -> None:
|
||||||
|
if not self.queue:
|
||||||
|
raise Explicit('empty queue')
|
||||||
|
if n > 99:
|
||||||
|
raise Explicit('too long')
|
||||||
|
audio = self.queue[0]
|
||||||
|
for _ in range(n):
|
||||||
|
self.queue.insert(1, audio.copy())
|
||||||
|
self.update_sources()
|
||||||
|
|
||||||
|
def shuffle(self) -> None:
|
||||||
|
try:
|
||||||
|
random.shuffle(ForwardView(self.queue))
|
||||||
|
except:
|
||||||
|
from traceback import print_exc
|
||||||
|
print_exc()
|
||||||
|
self.update_sources()
|
||||||
|
|
||||||
|
|
||||||
|
class ForwardView(MutableSequence[YTAudio]):
|
||||||
|
def __init__(self, sequence: MutableSequence[YTAudio]) -> None:
|
||||||
|
self.sequence = sequence
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return max(0, self.sequence.__len__() - 1)
|
||||||
|
|
||||||
|
def __setitem__(self, index: int, value: YTAudio) -> None:
|
||||||
|
self.sequence.__setitem__(index + 1, value)
|
||||||
|
|
||||||
|
def __getitem__(self, index: int) -> YTAudio:
|
||||||
|
return self.sequence.__getitem__(index + 1)
|
||||||
|
|
||||||
|
def __delitem__(self, index: int | slice) -> None:
|
||||||
|
self.sequence.__delitem__(index)
|
||||||
|
|
||||||
|
def insert(self, index: int, value: YTAudio) -> None:
|
||||||
|
self.sequence.insert(index, value)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from v6d3music.core.caching import Caching
|
from v6d3music.core.caching import *
|
||||||
from v6d3music.utils.bytes_hash import bytes_hash
|
from v6d3music.utils.bytes_hash import bytes_hash
|
||||||
from v6d3music.utils.tor_prefix import tor_prefix
|
from v6d3music.utils.tor_prefix import tor_prefix
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ from collections import deque
|
|||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import AsyncIterable, Iterable
|
from typing import AsyncIterable, Iterable
|
||||||
|
|
||||||
|
from v6d3music.core.ytaservicing import *
|
||||||
from v6d3music.core.create_ytaudio import *
|
from v6d3music.core.create_ytaudio import *
|
||||||
from v6d3music.core.ytaudio import *
|
from v6d3music.core.ytaudio import *
|
||||||
from v6d3music.processing.pool import *
|
from v6d3music.processing.pool import *
|
||||||
@ -14,8 +15,8 @@ __all__ = ('YState',)
|
|||||||
|
|
||||||
|
|
||||||
class YState:
|
class YState:
|
||||||
def __init__(self, caching: Caching, pool: Pool, ctx: Context, sources: Iterable[UrlCtx]) -> None:
|
def __init__(self, servicing: YTAServicing, pool: Pool, ctx: Context, sources: Iterable[UrlCtx]) -> None:
|
||||||
self.caching = caching
|
self.servicing = servicing
|
||||||
self.pool = pool
|
self.pool = pool
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.sources: deque[UrlCtx] = deque(sources)
|
self.sources: deque[UrlCtx] = deque(sources)
|
||||||
@ -53,7 +54,7 @@ class YState:
|
|||||||
|
|
||||||
async def result(self, entry: InfoCtx) -> YTAudio | None:
|
async def result(self, entry: InfoCtx) -> YTAudio | None:
|
||||||
try:
|
try:
|
||||||
return await create_ytaudio(self.caching, self.ctx, entry)
|
return await create_ytaudio(self.servicing, self.ctx, entry)
|
||||||
except Exception:
|
except Exception:
|
||||||
if not entry.ignore:
|
if not entry.ignore:
|
||||||
raise
|
raise
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
from typing import AsyncIterable
|
|
||||||
|
|
||||||
from v6d3music.core.default_effects import *
|
|
||||||
from v6d3music.core.ystate import *
|
|
||||||
from v6d3music.core.ytaudio import *
|
|
||||||
from v6d3music.core.caching import Caching
|
|
||||||
from v6d3music.processing.pool import *
|
|
||||||
from v6d3music.utils.argctx import *
|
|
||||||
|
|
||||||
from v6d2ctx.context import Context
|
|
||||||
|
|
||||||
|
|
||||||
async def yt_audios(caching: Caching, defaulteffects: DefaultEffects, ctx: Context, args: list[str]) -> AsyncIterable[YTAudio]:
|
|
||||||
assert ctx.guild is not None
|
|
||||||
argctx = ArgCtx(defaulteffects.get(ctx.guild.id), args)
|
|
||||||
async with Pool(5) as pool:
|
|
||||||
async for audio in YState(caching, pool, ctx, argctx.sources).iterate():
|
|
||||||
yield audio
|
|
11
v6d3music/core/ytaservicing.py
Normal file
11
v6d3music/core/ytaservicing.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from v6d3music.core.caching import *
|
||||||
|
from v6d3music.processing.abstractrunner import *
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ('YTAServicing',)
|
||||||
|
|
||||||
|
|
||||||
|
class YTAServicing:
|
||||||
|
def __init__(self, caching: Caching, runner: AbstractRunner) -> None:
|
||||||
|
self.caching = caching
|
||||||
|
self.runner = runner
|
@ -5,25 +5,22 @@ from typing import Optional
|
|||||||
import discord
|
import discord
|
||||||
from v6d3music.core.ffmpegnormalaudio import FFmpegNormalAudio
|
from v6d3music.core.ffmpegnormalaudio import FFmpegNormalAudio
|
||||||
from v6d3music.core.real_url import real_url
|
from v6d3music.core.real_url import real_url
|
||||||
from v6d3music.core.caching import Caching
|
|
||||||
from v6d3music.utils.fill import FILL
|
from v6d3music.utils.fill import FILL
|
||||||
from v6d3music.utils.sparq import sparq
|
from v6d3music.utils.sparq import sparq
|
||||||
from v6d3music.utils.tor_prefix import tor_prefix
|
from v6d3music.utils.tor_prefix import tor_prefix
|
||||||
|
from v6d3music.core.ytaservicing import *
|
||||||
|
|
||||||
from v6d2ctx.context import Explicit
|
from v6d2ctx.context import Explicit
|
||||||
|
|
||||||
__all__ = ('YTAudio',)
|
__all__ = ('YTAudio',)
|
||||||
|
|
||||||
|
|
||||||
semaphore = asyncio.BoundedSemaphore(5)
|
|
||||||
|
|
||||||
|
|
||||||
class YTAudio(discord.AudioSource):
|
class YTAudio(discord.AudioSource):
|
||||||
source: FFmpegNormalAudio
|
source: FFmpegNormalAudio
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
caching: Caching,
|
servicing: YTAServicing,
|
||||||
url: str,
|
url: str,
|
||||||
origin: str,
|
origin: str,
|
||||||
description: str,
|
description: str,
|
||||||
@ -35,7 +32,7 @@ class YTAudio(discord.AudioSource):
|
|||||||
*,
|
*,
|
||||||
stop_at: int | None = None
|
stop_at: int | None = None
|
||||||
):
|
):
|
||||||
self.caching = caching
|
self.servicing = servicing
|
||||||
self.url = url
|
self.url = url
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.description = description
|
self.description = description
|
||||||
@ -112,8 +109,7 @@ class YTAudio(discord.AudioSource):
|
|||||||
self._durations[url] = (await ap.stdout.read()).decode().strip().split('.')[0]
|
self._durations[url] = (await ap.stdout.read()).decode().strip().split('.')[0]
|
||||||
|
|
||||||
async def update_duration(self):
|
async def update_duration(self):
|
||||||
async with semaphore:
|
await self.servicing.runner.run(self._update_duration())
|
||||||
await self._update_duration()
|
|
||||||
|
|
||||||
def duration(self) -> str:
|
def duration(self) -> str:
|
||||||
duration = self._durations.get(self.url)
|
duration = self._durations.get(self.url)
|
||||||
@ -184,7 +180,7 @@ class YTAudio(discord.AudioSource):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def respawn(cls, caching: Caching, guild: discord.Guild, respawn: dict) -> 'YTAudio':
|
async def respawn(cls, servicing: YTAServicing, guild: discord.Guild, respawn: dict) -> 'YTAudio':
|
||||||
member_id: int | None = respawn['rby']
|
member_id: int | None = respawn['rby']
|
||||||
if member_id is None:
|
if member_id is None:
|
||||||
member = None
|
member = None
|
||||||
@ -197,7 +193,7 @@ class YTAudio(discord.AudioSource):
|
|||||||
except discord.NotFound:
|
except discord.NotFound:
|
||||||
member = None
|
member = None
|
||||||
audio = YTAudio(
|
audio = YTAudio(
|
||||||
caching,
|
servicing,
|
||||||
respawn['url'],
|
respawn['url'],
|
||||||
respawn['origin'],
|
respawn['origin'],
|
||||||
respawn['description'],
|
respawn['description'],
|
||||||
@ -213,7 +209,7 @@ class YTAudio(discord.AudioSource):
|
|||||||
async def regenerate(self):
|
async def regenerate(self):
|
||||||
try:
|
try:
|
||||||
print(f'regenerating {self.origin}')
|
print(f'regenerating {self.origin}')
|
||||||
self.url = await real_url(self.caching, self.origin, True, self.tor)
|
self.url = await real_url(self.servicing.caching, self.origin, True, self.tor)
|
||||||
if hasattr(self, 'source'):
|
if hasattr(self, 'source'):
|
||||||
self.source.cleanup()
|
self.source.cleanup()
|
||||||
self.set_source()
|
self.set_source()
|
||||||
@ -232,7 +228,7 @@ class YTAudio(discord.AudioSource):
|
|||||||
|
|
||||||
def copy(self) -> 'YTAudio':
|
def copy(self) -> 'YTAudio':
|
||||||
return YTAudio(
|
return YTAudio(
|
||||||
self.caching,
|
self.servicing,
|
||||||
self.url,
|
self.url,
|
||||||
self.origin,
|
self.origin,
|
||||||
self.description,
|
self.description,
|
||||||
@ -247,7 +243,7 @@ class YTAudio(discord.AudioSource):
|
|||||||
raise Explicit('already branched')
|
raise Explicit('already branched')
|
||||||
self.stop_at = stop_at = self.already_read + 50
|
self.stop_at = stop_at = self.already_read + 50
|
||||||
audio = YTAudio(
|
audio = YTAudio(
|
||||||
self.caching,
|
self.servicing,
|
||||||
self.url,
|
self.url,
|
||||||
self.origin,
|
self.origin,
|
||||||
self.description,
|
self.description,
|
||||||
|
@ -166,7 +166,7 @@ const aUpdateQueueSetup = async (el) => {
|
|||||||
while (true) {
|
while (true) {
|
||||||
await sleep(2);
|
await sleep(2);
|
||||||
if (queue !== null && queue.queuejson.length > 100) {
|
if (queue !== null && queue.queuejson.length > 100) {
|
||||||
await sleep(queue.queuejson.length / 100);
|
await sleep((queue.queuejson.length - 100) / 200);
|
||||||
}
|
}
|
||||||
const newQueue = await aQueue();
|
const newQueue = await aQueue();
|
||||||
await aUpdateQueueOnce(newQueue, el);
|
await aUpdateQueueOnce(newQueue, el);
|
||||||
|
13
v6d3music/processing/abstractrunner.py
Normal file
13
v6d3music/processing/abstractrunner.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from typing import Any, Coroutine, TypeVar
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ('AbstractRunner',)
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractRunner(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
async def run(self, coro: Coroutine[Any, Any, T]) -> T:
|
||||||
|
raise NotImplementedError
|
@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Union
|
from typing import Any, Coroutine, Generic, TypeVar, Union
|
||||||
|
|
||||||
from v6d3music.processing.yprocess import *
|
from .abstractrunner import AbstractRunner
|
||||||
|
|
||||||
__all__ = ('Job', 'Pool', 'JobDescriptor',)
|
__all__ = ('Job', 'Pool', 'JobDescriptor',)
|
||||||
|
|
||||||
@ -164,7 +164,23 @@ class Working:
|
|||||||
return self.__worker.busy()
|
return self.__worker.busy()
|
||||||
|
|
||||||
|
|
||||||
class Pool:
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
class CoroJD(JobDescriptor, Generic[T]):
|
||||||
|
def __init__(self, coro: Coroutine[Any, Any, T]) -> None:
|
||||||
|
self.future = asyncio.Future()
|
||||||
|
self.coro = coro
|
||||||
|
|
||||||
|
async def run(self) -> JobDescriptor | None:
|
||||||
|
try:
|
||||||
|
self.future.set_result(await self.coro)
|
||||||
|
except BaseException as e:
|
||||||
|
self.future.set_exception(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class Pool(AbstractRunner):
|
||||||
def __init__(self, workers: int) -> None:
|
def __init__(self, workers: int) -> None:
|
||||||
if workers < 1:
|
if workers < 1:
|
||||||
raise ValueError('non-positive number of workers')
|
raise ValueError('non-positive number of workers')
|
||||||
@ -203,6 +219,11 @@ class Pool:
|
|||||||
def workers(self) -> int:
|
def workers(self) -> int:
|
||||||
return self.__workers
|
return self.__workers
|
||||||
|
|
||||||
|
async def run(self, coro: Coroutine[Any, Any, T]) -> T:
|
||||||
|
job = CoroJD(coro)
|
||||||
|
self.submit(job.wrap())
|
||||||
|
return await job.future
|
||||||
|
|
||||||
|
|
||||||
class JDC:
|
class JDC:
|
||||||
def __init__(self, descriptor: JobDescriptor, pool: Pool) -> None:
|
def __init__(self, descriptor: JobDescriptor, pool: Pool) -> None:
|
||||||
|
@ -6,7 +6,7 @@ import time
|
|||||||
|
|
||||||
import discord
|
import discord
|
||||||
from v6d3music.app import AppContext
|
from v6d3music.app import AppContext
|
||||||
from v6d3music.commands import get_of
|
from v6d3music.commands import *
|
||||||
from v6d3music.config import prefix
|
from v6d3music.config import prefix
|
||||||
from v6d3music.core.caching import *
|
from v6d3music.core.caching import *
|
||||||
from v6d3music.core.default_effects import *
|
from v6d3music.core.default_effects import *
|
||||||
@ -54,8 +54,8 @@ def message_allowed(message: discord.Message) -> bool:
|
|||||||
return guild_allowed(message.guild)
|
return guild_allowed(message.guild)
|
||||||
|
|
||||||
|
|
||||||
def register_handlers(mainservice: MainService, defaulteffects: DefaultEffects):
|
def register_handlers(mainservice: MainService):
|
||||||
of = get_of(mainservice, defaulteffects)
|
of = get_of(mainservice)
|
||||||
|
|
||||||
@client.event
|
@client.event
|
||||||
async def on_message(message: discord.Message) -> None:
|
async def on_message(message: discord.Message) -> None:
|
||||||
@ -138,11 +138,11 @@ def _db_ee() -> contextlib.ExitStack:
|
|||||||
async def main():
|
async def main():
|
||||||
async with (
|
async with (
|
||||||
DefaultEffects() as defaulteffects,
|
DefaultEffects() as defaulteffects,
|
||||||
MainService(client) as mainservice,
|
MainService(defaulteffects, client) as mainservice,
|
||||||
AppContext(mainservice),
|
AppContext(mainservice),
|
||||||
ABlockMonitor(delta=0.5)
|
ABlockMonitor(delta=0.5)
|
||||||
):
|
):
|
||||||
register_handlers(mainservice, defaulteffects)
|
register_handlers(mainservice)
|
||||||
if 'guerilla' in sys.argv:
|
if 'guerilla' in sys.argv:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
tokenpath = Path('.token.txt')
|
tokenpath = Path('.token.txt')
|
||||||
|
@ -4,6 +4,7 @@ from v6d3music.utils.effects_for_preset import effects_for_preset
|
|||||||
from v6d3music.utils.entries_for_url import entries_for_url
|
from v6d3music.utils.entries_for_url import entries_for_url
|
||||||
from v6d3music.utils.options_for_effects import options_for_effects
|
from v6d3music.utils.options_for_effects import options_for_effects
|
||||||
from v6d3music.utils.sparq import sparq
|
from v6d3music.utils.sparq import sparq
|
||||||
|
from v6d2ctx.context import Explicit
|
||||||
|
|
||||||
__all__ = ('InfoCtx', 'UrlCtx', 'ArgCtx',)
|
__all__ = ('InfoCtx', 'UrlCtx', 'ArgCtx',)
|
||||||
|
|
||||||
@ -61,14 +62,16 @@ class ArgCtx:
|
|||||||
case [*args]:
|
case [*args]:
|
||||||
pass
|
pass
|
||||||
ctx.already_read = round(seconds / sparq(options_for_effects(effects)))
|
ctx.already_read = round(seconds / sparq(options_for_effects(effects)))
|
||||||
|
while True:
|
||||||
match args:
|
match args:
|
||||||
case ['tor', *args]:
|
case ['tor', *args]:
|
||||||
|
if ctx.tor:
|
||||||
|
raise Explicit('duplicate tor')
|
||||||
ctx.tor = True
|
ctx.tor = True
|
||||||
case [*args]:
|
|
||||||
pass
|
|
||||||
match args:
|
|
||||||
case ['ignore', *args]:
|
case ['ignore', *args]:
|
||||||
|
if ctx.ignore:
|
||||||
|
raise Explicit('duplicate ignore')
|
||||||
ctx.ignore = True
|
ctx.ignore = True
|
||||||
case [*args]:
|
case [*args]:
|
||||||
pass
|
break
|
||||||
self.sources.append(ctx)
|
self.sources.append(ctx)
|
||||||
|
@ -2,6 +2,8 @@ from typing import Iterable
|
|||||||
|
|
||||||
from v6d2ctx.context import Context, Implicit
|
from v6d2ctx.context import Context, Implicit
|
||||||
|
|
||||||
|
__all__ = ('catch',)
|
||||||
|
|
||||||
|
|
||||||
async def catch(ctx: Context, args: list[str], reply: str, *catched: (Iterable[str] | str), attachments_ok=True):
|
async def catch(ctx: Context, args: list[str], reply: str, *catched: (Iterable[str] | str), attachments_ok=True):
|
||||||
if ctx.message.attachments and attachments_ok:
|
if ctx.message.attachments and attachments_ok:
|
||||||
|
@ -3,6 +3,9 @@ from v6d2ctx.context import Explicit
|
|||||||
from v6d3music.utils.presets import presets
|
from v6d3music.utils.presets import presets
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ('effects_for_preset',)
|
||||||
|
|
||||||
|
|
||||||
def effects_for_preset(preset: str) -> str:
|
def effects_for_preset(preset: str) -> str:
|
||||||
if preset in presets:
|
if preset in presets:
|
||||||
return presets[preset]
|
return presets[preset]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import shlex
|
import shlex
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
__all__ = ('options_for_effects',)
|
||||||
|
|
||||||
|
|
||||||
def options_for_effects(effects: str | None) -> Optional[str]:
|
def options_for_effects(effects: str | None) -> Optional[str]:
|
||||||
return f'-af {shlex.quote(effects)}' if effects else None
|
return f'-af {shlex.quote(effects)}' if effects else None
|
||||||
|
Loading…
Reference in New Issue
Block a user