[PYTHON] Jouez en convertissant le corpus de dialectes japonais en DB (8) Ajoutez une fonction de conversion de format de fichier

Ceci est un article sérialisé. Puisque nous avons créé jusqu'à la dernière fois une liste de discours et de discours pour chaque intervenant, nous allons cette fois implémenter la fonction finale "Conversion mutuelle entre Excel et TextGrid sur le site". C'est un mémo de travail pour moi, et je ne pense pas qu'il y ait suffisamment d'explications, mais pardonnez-moi s'il vous plaît.

Comme mentionné précédemment, cette fois, nous avons déjà un script Python qui convertit TextGrid et Excel (nous n'entrerons pas dans les détails), nous allons donc essayer de l'intégrer dans une application Laravel et de l'exécuter sur le serveur.

--Partie 1: Jouer en convertissant le corpus de dialectes japonais en DB (1) Réflexion sur la configuration --Partie 2: Jouer avec le corpus de dialectes japonais en tant que DB (2) Convertir en DB avec SQLite3

Préparation préalable

Cette fois, nous allons enregistrer le fichier dans le stockage sur le serveur, le convertir et créer un mécanisme pour le télécharger, alors commencez par définir les paramètres à ce sujet. Les fichiers téléchargés sont enregistrés dans le dossier storage, mais comme il s'agit du dossier public qui est ouvert au public, il est habituel de créer un lien symbolique de public / storage vers storage / app / public. Vous pouvez le coller vous-même avec la commande artisan ci-dessous [^ symbolic].

[^ symbolic]: Comme cela sera décrit plus loin, les liens symboliques créés localement ne seront pas créés sur Heroku sans autorisation, alors écrivez les instructions nécessaires dans composer.json et créez un lien symbolique au moment de la construction. Vous devez pouvoir le faire.

cmd


php artisan storage:link

Diagramme de transition d'écran

Le diagramme de transition d'écran sera affiché à nouveau. C'est une page.

func_3.png

Routage des composants

Puisqu'il n'y a qu'un seul composant, il n'y a rien de spécial à expliquer.

resources/js/app.js


+ import ConvertComponent from "./components/ConvertComponent";

+        {
+            path: "/convert",
+            name: "convert",
+            component: ConvertComponent
+        }

Créer un composant

Bien qu'il s'agisse d'un seul écran, c'est plus compliqué que la dernière fois car il a de nombreuses fonctions.

resouces/js/components/ConvertComponent.vue


<template>
  <div>
    <form enctype="multipart/form-data">
      <input
        type="file"
        name="file"
        id="fileRef"
        style="display: none"
        @change="fileSelected"
      />
      <div class="input-group">
        <input
          type="text"
          id="fileShow"
          class="form-control"
          placeholder="select file..."
          readonly
        />
        <div class="input-group-append">
          <span class="input-group-btn">
            <button 
              type="button" 
              class="btn btn-outline-success" 
              onclick="fileRef.click()"
            >
              Browse
            </button>
          </span>
          <button 
            type="button" 
            class="btn btn-success" 
            @click="fileUpload"
          >
            Upload
          </button>
        </div>
      </div>
    </form>
    <div class="pt-3">
      <table class="table table-sm table-striped">
        <thead>
          <tr class="thead-dark">
            <th colspan="2">
              <div class="text-center">Liste des fichiers</div>
            </th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="file of files" v-bind:key="file.name">
            <td>
              <span class="pl-3">{{ file.replace("public/", "") }}</span>
            </td>
            <td>
              <div class="text-right">
                <span 
                  class="btn btn-success btn-sm" 
                  @click="toTextgrid(file)" 
                  v-if="file.indexOf('.xls') != -1"
                >
                  to TextGrid
                </span>
                <span 
                  class="btn btn-outline-success btn-sm disabled" 
                  v-else
                >
                  to TextGrid
                </span>
                <span 
                  class="btn btn-success btn-sm" 
                  @click="toExcel(file)" 
                  v-if="file.indexOf('.txt') != -1 || file.indexOf('.TextGrid') != -1"
                >
                  to Excel
                </span>
                <span 
                  class="btn btn-outline-success btn-sm disabled" 
                  v-else
                >
                  to Excel
                </span>
                <a 
                  v-bind:href="'./storage' + file.replace('public', '')" 
                  v-bind:download="file.replace('public', '')"
                >
                  <span class="btn btn-warning btn-sm">
                    download
                  </span>
                </a>
                <span 
                  class="btn btn-danger btn-sm" 
                  @click="deleteFile(file)"
                >
                  delete
                </span>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      files: [],
      uploadingFileInfo: ""
    };
  },
  methods: {
    fileSelected(event) {
      this.uploadingFileInfo = event.target.files[0];
      fileShow.value = fileRef.value.replace("C:\\fakepath\\", "");
    },
    fileUpload() {
      if (this.uploadingFileInfo) {
        const formData = new FormData();
        formData.append("file", this.uploadingFileInfo);
        axios.post("/api/toolkit/upload", formData).then(res => {
          fileRef.value = "";
          fileShow.value = "";
          this.uploadingFileInfo = "";
          this.getFileList();
        });
      } else {
        alert("Veuillez sélectionner le fichier à télécharger");
      }
    },
    getFileList() {
      axios.get("/api/convert/files").then(res => {
        this.files = res.data;
      });
    },
    to_textgrid(path) {
      axios.post("/api/convert/toTextgrid", { filepath: path }).then(() => {
        this.getFileList();
      });
    },
    to_excel(path) {
      axios.post("/api/convert/toExcel", { filepath: path }).then(() => {
        this.getFileList();
      });
    },
    deleteFile(path) {
      axios.post("/api/convert/delete", { filepath: path }).then(() => {
        this.getFileList();
      });
    }
  },
  mounted() {
    this.getFileList();
  }
};
</script>

Formulaire de sélection de fichier

Le formulaire file n'a pas l'air très bien avec le bootstrap seul. Certaines méthodes simples ont été conçues, mais cette fois, je me suis référé au site suivant.

Bouton Convertir

Les boutons [vers TextGrid] et [vers Excel] sont commutés en fonction de l'extension de fichier afin qu'ils soient déclenchés en cliquant uniquement lorsque l'extension est appropriée. Puisqu'il existe un format appelé TextGrid, il n'est pas possible d'utiliser la classification de cas par mimetype, donc il est simplement classé selon que le nom de fichier contient une chaîne de caractères telle que .txt ou .TextGrid [^ valider] ]. La classification de cas elle-même est «v-if» ・ «v-else».

[^ validate]: En fait, il ne s'agit pas d'une vérification d'un tel frontal, mais le fichier d'entrée doit être vérifié correctement côté serveur.

<!-- .txt/.TextGrid affiche le bouton d'activation ci-dessus-->
<span
    class="btn btn-success btn-sm"
    @click="toExcel(file)"
    v-if="file.indexOf('.txt') != -1 || file.indexOf('.TextGrid') != -1"
>
    to Excel
</span>
<!--Sinon, affichez le bouton de désactivation ci-dessous-->
<span
    class="btn btn-outline-success btn-sm disabled"
    v-else
>
    to Excel
</span>

Bouton de téléchargement

Diverses conversions et suppressions sont effectuées en cliquant pour exécuter la fonction, mais seul le téléchargement a un lien direct vers le fichier. Il y a plusieurs façons de télécharger des fichiers depuis le serveur de Laravel, mais la méthode utilisant la façade Storage et response () ne fonctionnait pas (plusieurs pertes) [^ down], donc liez directement. J'ai adopté la méthode du collage.

Si vous obtenez le chemin du fichier par une méthode simple comme décrit plus loin, le chemin sous / storage / app sera retourné (le chemin comme = / public / filename.ext sera retourné), alors remplacez-le de manière appropriée et le lien symbolique. Collez le lien directement dans le précédent (/ public) / storage / filename.ext.

lien de téléchargement


<a 
  v-bind:href="'./storage' + file.replace('public', '')" 
  v-bind:download="file.replace('public', '')"
>
  <span class="btn btn-warning btn-sm">
    download
  </span>
</a>

[^ down]: La résolution du chemin a échoué, une erreur 403 s'est produite et le contenu du fichier s'est empilé dans la réponse POST mais n'a pas pu être téléchargé.

Acheminement vers le contrôleur

Puisque tout est implémenté dans FileController, écrivez le routage dans ʻapi.php`, en considérant le nom de la fonction de manière appropriée.

routes/api.php


+ Route::get('/convert/files', 'FileController@getFileList');
+ Route::post('/convert/upload', 'FileController@upload');
+ Route::post('/convert/e_t', 'FileController@toTextgrid');
+ Route::post('/convert/t_e', 'FileController@toExcel');
+ Route::post('/convert/delete', 'FileController@deleteFile');

Créer un contrôleur

Nous allons implémenter les cinq fonctions que nous avons décidé d'utiliser plus tôt.

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;

class FileController extends Controller{

    //Téléchargez et enregistrez le fichier
    public function upload(Request $request){
        $filename = $request->file('file')->getClientOriginalName();
        $request->file('file')->storeAs('public/',$filename);
    }
    
    //Convertissez Excel en TextGrid et enregistrez
    public function toTextgrid(Request $request) {
        exec("which python", $pythonpath);

        $scriptpath = app_path('Python/excel_to_textgrid.py');
        $filepath = storage_path('app/' . $request->input('filepath'));

        $command = $pythonpath[0] . ' ' . $scriptpath . ' ' . $filepath;
        exec($command);
    }
    
    //Convertir TextGrid en Excel et enregistrer; presque le même que ci-dessus
    public function toExcel(Request $request) {
        exec("which python", $pythonpath);

        $scriptpath = app_path('Python/textgrid_to_excel.py');
        $filepath = storage_path('app/' . $request->input('filepath'));

        $command = $pythonpath[0] . ' ' . $scriptpath . ' ' . $filepath;
        exec($command);
    }

    //Obtenez une liste de fichiers
    public function getFileList(){
        //vrai est.Exclure les fichiers dotfiles tels que gitignore
        $files = Storage::allfiles('public/', true);
        //Le type SplFileInfo est gênant sur javascript, il est donc renvoyé sous forme de chaîne de chemin de fichier (mauvais?)
        $filepaths = explode('#', implode('#', $files));
        return $filepaths;
    }

    //Supprimer le fichier
    public function deleteFile(Request $request){
        $filepath = $request->input('filepath');
        Storage::delete($filepath);
    }
}

Exécuter le script Python

Tant que Python est installé sur votre serveur, vous pouvez exécuter Python avec la commande PHP ʻexec`. Comme nous le verrons plus tard, n'oubliez pas d'installer les modules que vous utilisez avec Python lors de la construction d'Heroku.

Puisque Heroku est un système Linux, [^ dyno], je vais l'écrire avec la commande Linux à l'esprit. Cette fois, je l'ai développé sur Windows 10 sans virtualisation de conteneur, mais comme cet article en traite un peu, il n'y avait pas de gros problème.

[^ dyno]: Heroku utilise Dyno, un conteneur Linux léger qui s'exécute sur une énorme instance d'Amazon EC2.

La procédure à exécuter est simple. Le script utilisé cette fois est "Donnez le chemin du fichier cible, convertissez ce fichier et enregistrez-le dans le même répertoire", donc ** Chemin du fichier d'exécution Python, chemin du script, chemin du fichier cible ** Tout ce que vous avez à faire est de l'obtenir et de créer une commande basée sur celle-ci. Cette fois, le script est placé sous / app / Python, donc vous pouvez obtenir le chemin en toute sécurité en utilisant un assistant de chemin tel que ʻapp_path` (sinon il sera instable contre le désalignement de la route). ).

<?php
//Convertissez Excel en TextGrid et enregistrez
public function toTextgrid(Request $request) {
    //Obtenez le chemin vers python dans l'environnement d'exécution
    //Exec pour Windows cmd("where python", $pythonpath);
    exec("which python", $pythonpath);

    //Obtenez le chemin du script python que vous souhaitez exécuter
    $scriptpath = app_path('Python/excel_to_textgrid.py');

    //Obtenez le chemin du fichier POSTé et convertissez-le en chemin relatif approprié
    $filepath = storage_path('app/' . $request->input('filepath'));

    //Assembler et exécuter des commandes
    //Notez l'index si votre environnement a plusieurs versions de Python
    $command = $pythonpath[0] . ' ' . $scriptpath . ' ' . $filepath;
    exec($command);
}

Le script utilisé ici est défini pour enregistrer le fichier de sortie dans le même répertoire que le fichier d'entrée.

Dessin d'achèvement

Ça devrait ressembler à ça.

/convert convert.png

Points d'amélioration

En plus de la gestion des erreurs mentionnée ci-dessus, ʻexec est un gros problème en termes de sécurité. En général, il est très dangereux de simplement jeter des données qui peuvent être falsifiées par l'utilisateur dans la fonction ʻexec de PHP, donc elles doivent être échappées de manière appropriée. Cette fois, je passe par l'assistant de chemin de Laravel, donc je pense que ça va, mais à moins que vous ne connaissiez le comportement exact de l'assistant, vous devriez faire tout ce qui est en votre pouvoir.

la prochaine fois

Je vais l'élever à Heroku (final).

--Le 9: Lire le corpus de dialectes japonais en tant que DB (9) Deploy with Heroku

Recommended Posts

Jouez en convertissant le corpus de dialectes japonais en DB (8) Ajoutez une fonction de conversion de format de fichier
Jouer en convertissant le corpus de dialectes japonais en DB (4) Décidez de l'image globale du service
Mettez le fichier téléchargé par django dans MinIO
[Python] Un bloc-notes qui traduit et télécharge le fichier ipynb de GitHub en japonais.
Ajouter une fonction pour indiquer la météo d'aujourd'hui au bot slack (fabriqué par python)