Discord Bot avec fonction d'enregistrement commençant par Python: (4) Lire des fichiers musicaux

introduction

Cet article est une continuation du précédent Discord Bot avec fonction d'enregistrement commençant par Python: (3) Coopération avec la base de données.

Cette fois, nous allons ajouter une fonction juke-box qui joue la musique préparée à l'avance sur le serveur avec une commande. Si vous utilisez discord.py, vous pouvez lire de l'audio avec un processus très simple. Ici, nous allons ajouter une fonction qui permet la lecture continue en implémentant une file d'attente de chansons au lieu de simplement la jouer.

Nous prévoyons d'écrire un total de 7 fois et avons fini d'écrire jusqu'à 5 articles.

  1. Discord Bot avec fonction d'enregistrement commençant par Python: (1) Mise en route discord.py
  2. Discord Bot avec fonction d'enregistrement commençant par Python: (2) Fonction pratique (extension Bot, Cog, Embed)
  3. Discord Bot avec fonction d'enregistrement commençant par Python: (3) Coopération avec la base de données
  4. Discord Bot avec fonction d'enregistrement commençant par Python: (4) Lire des fichiers musicaux
  5. Discord Bot avec fonction d'enregistrement commençant par Python: (5) Exploitez directement l'API Discord

Communication vocale Discord

Dans discord.py, c'est une partie qui n'est pas directement falsifiée, il n'y a donc pas grand chose à savoir, mais en plus de l'API REST utilisée lors de l'envoi de messages avec des requêtes HTTP et de l'obtention d'informations sur le serveur, bidirectionnel Il existe trois méthodes de transmission / réception d'informations: la communication WebSocket pour la communication et la communication RTP pour la transmission / réception de la voix.

Dans le cas de discord.py, c'est un mécanisme très intuitif pour obtenir une instance du canal vocal à partir de Context etc. et se connecter au canal vocal en exécutant le collout connect de cette instance. Ce qui est retourné par ce connect est une instance de la classe VoiceClient.

Dans VoiceClient, les parties ci-dessus telles que la communication WebSocket et d'autres communications cryptées sont masquées. Par conséquent, si vous souhaitez manipuler les informations de canal vocal pour chaque serveur, vous pouvez effectuer diverses opérations sur cette instance VoiceClient.

Lors de l'examen du traitement des canaux vocaux de plusieurs serveurs, un mécanisme de connexion de cette instance au serveur (ID) est nécessaire. Ici, le VoiceClient est exploité à l'aide d'un tableau de dictionnaire pour la gestion comme Implémentation officielle dans le référentiel discord.py. Essayez ensuite de lire la musique localement (sur le serveur exécutant le Bot).

Utiliser VoiceChannel

Jusqu'à ce que vous entriez dans le canal vocal

Comme mentionné ci-dessus, entrer et sortir du canal vocal est très facile. Ici, nous allons créer un rouage appelé «Voice», entrer dans la pièce avec «$ join» et partir avec «$ Leave». Un exemple de mise en œuvre est le suivant.

python:./src/app/dbot/cogs/Voice.py


import discord
from discord.ext import commands
from typing import Dict
from dbot.core.bot import DBot


class Voice(commands.Cog):
    def __init__(self, bot: DBot):
        self.bot = bot
        self.voice_clients: Dict[int, discord.VoiceClient] = {}

    @commands.command()
    async def join(self, ctx: commands.Context):
        #Ne participe pas à Voice Channel
        if not ctx.author.voice or not ctx.author.voice.channel:
            return await ctx.send('Rejoignez d'abord le canal vocal')
        vc = await ctx.author.voice.channel.connect()
        self.voice_clients[ctx.guild.id] = vc

    @commands.command()
    async def leave(self, ctx: commands.Context):
        vc = self.voice_clients.get(ctx.guild.id)
        if vc is None:
            return await ctx.send('Je n'ai pas encore rejoint le canal vocal')
        await vc.disconnect()
        del self.voice_clients[ctx.guild.id]


def setup(bot):
    return bot.add_cog(Voice(bot))

La propriété voice of discord.Member renvoie une instance de la classe VoiceState si le membre est joint à un canal vocal. Ce VoiceState a une propriété appelée «channel» en plus de l'état tel que muet du membre, et en faisant référence à cela, une instance de la classe de canal vocal (discord.VoiceChannel) actuellement dans le membre est obtenue. peut faire.

L'appel du collout connect de VoiceChannel renvoie VoiceClient et le Bot rejoint le canal vocal. Le VoiceClient est stocké dans le dictionnaire en utilisant l'ID du serveur comme clé. Au contraire, «$ Leave» recherche «VoiceClient» à partir de l'ID du serveur et appelle le collout «disconnect». Le processus d'entrée / sortie est maintenant terminé.

Lire l'audio

Tout d'abord, préparez la musique à lire et enregistrez-la dans le dossier . / Src / app / music /.

Image from Gyazo

Donnez-lui un nom approprié pour l'implémenter afin qu'il recherche en fonction du nom. Pour le moment, jouez avec «$ play song title» et arrêtez avec «$ stop».

python:./src/app/dbot/cogs/Voice.py


import discord
from glob import glob
import os
from discord.ext import commands
from typing import Dict
from dbot.core.bot import DBot


class Voice(commands.Cog):
    #Abréviation

    @commands.command()
    async def play(self, ctx: commands.Context, *, title: str = ''):
        vc: discord.VoiceClient = self.voice_clients.get(ctx.guild.id)
        if vc is None:
            await ctx.invoke(self.join)
            vc = self.voice_clients[ctx.guild.id]
        music_pathes = glob('./music/**.mp3')
        music_titles = [
            os.path.basename(path).rstrip('.mp3')
            for path in music_pathes
        ]
        if not title in music_titles:
            return await ctx.send('Il n'y a pas de chanson spécifiée.')
        idx = music_titles.index(title)
        src = discord.FFmpegPCMAudio(music_pathes[idx])
        vc.play(src)
        await ctx.send(f'{title}Jouer')

    @commands.command()
    async def stop(self, ctx: commands.Context):
        vc: discord.VoiceClient = self.voice_clients.get(ctx.guild.id)
        if vc is None:
            return await ctx.send('Bot n'a pas encore rejoint le canal vocal')
        if not vc.is_playing:
            return await ctx.send('Déjà arrêté')
        await vc.stop()
        await ctx.send('Arrêté')

    #Abréviation

Pour lire de la musique à l'aide de duscird.py, vous devez transmettre la source audio au VoiceClient Play Collout. Ffmpeg est requis pour créer AudioSource. Si vous avez un environnement ffmpeg, vous pouvez jouer de la musique très facilement en donnant simplement le chemin d'accès au fichier.

De même, pour arrêter la lecture, appelez le collout vc.stop.

Effectuer une lecture continue

Dans l'implémentation actuelle, la musique change dès que vous appelez $ play. Remplacez cela par une spécification qui ajoute de la musique à la file d'attente lorsque $ play est exécuté et lit la musique suivante lorsque la musique précédente est terminée (ce que l'on appelle la playlist).

Pour l'implémentation de la playlist, utilisez ʻasyncio.Queue et ʻasyncio.Event. Ils sont souvent utilisés pour les implémentations qui attendent qu'un élément soit ajouté à la file d'attente, et pour les implémentations qui attendent qu'un indicateur soit défini ailleurs.

python:./src/app/dbot/cogs/Voice.py


import discord
from glob import glob
import os
import asyncio
from discord.ext import commands
from typing import Dict
from dbot.core.bot import DBot


class AudioQueue(asyncio.Queue):
    def __init__(self):
        super().__init__(100)

    def __getitem__(self, idx):
        return self._queue[idx]

    def to_list(self):
        return list(self._queue)

    def reset(self):
        self._queue.clear()


class AudioStatus:
    def __init__(self, ctx: commands.Context, vc: discord.VoiceClient):
        self.vc: discord.VoiceClient = vc
        self.ctx: commands.Context = ctx
        self.queue = AudioQueue()
        self.playing = asyncio.Event()
        asyncio.create_task(self.playing_task())

    async def add_audio(self, title, path):
        await self.queue.put([title, path])

    def get_list(self):
        return self.queue.to_list()

    async def playing_task(self):
        while True:
            self.playing.clear()
            try:
                title, path = await asyncio.wait_for(self.queue.get(), timeout=180)
            except asyncio.TimeoutError:
                asyncio.create_task(self.leave())
            src = discord.FFmpegPCMAudio(path)
            self.vc.play(src, after=self.play_next)
            await self.ctx.send(f'{title}Jouer...')
            await self.playing.wait()

    def play_next(self, err=None):
        self.playing.set()

    async def leave(self):
        self.queue.reset()
        if self.vc:
            await self.vc.disconnect()
            self.vc = None

    @property
    def is_playing(self):
        return self.vc.is_playing()

    def stop(self):
        self.vc.stop()


class Voice(commands.Cog):
    def __init__(self, bot: DBot):
        self.bot = bot
        self.audio_statuses: Dict[int, AudioStatus] = {}

    @commands.command()
    async def join(self, ctx: commands.Context):
        #Ne participe pas à Voice Channel
        if not ctx.author.voice or not ctx.author.voice.channel:
            return await ctx.send('Rejoignez d'abord le canal vocal')
        vc = await ctx.author.voice.channel.connect()
        self.audio_statuses[ctx.guild.id] = AudioStatus(ctx, vc)

    @commands.command()
    async def play(self, ctx: commands.Context, *, title: str = ''):
        status = self.audio_statuses.get(ctx.guild.id)
        if status is None:
            await ctx.invoke(self.join)
            status = self.audio_statuses[ctx.guild.id]
        music_pathes = glob('./music/**.mp3')
        music_titles = [
            os.path.basename(path).rstrip('.mp3')
            for path in music_pathes
        ]
        if not title in music_titles:
            return await ctx.send('Il n'y a pas de chanson spécifiée.')
        idx = music_titles.index(title)
        await status.add_audio(title, music_pathes[idx])
        await ctx.send(f'{title}A été ajouté à la playlist')

    @commands.command()
    async def stop(self, ctx: commands.Context):
        status = self.audio_statuses.get(ctx.guild.id)
        if status is None:
            return await ctx.send('Bot n'a pas encore rejoint le canal vocal')
        if not status.is_playing:
            return await ctx.send('Déjà arrêté')
        await status.stop()
        await ctx.send('Arrêté')

    @commands.command()
    async def leave(self, ctx: commands.Context):
        status = self.audio_statuses.get(ctx.guild.id)
        if status is None:
            return await ctx.send('Je n'ai pas encore rejoint le canal vocal')
        await status.leave()
        del self.audio_statuses[ctx.guild.id]

    @commands.command()
    async def queue(self, ctx: commands.Context):
        status = self.audio_statuses.get(ctx.guild.id)
        if status is None:
            return await ctx.send('Rejoignez d'abord le canal vocal')
        queue = status.get_list()
        songs = ""
        for i, (title, _) in enumerate(queue):
            songs += f"{i+1}. {title}\n"
        await ctx.send(songs)


def setup(bot):
    return bot.add_cog(Voice(bot))

Créez une nouvelle classe appelée ʻAudioStatus` pour enregistrer ensemble VoiceClient et les informations du serveur, et enregistrez l'instance.

ʻAudioStatus appelle une fonction appelée ʻasyncio.create_task à l'initialisation. Comme son nom l'indique, la tâche de jouer de la musique est créée à partir du collout. En faisant cela, un accès asynchrone tel que la réponse aux commandes d'autres serveurs tout en effectuant d'autres traitements du côté des tâches est possible. La propriété playing est ʻasyncio.Event, qui est utilisée lorsque vous voulez définir un drapeau lorsqu'une condition spécifique est remplie et attendre sans rien faire jusqu'à ce que le drapeau soit défini. play_task appelle clear et wait, et play_nextappelleset, mais" met le drapeau à "False" "," attend que le drapeau devienne "True" ", et" C'est un processus de "réglage du drapeau sur" True "". play_next est passé à l'argument after de vc.play, qui donne à l'argument ce que vous voulez faire lorsque la lecture d'une chanson est terminée. En définissant le drapeau de lecture du morceau suivant à la fin du morceau, la boucle play_task` recommencera.

La propriété queue de cet ʻAudioStatus hérite de ʻasyncio.Queue et facilite l'obtention de la liste des chansons, mais cette ʻasyncio.Queue n'a aucune valeur à renvoyer lors de l'appel get. Attend là que de nouveaux éléments soient ajoutés à la volée. Cela permet de définir un temps d'attente pour l'entrée de morceau. Et s'il n'y a pas d'entrée pendant 3 minutes, il appelle le collout Leave` et quitte automatiquement le canal vocal.

Image from Gyazo

Cela permet d'ajouter une fonction de lecture de musique avec un repère de morceau.

À la fin

La fonction de lecture de musique est maintenant implémentée! Je suis content que ce soit facile! La fonction d'enregistrement vocal est tout aussi simple, n'est-ce pas? ?? ??

Il semble que cela ne puisse pas être dit, donc pour préparer la mise en œuvre de la fonction d'enregistrement, nous examinerons de plus près comment la fonction API de Discord est gérée en la touchant directement et en l'implémentant sans utiliser discord.py. ..

Recommended Posts

Discord Bot avec fonction d'enregistrement commençant par Python: (4) Lire des fichiers musicaux
Discord Bot avec fonction d'enregistrement commençant par Python: (3) Coopération avec la base de données
Discord Bot avec fonction d'enregistrement commençant par Python: (1) Introduction discord.py
Lire un fichier audio à partir de Python avec interruption
Discord bot raspberry pi zéro avec python [Note]
GRPC commençant par Python
Comment faire fonctionner l'API Discord avec Python (enregistrement de bot)
Créez un bot Mastodon avec une fonction pour répondre automatiquement avec Python
Un débutant en Python lance Discord Bot
Apprentissage amélioré à partir de Python
Tri des fichiers image avec Python (2)
Trier de gros fichiers avec python
Tri des fichiers image avec Python (3)
[Python] Jouez avec le Webhook de Discord.
Tri des fichiers image avec Python
Intégrez des fichiers PDF avec Python
Lire des fichiers .txt avec Python
Python commençant par Hello world!
Extraire récursivement des fichiers zip avec python
[Python] Fichiers wav POST avec requêtes [POST]
Décrypter les fichiers cryptés avec OpenSSL avec Python 3
Jouons avec Excel avec Python [Débutant]
Gérer les fichiers Excel CSV avec Python
Lire des fichiers en parallèle avec Python
Analyse de données à partir de python (visualisation de données 1)
Analyse de données à partir de python (visualisation de données 2)
Commerce système à partir de Python3: investissement à long terme
Lecture vidéo avec son sur python !! (tkinter / imageio)
[AWS] Utilisation de fichiers ini avec Lambda [Python]
Créer un décorateur de fonction Python avec Class
Jouez des nombres manuscrits avec python, partie 2 (identifier)
"Commerce du système à partir de Python3" lecture du mémo
Fractal pour faire et jouer avec Python
Efficacité commerciale à partir de zéro avec Python
Lancez le bot Discord Python pendant 24 heures.
Décrypter les fichiers cryptés avec openssl depuis python avec openssl
Je veux jouer avec aws avec python
Télécharger des fichiers sur le Web avec Python
Apprendre Python! Comparaison avec Java (fonction de base)
[Easy Python] Lecture de fichiers Excel avec openpyxl
Analyse de données à partir de python (pré-traitement des données-apprentissage automatique)
"Première recherche élastique" commençant par un client python
[Easy Python] Lecture de fichiers Excel avec des pandas
Lire des fichiers audio avec des interruptions à l'aide de PyAudio
Faisons un bot Twitter avec Python!
Si vous voulez créer un bot discord avec python, utilisons un framework