首頁  >  文章  >  後端開發  >  Go 中「mime」包的不確定性:信任但驗證

Go 中「mime」包的不確定性:信任但驗證

WBOY
WBOY原創
2024-07-29 07:26:131196瀏覽

Indeterminacy of the `mime` Package in Go: Trust But Verify

最初發佈在我的部落格:https://lazy.bearblog.dev/go-mime-wtf/

背景

一個週末,我決定為自己編寫一個 Telegram 機器人來自動執行待辦事項或購物清單。

這個想法很簡單。我向機器人發送了要購買的商品列表,它會創建一個帶有復選框的網頁,我可以在一手拿著手機、另一隻手拿著購物籃的情況下勾選這些複選框。

機器人寫得很快,但後來我想嘗試 OpenAI 的 Whisper 模型。因此,我決定在我的應用程式中添加語音識別。

現在,程式的工作原理如下:

  • 我向機器人發送這樣的語音訊息:「買一公斤土豆,一堆蒔蘿,一頭捲心菜,幾瓶啤酒,哦,不需要捲心菜,我們有」
  • 機器人辨識語音並建立一個有複選框的頁面:
[ ] potatoes - 1 kg
[ ] dill - 1 bunch
[ ] beer - 2 bottles

現在,關於這篇文章。在 go-openai 中,您可以使用 openai.AudioRequest 結構發送音頻,該結構允許您在 Reader 欄位中指定音訊資料流或在 FilePath 欄位中指定檔案路徑。好吧,我認為這些欄位是互斥的,但事實證明,即使使用音訊串流也需要指定 FilePath。 API 可能使用它來決定流類型。

在 Telegram 端,我們收到一個 tgbotapi.Voice 結構,其中有一個 MimeType 欄位。對於語音訊息,它是audio/ogg,但將來很容易改變。

因此,我們有了音訊串流的 MIME 類型,我們需要從中確定檔案副檔名,建立一個檔案名稱以確保 OpenAI API 不會抱怨未知的檔案類型。

還有什麼可以更簡單呢?

程式碼看起來很簡單:

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)

本地測試 - 它有效:

2024/07/28 17:49:06 Assumed extension: oga

部署到雲端 - 不起作用:

2024/07/28 17:55:32 unsupported mime type: audio/ogg

檢查本地和用於構建的 CI/CD 中的 Go 版本 - 它們是相同的。

讓我們開始吧

事實證明,mime 套件的行為不是確定性的,並且在運行時取決於作業系統中是否存在 /etc/mime.types 檔案及其內容。

這是 mime 套件讀取 MIME 類型到檔案副檔名映射的運行時表的地方。

我是認真的:你編譯一個二進位文件,運行所有測試,一切看起來都很棒,但是這個二進製文件將在不同的環境中以不同的方式確定相同MIME 類型的假定文件擴展名。

如何修復它

讓我們取得已知的 MIME 類型的音訊檔案及其副檔名,並使用 mime.AddExtensionType 手動將它們新增至 init 函數。

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())
            }
        }
    }
}

這將使我們的應用程式的行為在其將使用的網域(在我的例子中是音訊檔案)方面具有確定性。

結論

這個經驗表明,Go 中的事物並不總是確定性的。如果有些事情看起來很奇怪,請不要猶豫,深入研究庫的源代碼,看看它是如何實現的。從長遠來看,這可以為您節省大量時間和麻煩。

以上是Go 中「mime」包的不確定性:信任但驗證的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn