[PYTHON] Une histoire qui a permis de créer automatiquement une liste de lecture Anison à partir de vos fichiers musicaux

introduction

J'ai pris de la musique (principalement anison) à partir d'un CD et je l'ai écoutée avec un baladeur, mais avant de le savoir, mes fichiers dépassaient 8000 chansons. Même si je pense créer une liste de lecture, ** cela prend beaucoup de temps pour ajouter mon anison préféré à la liste de lecture **, vérifier les informations de lien dans la liste des artistes, ajouter des chansons, etc. ** Les vacances sont terminées **. (Je ne pense pas que ce soit pertinent pour quiconque utilise Spotify, Apple Music, etc.) Ce serait bien si je pouvais apprécier ce genre de travail, mais comme j'étais fatigué de créer des listes de lecture, j'ai trouvé que ** je n'étais pas adapté pour le travail de création de listes de lecture en premier lieu **. Heureusement, j'ai réalisé que ** je ne peux pas créer de listes de lecture, mais que je peux écrire des programmes **, j'ai donc créé un programme qui crée automatiquement des listes de lecture Anison, mais il y avait beaucoup de choses qui ne fonctionnaient pas, alors j'ai partagé des informations. J'ai également écrit un article.

Mis en œuvre

Bien que l'introduction soit devenue longue, le programme créé cette fois-ci est un programme qui crée une ** playlist anison (fichier m3u) ** à partir de ** fichiers musicaux existants (fichiers m4a, mp3, flac) **. Le contenu du processus est

  1. Obtenez des informations d'anison à partir du fichier csv
  2. Obtenez des informations sur les chansons à partir de fichiers musicaux stockés sur votre PC
  3. Comparez les données enregistrées en 1. et 2. et sortez les données correspondantes sous forme de playlist (fichier m3u). J'ai également créé l'interface graphique suivante avec une interface simple. (La partie GUI sera omise dans cet article.) apg_ui.png

Si vous spécifiez un chemin arbitraire et appuyez sur le bouton d'exécution, l'anison sauvegardée sur l'ordinateur personnel sera extraite et le fichier de liste de lecture sera sorti. Il est également possible de créer une liste de lecture en spécifiant une animation spécifique en spécifiant le nom de l'œuvre. Le code source et le fichier exécutable (pour Windows) se trouvent sur GitHub, veuillez donc les vérifier également.

Flux de programme

Le code de base du générateur de playlist est apg.py dans le code publié sur github. J'ai également implémenté le code GUI apg_gui.py implémenté dans PyQt5, mais comme cela n'appelle que la classe définie dans apg.py, vous pouvez créer une liste de lecture avec juste apg.py. Dans apg.py, ** A ** nison ** P ** laylist ** G ** énerator est défini comme la classe APG, et il a des fonctions correspondant à 1 à 3 de l'implémentation ci-dessus. Je vais. Les détails de chaque processus sont expliqués ci-dessous.

Obtenir des informations Anison à partir du fichier csv

La fonction MakeAnisonDatabase dans apg.py est expliquée. Ici, les informations d'Anison sont enregistrées dans la base de données. La raison d'utiliser la base de données est qu'il est inutile d'acquérir les données d'Anison et la musique disponible chaque fois que vous créez une liste de lecture, et une fois que vous créez la base de données, vous pouvez réduire le temps nécessaire pour créer la deuxième liste de lecture et les suivantes. C'est le résultat de la réflexion. J'ai juste besoin de le déplacer, donc je l'implémente sans réfléchir profondément à la structure de la base de données (même si je n'ai tout simplement pas connaissance de la base de données ...). La bibliothèque utilisée est sqlite3. Le code spécifique est le suivant. Tout d'abord, récupérez le chemin du fichier csv DL à partir d'Anison Generation. Il y a trois fichiers zip dans DL, et ils sont enregistrés dans anison.csv pour l'animation, game.csv pour les jeux et sf.csv pour les effets spéciaux (le but initial est l'animation, mais c'est un gros problème J'utilise aussi des jeux et des effets spéciaux). Si vous placez le dossier décompressé dans le dossier de données, ce sera comme suit.


./data/ --- anison/ -- anison.csv #Anime
         |          └ readme.txt
         ├ game/ --- game.csv    #Jeu
         |         └ readme.txt
         └ sf/--- sf.csv         #Effets spéciaux
               └ readme.txt

Chaque dossier contient également un fichier texte, mais en exécutant le code suivant, vous ne pouvez obtenir que le fichier csv même si vous placez le dossier décompressé dans ./data tel quel.


path_data = "./data" #Dossier dans lequel le fichier csv téléchargé depuis Anison Generation est enregistré
file_paths = [i for i in glob.glob(path_data + "/**", recursive=True) if os.path.splitext(i)[-1] == ".csv"] #Obtenez uniquement le chemin du fichier csv de manière récursive

Ensuite, enregistrez les informations dans le fichier csv dans la base de données. Comme il n'y a que trois fichiers csv cibles, anison.csv, game.csv et sf.csv, ne lisez pas les autres fichiers. Le code suivant crée des tables appelées anison, game et sf et enregistre 1000 lignes dans la base de données. (J'ai fait référence à un site, mais je pense que cela ne change pas avec ou sans lui.)


data_name = ["anison.csv", "game.csv", "sf.csv"] #Le nom du fichier csv téléchargé depuis Anison Generation
for file_path in file_paths:
       if os.path.basename(file_path) in data_name: #Le nom du fichier csv est anison.csv, game.csv, sf.Si l'un des csv
            category = os.path.splitext(os.path.basename(file_path))[0]

            # anison, game,Créer une table nommée sf
            with sqlite3.connect(self.path_database)as con, open(file_path, "r", encoding="utf-8") as f:
                cursor = con.cursor()
                #Créer si la table n'existe pas
                cursor.execute("CREATE TABLE IF NOT EXISTS '%s'('%s', '%s', '%s', '%s', '%s', '%s')" % (category, "artist", "title", "anime", "genre", "oped", "order"))

                command = "INSERT INTO " + category + " VALUES(?, ?, ?, ?, ?, ?)" #Définition de l'instruction SQL
                
                lines = f.readlines() #Lire le fichier csv
                buffer = []           #Variables d'enregistrement des données collectivement
                buffer_size = 1000    #Enregistrez 100 articles à la fois

                for i, line in tqdm(enumerate(lines[1:])): #Lire le fichier csv ligne par ligne
                   *keys, = line.split(",")  #Obtenez la clé pour chaque ligne
                    #Obtenez le nom du chanteur, le nom de la chanson, l'ordre de diffusion, le type OPED, le nom de l'anime, le genre comme clés
                    artist, title, order, oped, anime, genre = trim(keys[7]), trim(keys[6]), trim(keys[4]), trim(keys[3]), trim(keys[2]), trim(keys[1]) 
                        
                    buffer.append([artist, title, anime, genre, oped, order])

                    if i%buffer_size == 0 or i == len(lines) - 1:
                        cursor.executemany(command, buffer) #Exécution SQL
                        buffer = []
                

                #Supprimer les données enregistrées en double
                cursor.executescript("""
                    CREATE TEMPORARY TABLE tmp AS SELECT DISTINCT * FROM """ + category + """;
                    DELETE FROM """ + category + """;
                    INSERT INTO """ + category + """ SELECT * FROM tmp;
                    DROP TABLE tmp;
                    """)

                con.commit()

La fonction de découpage est une fonction qui remplace les caractères qui ne sont pas souhaitables lors de la création d'une base de données ou d'une liste de lecture par d'autres caractères, et efface les chaînes de caractères telles que les sauts de ligne et les virgules. En outre, il y avait un problème lors de la création d'une liste de lecture, même avec la différence entre pleine largeur et demi-largeur, donc tous les caractères pouvant être convertis en caractères demi-largeur dans la bibliothèque mojimoji ont été modifiés en caractères demi-largeur.


import mojimoji as moji

def trim(name):
    name = name.replace("\n", "").replace('\'', '_').replace(" ", "").replace("\x00", "").replace("\"", "")
    name = moji.zen_to_han(name)
    return name.lower()

Ensuite, récupérez les informations du fichier musical enregistré sur le PC et enregistrez-le dans la base de données.

Obtenez des informations sur les chansons à partir de fichiers musicaux stockés sur votre PC

Le format du fichier m3u, qui est un fichier de liste de lecture, est le suivant, et la longueur de la chanson, le titre de la chanson et le chemin d'accès au fichier de musique sont nécessaires pour chaque chanson.

 #EXTM3U
 #EXTINF:Durée de la chanson, titre de la chanson
Chemin absolu vers le fichier
 #EXTINF:Durée de la chanson, titre de la chanson
Chemin absolu vers le fichier
        :

Dans la fonction MakeMusiclibrary, le chemin du fichier de musique est acquis et le fichier de musique est enregistré dans la base de données de la même manière que les données d'anison. Le processus d'acquisition des données d'anison a changé en un fichier musical, mais le processus de base n'est pas très différent du processus de ↑. J'ai défini la fonction suivante pour obtenir les informations du fichier musical.

from mutagen.flac import FLAC
from mutagen.mp3 import MP3
from mutagen.mp4 import MP4

def getMusicInfo(path):
    length, audio, title, artist = 0, "", "", ""
    
    if path.endswith(".flac"):
        audio = FLAC(path)
        artist = trim(audio.get('artist', [""])[0])
        title = trim(audio.get('title', [""])[0])
        length = audio.info.length

     elif path.endswith(".mp3"):
        audio = EasyID3(path)
        artist = trim(audio.get('artist', [""])[0])
        title = trim(audio.get('title', [""])[0])
        length = MP3(path).info.length
        
    elif path.endswith(".m4a"):
        audio = MP4(path)
        artist = trim(audio.get('\xa9ART', [""])[0])
        title = trim(audio.get('\xa9nam', [""])[0])
         length = audio.info.length
        
    return audio, artist, title, length

La fonction getMusicInfo obtient le titre de la chanson, le nom du chanteur et la longueur de la chanson à partir du chemin du fichier. Les fichiers qui enregistrent la bibliothèque musicale dans la base de données sont les suivants. Pour le nom et le titre du chanteur, les caractères peu pratiques ont été convertis avec la fonction de découpage.


    def makeLibrary(path_music):
        music_files = glob.glob(path_music + "/**", recursive=True) #Récupérez tous les fichiers de la bibliothèque
        
        #Créez une table nommée bibliothèque et enregistrez des fichiers musicaux
        with sqlite3.connect(self.path_database) as con:
            cursor = con.cursor()
            #S'il n'y a pas de table de bibliothèque, créez-la
            cursor.execute("CREATE TABLE IF NOT EXISTS library(artist, title, length, path)")
            #Instruction SQL
            command = "INSERT INTO library VALUES(?, ?, ?, ?)"

            buffer = []
            for i, music_file in tqdm(enumerate(music_files)):
                audio, artist, title, length = getMusicInfo(music_file) #Obtenir des informations sur les fichiers musicaux à partir du chemin
                
                if audio != "":

                    buffer.append(tuple([trim(artist), trim(title), length, music_file]))

                    if i % 1000 == 0 or i == len(music_files) - 1:
                        cursor.executemany(command, buffer)
                        buffer = []
          
            cursor.executescript("""
                CREATE TEMPORARY TABLE tmp AS SELECT DISTINCT * FROM library;
                DELETE FROM library;
                INSERT INTO library SELECT * FROM tmp;
                DROP TABLE tmp;
                """)
            
            con.commit()

Ensuite, j'expliquerai la fonction qui génère une liste de lecture en utilisant les données jusqu'à présent.

Liste de lecture de sortie (fichier .m3u)

L'explication ici concerne la fonction generatePlaylist dans apg.py (bien que j'aie omis diverses choses pour l'explication ...). Tout d'abord, le nom du chanteur est obtenu à partir des informations d'anison enregistrées dans la base de données. L'instruction SQL et la partie exécution sont les suivantes. category est un nom de table qui peut être anison, game ou sf. Remplacez la liste acquise des noms de chanteurs dans une variable appropriée. (L'accès à la base de données est le même que le programme ci-dessus, il est donc omis)

cursor.execute("SELECT DISTINCT artist FROM '%s'" % category)
artist_db = cursor.fetchall()

De même, obtenez le nom du chanteur dans la bibliothèque musicale.


cursor.execute('SELECT artist FROM library')
artist_lib = sorted(set([artist[0] for artist in cursor.fetchall()]))

Ensuite, découvrez si le nom du chanteur dans la bibliothèque musicale est dans la liste des noms du chanteur dans la base de données anison, c'est-à-dire si le chanteur de la bibliothèque musicale a peut-être chanté anison. Je pense que c'est une culture propre à Anison, mais lorsque la musique est importée d'un CD, le nom du chanteur peut être le nom du personnage (cv. Nom de l'acteur vocal). D'autre part, la liste des chanteurs dans la base de données Anison est (probablement) enregistrée par nom d'acteur vocal, il n'est donc pas possible de rechercher par correspondance exacte du nom du chanteur. Par conséquent, nous avons examiné la similitude entre le chanteur dans la bibliothèque musicale et le chanteur dans la base de données anison, et avons sélectionné le nom du chanteur avec la plus grande similitude et la similitude maximale dépassant le seuil en tant que chanteur qui peut chanter anison. Dans le code (apg.py), cela correspond à la partie suivante.

#Pour tous les artistes de la bibliothèque
for i, artist in artist_lib:
    #Calculer la similitude des noms d'artistes
    similarities = [difflib.SequenceMatcher(None, artist, a[0]).ratio() for a in artist_db]

    if th_artist < max(similarities): #Lorsque la valeur maximale de similitude est supérieure au seuil
        #Obtenez toutes les informations sur la chanson de l'artiste cible
        info_list = self.getInfoDB('SELECT * FROM library WHERE artist LIKE \'' + artist + '\'', cursor)

Après avoir identifié le chanteur, récupérez toutes les informations sur la chanson de ce chanteur dans la bibliothèque musicale et recherchez les chansons dans la bibliothèque musicale d'Anison. Le fait que les correspondances exactes ne peuvent pas être utilisées pour les noms de chanteurs signifie également que les correspondances exactes ne peuvent pas être utilisées pour les titres de chansons. Par exemple, une chanson telle qu'un nom de chanson (version d'album) est une correspondance exacte et ne sera pas recherchée. Par conséquent, la liste de musique vérifie également la similitude.


        #C'est une continuation de l'instruction if de ↑
        #Obtenez toutes les chansons du chanteur avec le plus haut degré de similitude de la base de données Anison
        cursor.execute("SELECT DISTINCT * FROM '%s' WHERE artist LIKE \'%%%s%%\'" % (category, artist_db[similarities.index(max(similarities))][0]))
        title_list = cursor.fetchall() #Liste des chansons d'un artiste spécifique incluses dans la base de données Anison

        for info in info_list: #Découvrez si toutes les chansons de votre bibliothèque musicale sont anison
            artist, title, length, path = info[0], info[1], info[2], info[3]                         

            title_ratio = [difflib.SequenceMatcher(None, title, t[1]).ratio() for t in title_list] #Calculez la similitude avec les chansons de la base de données Anison
 
            if th_title < max(title_ratio): #Anison si la similitude est au-dessus du seuil
                t = title_list[title_ratio.index(max(title_ratio))]
                lines.append(['#EXTINF: ' + str(int(length)) + ', ' + title + "\n" + path, t[-1]]) # .Variables pour la sortie du fichier m3u(liste)Ajouter à

Après avoir calculé la similitude de tous les artistes et la similitude des chansons, écrivez les informations de l'anison contenues dans les lignes variables dans un fichier.

path_playlist = "./playlist/AnimeSongs.m3u"

with open(path_playlist, 'w', encoding='utf-16') as pl:
    pl.writelines('#EXTM3U \n')
    pl.writelines("\n".join([line[0] for line in lines]))

Vous avez maintenant créé une liste de lecture Anison. Pour des raisons d'explication, il existe quelques différences par rapport au code publié sur github, donc si vous êtes intéressé, veuillez vérifier apg.py sur github.

problème

En créant trois fonctions, il est possible de créer une liste de lecture pour le moment, mais je voudrais aborder certains des problèmes survenus dans cette implémentation.

Les chansons qui ne sont pas anison sont également ajoutées en tant qu'anison

Actuellement, il existe un problème en ce que les chansons qui ne sont pas anison sont également incluses dans la liste de lecture lors de la création de la liste de lecture. Par exemple, si vous définissez l'animation BLEACH comme titre de votre travail, en plus de D-technolife d'UVERworld, une chanson très similaire appelée D-technorize sera ajoutée à la playlist. En plus de Re: I am d'Aimer, si vous essayez de créer une liste de lecture Gundam UC, les chansons Re: far et Re: prier seront également incluses dans la liste de lecture Gundam UC. Dans le cas d'un tel travail seul, il peut être traité en élevant le seuil de similitude des chansons, mais comme le seuil des chansons diffère selon l'animation, il est actuellement difficile de créer une playlist d'anison qui ne se limite pas à une animation spécifique. Il est devenu. Je pense que c'est lié aux problèmes suivants, mais si je trouve une bonne méthode, j'aimerais l'améliorer.

La génération de la playlist est lente

Actuellement, la base de données est devenue simplement un emplacement de stockage temporaire pour les données, et à la fin, elle sera traitée avec l'instruction for. Si je peux écrire SQL plus intelligemment, je pense que le temps de création de la playlist sera réduit, donc j'aimerais réécrire le code lié à la base de données à l'avenir.

Résumé

C'était trop difficile de créer une liste de lecture Anison, j'ai donc créé un logiciel qui crée automatiquement une liste de lecture Anison. Il y a encore des problèmes, mais je pense que nous avons créé des logiciels avec les fonctions minimales. En regardant en arrière, le résultat était que ** le temps passé à coder dépassait de loin le temps nécessaire pour créer une liste de lecture régulièrement **, mais j'aimerais pouvoir sauver des personnes ayant des problèmes similaires. pensée. Enfin, si vous avez des erreurs ou des améliorations (en particulier concernant le traitement lié à la base de données), je vous serais reconnaissant de bien vouloir commenter. Merci d'avoir lu jusqu'au bout.

Journal des modifications

2020/03/04 publié

Recommended Posts

Une histoire qui a permis de créer automatiquement une liste de lecture Anison à partir de vos fichiers musicaux
Une histoire qui a permis de créer automatiquement une liste de lecture Anison à partir de vos fichiers musicaux
J'ai créé un outil qui facilite un peu la création et l'installation d'une clé publique.
J'ai créé un outil pour créer un nuage de mots à partir de wikipedia
J'ai créé un outil pour générer automatiquement un simple diagramme ER à partir de l'instruction CREATE TABLE
Une histoire à laquelle j'étais accro à appeler Lambda depuis AWS Lambda.
Créez un outil qui secoue automatiquement furigana avec html en utilisant Mecab de Python3
Rendre possible la lecture de .eml depuis un smartphone à l'aide d'un bot Discord
Une histoire sur l'écriture d'un programme qui résume automatiquement vos propres transitions d'actifs
Une histoire qui a souffert d'une différence de système d'exploitation lors de la tentative d'implémentation d'un article
Comment créer un clone depuis Github
Comment créer un référentiel à partir d'un média
J'ai créé un système qui décide automatiquement de s'exécuter demain avec Python et l'ajoute à Google Agenda.
[Python] J'ai créé un script qui coupe et colle automatiquement les fichiers du PC local sur un SSD externe.
[Django] Créez un formulaire qui remplit automatiquement l'adresse à partir du code postal
Une histoire à laquelle j'étais accro après la communication SFTP avec python
J'ai créé un package pour créer un fichier exécutable à partir du code source Hy
Modifier Excel à partir de Python pour créer un tableau croisé dynamique
Comment créer un objet fonction à partir d'une chaîne
Créer automatiquement un fichier de configuration Ansible (vars) à partir d'Excel (Ansible)
Une histoire qui a échoué lors de la tentative de suppression du suffixe d'une chaîne avec rstrip
J'ai créé et publié une image Docker qui lit RSS et tweete automatiquement régulièrement.
Un système simple qui prend automatiquement des photos avec détection d'objet et l'envoie à LINE