mirror of
https://github.com/xzeldon/whisper-api-server.git
synced 2025-07-14 08:54:34 +03:00
feat: big update
add cli parsing, model and .dll download prompt and so on
This commit is contained in:
103
internal/resources/cliArgs.go
Normal file
103
internal/resources/cliArgs.go
Normal file
@ -0,0 +1,103 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
//go:embed languageMap.json
|
||||
var languageMapData []byte // Embedded language map file as a byte slice
|
||||
|
||||
// Arguments holds the parsed CLI arguments
|
||||
type Arguments struct {
|
||||
Language string
|
||||
ModelPath string
|
||||
Port int
|
||||
}
|
||||
|
||||
// ParsedArguments holds the processed arguments
|
||||
type ParsedArguments struct {
|
||||
Language int32
|
||||
ModelPath string
|
||||
Port int
|
||||
}
|
||||
|
||||
// LanguageMap represents the mapping of languages to their hex codes
|
||||
type LanguageMap map[string]string
|
||||
|
||||
func processLanguageAndCode(language string) (int32, error) {
|
||||
var languageMap LanguageMap
|
||||
err := json.Unmarshal(languageMapData, &languageMap)
|
||||
if err != nil {
|
||||
return 0x6E65, fmt.Errorf("error parsing language map: %w", err)
|
||||
}
|
||||
|
||||
hexCode, ok := languageMap[strings.ToLower(language)]
|
||||
if !ok {
|
||||
return 0x6E65, fmt.Errorf("unsupported language")
|
||||
}
|
||||
|
||||
fmt.Printf("Hex Code Found: %s\n", hexCode)
|
||||
|
||||
languageCode, err := strconv.ParseInt(hexCode, 0, 32)
|
||||
if err != nil {
|
||||
return 0x6E65, fmt.Errorf("error converting hex code: %w", err)
|
||||
}
|
||||
|
||||
return int32(languageCode), nil
|
||||
}
|
||||
|
||||
func ApplyExitOnHelp(c *cobra.Command, exitCode int) {
|
||||
helpFunc := c.HelpFunc()
|
||||
c.SetHelpFunc(func(c *cobra.Command, s []string) {
|
||||
helpFunc(c, s)
|
||||
os.Exit(exitCode)
|
||||
})
|
||||
}
|
||||
|
||||
func ParseFlags() (*ParsedArguments, error) {
|
||||
args := &Arguments{}
|
||||
|
||||
var parsedArgs *ParsedArguments
|
||||
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "whisper",
|
||||
Short: "Audio transcription using the OpenAI Whisper models",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
// Process language code with fallback
|
||||
languageCode, err := processLanguageAndCode(args.Language)
|
||||
if err != nil {
|
||||
fmt.Printf("Error setting language, defaulting to English")
|
||||
// Default to English
|
||||
languageCode = 0x6E65
|
||||
}
|
||||
|
||||
parsedArgs = &ParsedArguments{
|
||||
Language: languageCode,
|
||||
ModelPath: args.ModelPath,
|
||||
Port: args.Port,
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
rootCmd.Flags().StringVarP(&args.Language, "language", "l", "", "Language to be processed")
|
||||
rootCmd.Flags().StringVarP(&args.ModelPath, "modelPath", "m", "ggml-medium.bin", "Path to the model file (required)")
|
||||
rootCmd.Flags().IntVarP(&args.Port, "port", "p", 3000, "Port to start the server on")
|
||||
|
||||
ApplyExitOnHelp(rootCmd, 0)
|
||||
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsedArgs, nil
|
||||
}
|
||||
|
@ -1,96 +0,0 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Arguments defines the structure to hold parsed arguments
|
||||
type Arguments struct {
|
||||
Language string
|
||||
ModelPath string
|
||||
Port int
|
||||
}
|
||||
type ParsedArguments struct {
|
||||
Language int32
|
||||
ModelPath string
|
||||
Port int
|
||||
}
|
||||
|
||||
type LanguageMap map[string]string
|
||||
|
||||
func processLanguageAndCode(args *Arguments) (int32, error) {
|
||||
// Read the language map from JSON file
|
||||
jsonFile, err := os.Open("languageMap.json")
|
||||
if err != nil {
|
||||
return 0x6E65, fmt.Errorf("error opening language map: %w", err) // Wrap error for context
|
||||
}
|
||||
defer jsonFile.Close()
|
||||
|
||||
byteData, err := io.ReadAll(jsonFile)
|
||||
if err != nil {
|
||||
return 0x6E65, fmt.Errorf("error reading language map: %w", err)
|
||||
}
|
||||
|
||||
var languageMap LanguageMap
|
||||
err = json.Unmarshal(byteData, &languageMap)
|
||||
if err != nil {
|
||||
return 0x6E65, fmt.Errorf("error parsing language map: %w", err)
|
||||
}
|
||||
|
||||
hexCode, ok := languageMap[strings.ToLower(args.Language)]
|
||||
if !ok {
|
||||
return 0x6E65, fmt.Errorf("unsupported language: %s", args.Language)
|
||||
}
|
||||
|
||||
languageCode, err := strconv.ParseInt(hexCode, 0, 32)
|
||||
if err != nil {
|
||||
return 0x6E65, fmt.Errorf("error converting hex code: %w", err)
|
||||
}
|
||||
|
||||
return int32(languageCode), nil
|
||||
}
|
||||
|
||||
// ParseFlags parses command line arguments and returns an Arguments struct
|
||||
func ParseFlags() (*ParsedArguments, error) {
|
||||
args := &Arguments{}
|
||||
|
||||
flag.StringVar(&args.Language, "l", "", "Language to be processed")
|
||||
flag.StringVar(&args.Language, "language", "", "Language to be processed") // Optional: Redundant to demonstrate
|
||||
flag.StringVar(&args.ModelPath, "m", "", "Path to the model file (required)")
|
||||
flag.StringVar(&args.ModelPath, "modelPath", "", "Path to the model file (required)") // Optional: Redundant
|
||||
flag.IntVar(&args.Port, "p", 3031, "Port to start the server on")
|
||||
flag.IntVar(&args.Port, "port", 3031, "Port to start the server on") // Optional: Redundant
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage: your_program [OPTIONS]")
|
||||
fmt.Println("Options:")
|
||||
flag.PrintDefaults() // Print default values for all flags
|
||||
}
|
||||
|
||||
// Parsing flags
|
||||
flag.Parse()
|
||||
|
||||
args.Language = strings.ToLower(args.Language)
|
||||
|
||||
if args.ModelPath == "" {
|
||||
return nil, fmt.Errorf("modelPath argument is required")
|
||||
}
|
||||
|
||||
languageCode, err := processLanguageAndCode(args)
|
||||
if err != nil {
|
||||
fmt.Println("Error setting language, defaulting to English:", err)
|
||||
// Use default language code directly as the result here
|
||||
}
|
||||
|
||||
return &ParsedArguments{
|
||||
Language: languageCode,
|
||||
ModelPath: args.ModelPath,
|
||||
Port: args.Port,
|
||||
}, nil
|
||||
}
|
143
internal/resources/downloadResources.go
Normal file
143
internal/resources/downloadResources.go
Normal file
@ -0,0 +1,143 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/schollz/progressbar/v3"
|
||||
)
|
||||
|
||||
func GetModel(modelType string) (string, error) {
|
||||
fileURL := fmt.Sprintf("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/%s", modelType)
|
||||
filePath := modelType
|
||||
|
||||
isModelFileExists := IsFileExists(filePath)
|
||||
|
||||
if !isModelFileExists {
|
||||
fmt.Println("Model not found.")
|
||||
err := DownloadFile(fileURL, filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fmt.Printf("Model found: %s\n", absPath)
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
func DownloadFile(url string, filepath string) error {
|
||||
out, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
fileSize := resp.ContentLength
|
||||
bar := progressbar.DefaultBytes(
|
||||
fileSize,
|
||||
"Downloading",
|
||||
)
|
||||
|
||||
writer := io.MultiWriter(out, bar)
|
||||
|
||||
_, err = io.Copy(writer, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetWhisperDll(version string) (string, error) {
|
||||
fileUrl := fmt.Sprintf("https://github.com/Const-me/Whisper/releases/download/%s/Library.zip", version)
|
||||
fileToExtract := "Binary/Whisper.dll"
|
||||
|
||||
isWhisperDllExists := IsFileExists("Whisper.dll")
|
||||
|
||||
if !isWhisperDllExists {
|
||||
fmt.Println("Whisper DLL not found.")
|
||||
archivePath, err := os.CreateTemp("", "WhisperLibrary-*.zip")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer archivePath.Close()
|
||||
|
||||
err = DownloadFile(fileUrl, archivePath.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = extractFile(archivePath.Name(), fileToExtract)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs("Whisper.dll")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fmt.Printf("Library found: %s\n", absPath)
|
||||
return "Whisper.dll", nil
|
||||
}
|
||||
|
||||
func extractFile(archivePath string, fileToExtract string) error {
|
||||
reader, err := zip.OpenReader(archivePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
for _, file := range reader.File {
|
||||
if file.Name == fileToExtract {
|
||||
targetPath := filepath.Base(fileToExtract)
|
||||
|
||||
writer, err := os.Create(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer writer.Close()
|
||||
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
_, err = io.Copy(writer, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("File not found in the archive")
|
||||
}
|
||||
|
||||
func IsFileExists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
84
internal/resources/languageMap.json
Normal file
84
internal/resources/languageMap.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"af": "0x6661",
|
||||
"sq": "0x7173",
|
||||
"am": "0x6D61",
|
||||
"ar": "0x7261",
|
||||
"hy": "0x7968",
|
||||
"as": "0x7361",
|
||||
"az": "0x7A61",
|
||||
"ba": "0x6162",
|
||||
"eu": "0x7565",
|
||||
"be": "0x6562",
|
||||
"bn": "0x6E62",
|
||||
"bs": "0x7362",
|
||||
"br": "0x7262",
|
||||
"bg": "0x6762",
|
||||
"ca": "0x6163",
|
||||
"zh": "0x687A",
|
||||
"hr": "0x7268",
|
||||
"cs": "0x7363",
|
||||
"da": "0x6164",
|
||||
"nl": "0x6C6E",
|
||||
"en": "0x6E65",
|
||||
"et": "0x7465",
|
||||
"fo": "0x6F66",
|
||||
"fi": "0x6966",
|
||||
"fr": "0x7266",
|
||||
"gl": "0x6C67",
|
||||
"ka": "0x616B",
|
||||
"de": "0x7265",
|
||||
"el": "0x6C61",
|
||||
"gu": "0x7567",
|
||||
"he": "0x6568",
|
||||
"hi": "0x6968",
|
||||
"hu": "0x7568",
|
||||
"is": "0x7369",
|
||||
"id": "0x6469",
|
||||
"it": "0x7469",
|
||||
"ja": "0x616A",
|
||||
"kn": "0x6E6B",
|
||||
"kk": "0x6B6B",
|
||||
"km": "0x6D6B",
|
||||
"ko": "0x6F6B",
|
||||
"ky": "0x796B",
|
||||
"lo": "0x6F6C",
|
||||
"lv": "0x766C",
|
||||
"lt": "0x746C",
|
||||
"mk": "0x6B6D",
|
||||
"ms": "0x736D",
|
||||
"ml": "0x6C6D",
|
||||
"mr": "0x726D",
|
||||
"mn": "0x6E6D",
|
||||
"ne": "0x6570",
|
||||
"no": "0x6F6E",
|
||||
"or": "0x726F",
|
||||
"ps": "0x7368",
|
||||
"fa": "0x6172",
|
||||
"pl": "0x6C70",
|
||||
"pt": "0x7470",
|
||||
"pa": "0x6170",
|
||||
"ro": "0x6F72",
|
||||
"ru": "0x7572",
|
||||
"sa": "0x6173",
|
||||
"sr": "0x7273",
|
||||
"sd": "0x6473",
|
||||
"si": "0x6973",
|
||||
"sk": "0x6B73",
|
||||
"sl": "0x6C73",
|
||||
"es": "0x6573",
|
||||
"sw": "0x7773",
|
||||
"sv": "0x6576",
|
||||
"tg": "0x6769",
|
||||
"ta": "0x6174",
|
||||
"te": "0x6574",
|
||||
"th": "0x6874",
|
||||
"tr": "0x7274",
|
||||
"uk": "0x6B75",
|
||||
"ur": "0x7275",
|
||||
"uz": "0x7A75",
|
||||
"vi": "0x6976",
|
||||
"cy": "0x7963",
|
||||
"xh": "0x6877",
|
||||
"yi": "0x6979",
|
||||
"yo": "0x6F79"
|
||||
}
|
76
internal/resources/promt.go
Normal file
76
internal/resources/promt.go
Normal file
@ -0,0 +1,76 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PromptUser prompts the user with a question and returns true if they agree
|
||||
func PromptUser(question string) bool {
|
||||
fmt.Printf("%s (y/n): ", question)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
fmt.Println("Error reading input:", err)
|
||||
return false
|
||||
}
|
||||
response = strings.TrimSpace(strings.ToLower(response))
|
||||
return response == "y" || response == "yes"
|
||||
}
|
||||
|
||||
// HandleWhisperDll checks if Whisper.dll exists or prompts the user to download it
|
||||
func HandleWhisperDll(version string) (string, error) {
|
||||
if IsFileExists("Whisper.dll") {
|
||||
absPath, err := filepath.Abs("Whisper.dll")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Printf("Library found: %s\n", absPath)
|
||||
return "Whisper.dll", nil
|
||||
}
|
||||
|
||||
fmt.Println("Whisper DLL not found.")
|
||||
if PromptUser("Do you want to download Whisper.dll automatically?") {
|
||||
path, err := GetWhisperDll(version)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to download Whisper.dll: %w", err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
fmt.Println("To use Whisper, download the DLL manually:")
|
||||
fmt.Printf("URL: https://github.com/Const-me/Whisper/releases/download/%s/Library.zip\n", version)
|
||||
fmt.Println("Extract 'Binary/Whisper.dll' from the archive and place it in the executable's directory.")
|
||||
fmt.Println("You can manually specify path to .dll file using cli arguments, use --help to print available cli flags")
|
||||
return "", fmt.Errorf("whisper.dll not found and user chose not to download")
|
||||
}
|
||||
|
||||
// HandleDefaultModel checks if the default model exists or prompts the user to download it
|
||||
func HandleDefaultModel(modelType string) (string, error) {
|
||||
if IsFileExists(modelType) {
|
||||
absPath, err := filepath.Abs(modelType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Printf("Model found: %s\n", absPath)
|
||||
return modelType, nil
|
||||
}
|
||||
|
||||
fmt.Println("Default model not found.")
|
||||
if PromptUser("Do you want to download the default model (ggml-medium.bin) automatically?") {
|
||||
path, err := GetModel(modelType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to download the default model: %w", err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
fmt.Println("To use Whisper, download the model manually:")
|
||||
fmt.Println("URL: https://huggingface.co/ggerganov/whisper.cpp/tree/main")
|
||||
fmt.Println("Place the model file in the executable's directory or specify its path using cli arguments.")
|
||||
fmt.Println("You can manually specify path to model file using cli arguments, use --help to print available cli flags")
|
||||
return "", fmt.Errorf("default model not found and user chose not to download")
|
||||
}
|
Reference in New Issue
Block a user