package main import ( "encoding/base64" "net/http" "os" "path/filepath" "sync" "github.com/wailsapp/wails/v3/pkg/application" ) const localMediaVideoPath = "/localmedia/video" // VideoResult is returned when the user opens a video file. type VideoResult struct { URL string `json:"url"` Name string `json:"name"` } // FileService exposes native file dialogs and streams the current video. type FileService struct { app *application.App mu sync.RWMutex videoPath string videoMod os.FileInfo } func NewFileService(app *application.App) *FileService { return &FileService{app: app} } func (s *FileService) OpenVideo() (VideoResult, error) { path, err := s.app.Dialog.OpenFile(). SetTitle("Open Video"). CanChooseFiles(true). AddFilter("Video", "*.mp4;*.mov;*.webm;*.mkv"). AddFilter("All Files", "*"). PromptForSingleSelection() if err != nil { return VideoResult{}, err } if path == "" { return VideoResult{}, nil } info, err := os.Stat(path) if err != nil { return VideoResult{}, err } s.mu.Lock() s.videoPath = path s.videoMod = info s.mu.Unlock() return VideoResult{ URL: localMediaVideoPath, Name: filepath.Base(path), }, nil } func (s *FileService) OpenProject() (string, error) { path, err := s.app.Dialog.OpenFile(). SetTitle("Open Project"). CanChooseFiles(true). AddFilter("JSON", "*.json"). AddFilter("All Files", "*"). PromptForSingleSelection() if err != nil { return "", err } if path == "" { return "", nil } data, err := os.ReadFile(path) if err != nil { return "", err } return string(data), nil } func (s *FileService) SaveProject(content string, defaultName string) error { if defaultName == "" { defaultName = "project.json" } path, err := s.app.Dialog.SaveFile(). SetMessage("Save Project"). SetFilename(defaultName). AddFilter("JSON", "*.json"). AddFilter("All Files", "*"). PromptForSingleSelection() if err != nil { return err } if path == "" { return nil } return os.WriteFile(path, []byte(content), 0o644) } func (s *FileService) SaveAudio(dataBase64 string, defaultName string) error { if defaultName == "" { defaultName = "keyboard_track.wav" } data, err := base64.StdEncoding.DecodeString(dataBase64) if err != nil { return err } path, err := s.app.Dialog.SaveFile(). SetMessage("Export Audio"). SetFilename(defaultName). AddFilter("WAV", "*.wav"). AddFilter("All Files", "*"). PromptForSingleSelection() if err != nil { return err } if path == "" { return nil } return os.WriteFile(path, data, 0o644) } func (s *FileService) serveVideo(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } s.mu.RLock() path := s.videoPath modTime := s.videoMod s.mu.RUnlock() if path == "" { http.NotFound(w, r) return } file, err := os.Open(path) if err != nil { http.Error(w, "video unavailable", http.StatusNotFound) return } defer file.Close() info, err := file.Stat() if err != nil { http.Error(w, "video unavailable", http.StatusNotFound) return } if modTime != nil && !info.ModTime().Equal(modTime.ModTime()) { s.mu.Lock() s.videoMod = info s.mu.Unlock() } http.ServeContent(w, r, info.Name(), info.ModTime(), file) }