Publié à l'origine sur mon blog : https://lazy.bearblog.dev/go-mime-wtf/
Un week-end, j'ai décidé d'écrire un robot Telegram pour automatiser les listes de tâches ou de courses.
L'idée est simple. J'envoie au robot une liste d'articles à acheter et il crée une page Web avec des cases à cocher que je peux cocher tout en tenant mon téléphone dans une main et un panier dans l'autre.
Le bot a été écrit assez rapidement, mais j'ai ensuite voulu expérimenter le modèle Whisper d'OpenAI. J'ai donc décidé d'ajouter la reconnaissance vocale à mon application.
Maintenant, le programme fonctionne comme suit :
[ ] potatoes - 1 kg [ ] dill - 1 bunch [ ] beer - 2 bottles
Maintenant, à propos de l'article. Dans go-openai, vous pouvez envoyer de l'audio à l'aide de la structure openai.AudioRequest, qui vous permet de spécifier un flux de données audio dans le champ Reader ou un chemin de fichier dans le champ FilePath. Eh bien, je pensais que ces champs s'excluaient mutuellement, mais il s'avère que vous devez spécifier FilePath même lorsque vous utilisez un flux audio. L'API l'utilise probablement pour déterminer le type de flux.
Côté Telegram, on reçoit une structure tgbotapi.Voice, qui possède un champ MimeType. Pour les messages vocaux, c'est audio/ogg, mais cela pourrait facilement changer dans le futur.
Nous avons donc le type MIME du flux audio, et nous devons en déterminer l'extension de fichier, en créant un nom de fichier pour garantir que l'API OpenAI ne se plaint pas d'un type de fichier inconnu.
Le code semble trivial :
extensions, err := mime.ExtensionsByType(mimeType) if err != nil { return err } if len(extensions) == 0 { return fmt.Errorf("unsupported mime type: %s", mimeType) } ext := extensions[0] log.Printf("Assumed extension: %s", ext) fakeFileName := fmt.Sprintf("voice_message.%s", ext)
Tester localement - ça marche :
2024/07/28 17:49:06 Assumed extension: oga
Déploiement sur le cloud - ça ne marche pas :
2024/07/28 17:55:32 unsupported mime type: audio/ogg
Vérification des versions Go, localement et dans le CI/CD utilisé pour la construction - ce sont les mêmes.
Il s'avère que le comportement du package MIME n'est pas déterministe et dépend au moment de l'exécution de la présence ou non d'un fichier /etc/mime.types dans le système d'exploitation et de son contenu.
C'est là que le package MIME lit la table d'exécution du type MIME vers les mappages d'extension de fichier.
Je suis très sérieux : vous compilez un binaire, exécutez tous les tests, tout semble bien, mais ce binaire déterminera les extensions de fichier supposées pour le même type MIME différemment dans différents environnements.
Prenons les types MIME connus de fichiers audio et leurs extensions, et ajoutons-les manuellement dans la fonction init en utilisant mime.AddExtensionType.
var mimetypes = [][]string{ {"audio/amr", "amr"}, {"audio/amr-wb", "awb"}, {"audio/annodex", "axa"}, {"audio/basic", "au", "snd"}, {"audio/csound", "csd", "orc", "sco"}, {"audio/flac", "flac"}, {"audio/midi", "mid", "midi", "kar"}, {"audio/mpeg", "mpga", "mpega", "mp2", "mp3", "m4a"}, {"audio/mpegurl", "m3u"}, {"audio/ogg", "oga", "ogg", "opus", "spx"}, {"audio/prs.sid", "sid"}, {"audio/x-aiff", "aif", "aiff", "aifc"}, {"audio/x-gsm", "gsm"}, {"audio/x-mpegurl", "m3u"}, {"audio/x-ms-wma", "wma"}, {"audio/x-ms-wax", "wax"}, {"audio/x-pn-realaudio", "ra", "rm", "ram"}, {"audio/x-realaudio", "ra"}, {"audio/x-scpls", "pls"}, {"audio/x-sd2", "sd2"}, {"audio/x-wav", "wav"}, } func init() { log.Println("init mimetypes") for _, v := range mimetypes { typ := v[0] extensions := v[1:] for _, ext := range extensions { err := mime.AddExtensionType("."+ext, typ) if err != nil { log.Fatalf("mime: %s", err.Error()) } } } }
Cela rendra le comportement de notre application déterministe quant au domaine avec lequel elle fonctionnera (dans mon cas, les fichiers audio).
Cette expérience a montré que les choses en Go ne sont pas toujours déterministes. Si quelque chose vous semble étrange, n'hésitez pas à vous plonger dans le code source de la bibliothèque et à voir comment il est implémenté. Cela peut vous épargner beaucoup de temps et d'ennuis à long terme.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!