stability check
This commit is contained in:
parent
abae781b52
commit
5615271518
@ -60,9 +60,11 @@ presets: {shlex.join(allowed_presets)}
|
|||||||
queue = await mainservice.context(ctx, create=True, force_play=False).queue()
|
queue = await mainservice.context(ctx, create=True, force_play=False).queue()
|
||||||
if attachments:
|
if attachments:
|
||||||
args = ["[[", *(attachment.url for attachment in attachments), "]]"] + args
|
args = ["[[", *(attachment.url for attachment in attachments), "]]"] + args
|
||||||
|
added = 0
|
||||||
async for audio in mainservice.yt_audios(ctx, args):
|
async for audio in mainservice.yt_audios(ctx, args):
|
||||||
queue.append(audio)
|
queue.append(audio)
|
||||||
await ctx.reply("done")
|
added += 1
|
||||||
|
await ctx.reply(f"added track(s): {added}")
|
||||||
|
|
||||||
@at("cancel")
|
@at("cancel")
|
||||||
async def cancel(ctx: Context, _args: list[str]) -> None:
|
async def cancel(ctx: Context, _args: list[str]) -> None:
|
||||||
|
@ -31,7 +31,8 @@ class QueueAudio(discord.AudioSource):
|
|||||||
def update_sources(self):
|
def update_sources(self):
|
||||||
for i in range(PRE_SET_LENGTH):
|
for i in range(PRE_SET_LENGTH):
|
||||||
try:
|
try:
|
||||||
self.queue[i].set_source_if_necessary()
|
audio = self.queue[i]
|
||||||
|
audio.set_source_given_index(i)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ class QueueAudio(discord.AudioSource):
|
|||||||
|
|
||||||
def append(self, audio: YTAudio):
|
def append(self, audio: YTAudio):
|
||||||
if len(self.queue) < PRE_SET_LENGTH:
|
if len(self.queue) < PRE_SET_LENGTH:
|
||||||
audio.set_source_if_necessary()
|
audio.set_source_given_index(len(self.queue))
|
||||||
self.queue.append(audio)
|
self.queue.append(audio)
|
||||||
|
|
||||||
def _popleft(self, audio: YTAudio):
|
def _popleft(self, audio: YTAudio):
|
||||||
|
@ -16,7 +16,7 @@ from v6d3music.utils.options_for_effects import *
|
|||||||
from v6d3music.utils.sparq import *
|
from v6d3music.utils.sparq import *
|
||||||
from v6d3music.utils.tor_prefix import *
|
from v6d3music.utils.tor_prefix import *
|
||||||
|
|
||||||
__all__ = ('YTAudio',)
|
__all__ = ("YTAudio",)
|
||||||
|
|
||||||
|
|
||||||
class YTAudio(discord.AudioSource):
|
class YTAudio(discord.AudioSource):
|
||||||
@ -34,11 +34,14 @@ class YTAudio(discord.AudioSource):
|
|||||||
tor: bool,
|
tor: bool,
|
||||||
/,
|
/,
|
||||||
*,
|
*,
|
||||||
stop_at: int | None = None
|
stop_at: int | None = None,
|
||||||
):
|
):
|
||||||
self.servicing = servicing
|
self.servicing = servicing
|
||||||
self.url = url
|
self.url = url
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
|
self.unstable = False
|
||||||
|
if "https://soundcloud.com/" in self.origin:
|
||||||
|
self.unstable = True
|
||||||
self.description = description
|
self.description = description
|
||||||
self.options = options
|
self.options = options
|
||||||
self.rby = rby
|
self.rby = rby
|
||||||
@ -56,16 +59,23 @@ class YTAudio(discord.AudioSource):
|
|||||||
return {url: duration for url, duration in self._durations.items() if url == self.url}
|
return {url: duration for url, duration in self._durations.items() if url == self.url}
|
||||||
|
|
||||||
def set_source_if_necessary(self):
|
def set_source_if_necessary(self):
|
||||||
if not hasattr(self, 'source'):
|
if not hasattr(self, "source"):
|
||||||
self.set_source()
|
self.set_source()
|
||||||
|
|
||||||
|
def set_source_if_stable(self):
|
||||||
|
if not self.unstable:
|
||||||
|
self.set_source_if_necessary()
|
||||||
|
|
||||||
|
def set_source_given_index(self, index: int):
|
||||||
|
if index:
|
||||||
|
self.set_source_if_stable()
|
||||||
|
else:
|
||||||
|
self.set_source_if_necessary()
|
||||||
|
|
||||||
def set_source(self):
|
def set_source(self):
|
||||||
self.schedule_duration_update()
|
self.schedule_duration_update()
|
||||||
self.source = FFmpegNormalAudio(
|
self.source = FFmpegNormalAudio(
|
||||||
self.url,
|
self.url, options=self.options, before_options=self.before_options(), tor=self.tor
|
||||||
options=self.options,
|
|
||||||
before_options=self.before_options(),
|
|
||||||
tor=self.tor
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_already_read(self, already_read: int):
|
def set_already_read(self, already_read: int):
|
||||||
@ -87,7 +97,7 @@ class YTAudio(discord.AudioSource):
|
|||||||
seconds = round(self.source_seconds())
|
seconds = round(self.source_seconds())
|
||||||
minutes, seconds = divmod(seconds, 60)
|
minutes, seconds = divmod(seconds, 60)
|
||||||
hours, minutes = divmod(minutes, 60)
|
hours, minutes = divmod(minutes, 60)
|
||||||
return f'{hours}:{minutes:02d}:{seconds:02d}'
|
return f"{hours}:{minutes:02d}:{seconds:02d}"
|
||||||
|
|
||||||
def _schedule_duration_update(self):
|
def _schedule_duration_update(self):
|
||||||
self.loop.create_task(self.update_duration())
|
self.loop.create_task(self.update_duration())
|
||||||
@ -99,17 +109,22 @@ class YTAudio(discord.AudioSource):
|
|||||||
url: str = self.url
|
url: str = self.url
|
||||||
if url in self._durations:
|
if url in self._durations:
|
||||||
return
|
return
|
||||||
self._durations.setdefault(url, '')
|
self._durations.setdefault(url, "")
|
||||||
if self.tor:
|
if self.tor:
|
||||||
args = [*tor_prefix()]
|
args = [*tor_prefix()]
|
||||||
else:
|
else:
|
||||||
args = []
|
args = []
|
||||||
args += [
|
args += [
|
||||||
'ffprobe', '-i', url,
|
"ffprobe",
|
||||||
'-show_entries', 'format=duration',
|
"-i",
|
||||||
'-v', 'quiet',
|
url,
|
||||||
'-of', 'default=noprint_wrappers=1:nokey=1',
|
"-show_entries",
|
||||||
'-sexagesimal'
|
"format=duration",
|
||||||
|
"-v",
|
||||||
|
"quiet",
|
||||||
|
"-of",
|
||||||
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
|
"-sexagesimal",
|
||||||
]
|
]
|
||||||
ap = await asyncio.create_subprocess_exec(*args, stdout=asyncio.subprocess.PIPE)
|
ap = await asyncio.create_subprocess_exec(*args, stdout=asyncio.subprocess.PIPE)
|
||||||
code = await ap.wait()
|
code = await ap.wait()
|
||||||
@ -117,14 +132,14 @@ class YTAudio(discord.AudioSource):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
assert ap.stdout is not None
|
assert ap.stdout is not None
|
||||||
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 self._duration_lock:
|
async with self._duration_lock:
|
||||||
await self._do_update_duration()
|
await self._do_update_duration()
|
||||||
|
|
||||||
async def _update_duration_context(self, context: CoroContext):
|
async def _update_duration_context(self, context: CoroContext):
|
||||||
context.events.send(CoroStatusChanged({'ytaudio': 'duration'}))
|
context.events.send(CoroStatusChanged({"ytaudio": "duration"}))
|
||||||
await self._update_duration()
|
await self._update_duration()
|
||||||
|
|
||||||
async def update_duration(self):
|
async def update_duration(self):
|
||||||
@ -134,23 +149,21 @@ class YTAudio(discord.AudioSource):
|
|||||||
duration = self._durations.get(self.url)
|
duration = self._durations.get(self.url)
|
||||||
if duration is None:
|
if duration is None:
|
||||||
self.schedule_duration_update()
|
self.schedule_duration_update()
|
||||||
return duration or '?:??:??'
|
return duration or "?:??:??"
|
||||||
|
|
||||||
def before_options(self) -> str:
|
def before_options(self) -> str:
|
||||||
before_options = ''
|
before_options = ""
|
||||||
if 'https' in self.url:
|
if "https" in self.url and not self.unstable:
|
||||||
before_options += (
|
before_options += (
|
||||||
'-reconnect 1 -reconnect_at_eof 0 -reconnect_streamed 1 -reconnect_delay_max 60 -copy_unknown'
|
" -reconnect 1 -reconnect_at_eof 0 -reconnect_streamed 1 -reconnect_delay_max 60 -copy_unknown"
|
||||||
)
|
)
|
||||||
if self.already_read:
|
if self.already_read:
|
||||||
before_options += (
|
before_options += f" -ss {self.source_seconds()}"
|
||||||
f' -ss {self.source_seconds()}'
|
return before_options.strip()
|
||||||
)
|
|
||||||
return before_options
|
|
||||||
|
|
||||||
def estimated_seconds_duration(self) -> float:
|
def estimated_seconds_duration(self) -> float:
|
||||||
duration = self.duration()
|
duration = self.duration()
|
||||||
_m = re.match(r'(\d+):(\d+):(\d+)', duration)
|
_m = re.match(r"(\d+):(\d+):(\d+)", duration)
|
||||||
if _m is None:
|
if _m is None:
|
||||||
return 0.0
|
return 0.0
|
||||||
else:
|
else:
|
||||||
@ -170,25 +183,21 @@ class YTAudio(discord.AudioSource):
|
|||||||
if self.regenerating:
|
if self.regenerating:
|
||||||
return FILL
|
return FILL
|
||||||
if self.stop_at is not None and self.already_read >= self.stop_at - 1:
|
if self.stop_at is not None and self.already_read >= self.stop_at - 1:
|
||||||
return b''
|
return b""
|
||||||
self.already_read += 1
|
self.already_read += 1
|
||||||
ret: bytes = self.source.read()
|
ret: bytes = self.source.read()
|
||||||
if not ret and (not (droppable := self.source.droppable()) or self.underran()):
|
if not ret and (not (droppable := self.source.droppable()) or self.underran()):
|
||||||
if self.attempts < 5 or random.random() > .1:
|
if self.attempts < 5 or random.random() > 0.1:
|
||||||
self.attempts += 1
|
self.attempts += 1
|
||||||
self.regenerating = True
|
self.regenerating = True
|
||||||
self.loop.create_task(
|
self.loop.create_task(self.regenerate("underran" if droppable else "not droppable"))
|
||||||
self.regenerate(
|
|
||||||
'underran' if droppable else 'not droppable'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return FILL
|
return FILL
|
||||||
else:
|
else:
|
||||||
print(f'dropped {self.origin}')
|
print(f"dropped {self.origin}")
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
if hasattr(self, 'source'):
|
if hasattr(self, "source"):
|
||||||
self.source.cleanup()
|
self.source.cleanup()
|
||||||
|
|
||||||
def can_be_skipped_by(self, member: discord.Member) -> bool:
|
def can_be_skipped_by(self, member: discord.Member) -> bool:
|
||||||
@ -210,65 +219,65 @@ class YTAudio(discord.AudioSource):
|
|||||||
|
|
||||||
def hybernate(self) -> dict:
|
def hybernate(self) -> dict:
|
||||||
return {
|
return {
|
||||||
'url': self.url,
|
"url": self.url,
|
||||||
'origin': self.origin,
|
"origin": self.origin,
|
||||||
'description': self.description,
|
"description": self.description,
|
||||||
'options': self.options,
|
"options": self.options,
|
||||||
'rby': None if self.rby is None else self.rby.id,
|
"rby": None if self.rby is None else self.rby.id,
|
||||||
'already_read': self.already_read,
|
"already_read": self.already_read,
|
||||||
'tor': self.tor,
|
"tor": self.tor,
|
||||||
'stop_at': self.stop_at,
|
"stop_at": self.stop_at,
|
||||||
'durations': self._reduced_durations(),
|
"durations": self._reduced_durations(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def respawn(cls, servicing: YTAServicing, 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
|
||||||
else:
|
else:
|
||||||
member = guild.get_member(member_id)
|
member = guild.get_member(member_id)
|
||||||
if member is None:
|
if member is None:
|
||||||
try:
|
try:
|
||||||
member = await guild.fetch_member(respawn['rby'])
|
member = await guild.fetch_member(respawn["rby"])
|
||||||
guild._add_member(member)
|
guild._add_member(member)
|
||||||
except discord.NotFound:
|
except discord.NotFound:
|
||||||
member = None
|
member = None
|
||||||
audio = YTAudio(
|
audio = YTAudio(
|
||||||
servicing,
|
servicing,
|
||||||
respawn['url'],
|
respawn["url"],
|
||||||
respawn['origin'],
|
respawn["origin"],
|
||||||
respawn['description'],
|
respawn["description"],
|
||||||
respawn['options'],
|
respawn["options"],
|
||||||
member,
|
member,
|
||||||
respawn['already_read'],
|
respawn["already_read"],
|
||||||
respawn.get('tor', False),
|
respawn.get("tor", False),
|
||||||
stop_at=respawn.get('stop_at', None),
|
stop_at=respawn.get("stop_at", None),
|
||||||
)
|
)
|
||||||
audio._durations |= respawn.get('durations', {})
|
audio._durations |= respawn.get("durations", {})
|
||||||
return audio
|
return audio
|
||||||
|
|
||||||
async def regenerate(self, reason: str):
|
async def regenerate(self, reason: str):
|
||||||
try:
|
try:
|
||||||
print(f'regenerating {self.origin} {reason=}')
|
print(f"regenerating {self.origin} {reason=}")
|
||||||
self.url = await real_url(self.servicing.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()
|
||||||
print(f'regenerated {self.origin}')
|
print(f"regenerated {self.origin}")
|
||||||
finally:
|
finally:
|
||||||
self.regenerating = False
|
self.regenerating = False
|
||||||
|
|
||||||
async def pubjson(self, member: discord.Member) -> dict:
|
async def pubjson(self, member: discord.Member) -> dict:
|
||||||
return {
|
return {
|
||||||
'seconds': self.source_seconds(),
|
"seconds": self.source_seconds(),
|
||||||
'timecode': self.source_timecode(),
|
"timecode": self.source_timecode(),
|
||||||
'duration': self.duration(),
|
"duration": self.duration(),
|
||||||
'description': self.description,
|
"description": self.description,
|
||||||
'canbeskipped': self.can_be_skipped_by(member),
|
"canbeskipped": self.can_be_skipped_by(member),
|
||||||
}
|
}
|
||||||
|
|
||||||
def copy(self) -> 'YTAudio':
|
def copy(self) -> "YTAudio":
|
||||||
return YTAudio(
|
return YTAudio(
|
||||||
self.servicing,
|
self.servicing,
|
||||||
self.url,
|
self.url,
|
||||||
@ -280,9 +289,9 @@ class YTAudio(discord.AudioSource):
|
|||||||
self.tor,
|
self.tor,
|
||||||
)
|
)
|
||||||
|
|
||||||
def branch(self) -> 'YTAudio':
|
def branch(self) -> "YTAudio":
|
||||||
if self.stop_at is not None:
|
if self.stop_at is not None:
|
||||||
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.servicing,
|
self.servicing,
|
||||||
|
Loading…
Reference in New Issue
Block a user