package main import ( "bytes" "encoding/base64" "fmt" "io" "net/http" "os" "os/exec" "path/filepath" "strings" "time" ) func removeBOM(input []byte) []byte { if len(input) >= 3 && input[0] == 0xEF && input[1] == 0xBB && input[2] == 0xBF { return input[3:] } return input } func downloadFile(item Item, jobInfo *JobInfo) error { logger.LogInfo("Download File", fmt.Sprintf("Starting download for: %s", item.Filename)) tempDir := filepath.Join(config.TempBaseDir, sanitizeFilename(item.Filename)) err := os.MkdirAll(tempDir, 0755) if err != nil { logger.LogError("Download File", fmt.Sprintf("Error creating temporary directory: %v", err)) return fmt.Errorf("error creating temporary directory: %v", err) } jobInfo.TempDir = tempDir mpdPath := item.MPD if !isValidURL(item.MPD) { decodedMPD, err := base64.StdEncoding.DecodeString(item.MPD) if err != nil { logger.LogError("Download File", fmt.Sprintf("Error decoding base64 MPD: %v", err)) return fmt.Errorf("error decoding base64 MPD: %v", err) } tempFile, err := os.CreateTemp("", "temp_mpd_*.mpd") if err != nil { logger.LogError("Download File", fmt.Sprintf("Error creating temporary MPD file: %v", err)) return fmt.Errorf("error creating temporary MPD file: %v", err) } defer os.Remove(tempFile.Name()) if _, err := tempFile.Write(decodedMPD); err != nil { logger.LogError("Download File", fmt.Sprintf("Error writing to temporary MPD file: %v", err)) return fmt.Errorf("error writing to temporary MPD file: %v", err) } if err := tempFile.Close(); err != nil { logger.LogError("Download File", fmt.Sprintf("Error closing temporary MPD file: %v", err)) return fmt.Errorf("error closing temporary MPD file: %v", err) } mpdPath = tempFile.Name() } else if strings.HasPrefix(item.MPD, "https://pubads.g.doubleclick.net") { resp, err := http.Get(item.MPD) if err != nil { logger.LogError("Download File", fmt.Sprintf("Error downloading MPD: %v", err)) return fmt.Errorf("error downloading MPD: %v", err) } defer resp.Body.Close() mpdContent, err := io.ReadAll(resp.Body) if err != nil { logger.LogError("Download File", fmt.Sprintf("Error reading MPD content: %v", err)) return fmt.Errorf("error reading MPD content: %v", err) } fixedMPDContent, err := fixGoPlay(string(mpdContent)) if err != nil { logger.LogError("Download File", fmt.Sprintf("Error fixing MPD content: %v", err)) return fmt.Errorf("error fixing MPD content: %v", err) } tempFile, err := os.CreateTemp("", "fixed_mpd_*.mpd") if err != nil { logger.LogError("Download File", fmt.Sprintf("Error creating temporary MPD file: %v", err)) return fmt.Errorf("error creating temporary MPD file: %v", err) } defer os.Remove(tempFile.Name()) if _, err := tempFile.WriteString(fixedMPDContent); err != nil { logger.LogError("Download File", fmt.Sprintf("Error writing to temporary MPD file: %v", err)) return fmt.Errorf("error writing to temporary MPD file: %v", err) } if err := tempFile.Close(); err != nil { logger.LogError("Download File", fmt.Sprintf("Error closing temporary MPD file: %v", err)) return fmt.Errorf("error closing temporary MPD file: %v", err) } mpdPath = tempFile.Name() } command := getDownloadCommand(item, mpdPath, tempDir) if item.Subtitles != "" { subtitlePaths, err := downloadAndConvertSubtitles(item.Subtitles) if err != nil { logger.LogError("Download File", fmt.Sprintf("Error processing subtitles: %v", err)) } else { for _, path := range subtitlePaths { logger.LogInfo("Download File", fmt.Sprintf("Adding subtitle: %s", path)) command += fmt.Sprintf(" --mux-import \"path=%s:lang=nl:name=Nederlands\"", path) } } } cmd := exec.Command("bash", "-c", command) jobInfo.Cmd = cmd var outputBuffer bytes.Buffer cmd.Stdout = io.MultiWriter(&outputBuffer) cmd.Stderr = os.Stderr err = cmd.Start() if err != nil { logger.LogError("Download File", fmt.Sprintf("Error starting download command: %v", err)) return fmt.Errorf("error starting download command: %v", err) } done := make(chan error) go func() { done <- cmd.Wait() }() go func() { for { if outputBuffer.Len() > 0 { broadcast(outputBuffer.Bytes()) outputBuffer.Reset() } time.Sleep(1 * time.Second) } }() select { case <-jobInfo.AbortChan: if cmd.Process != nil { cmd.Process.Kill() } os.RemoveAll(tempDir) logger.LogInfo("Download File", "Download aborted") return fmt.Errorf("download aborted") case err := <-done: if jobInfo.Paused { logger.LogInfo("Download File", "Download paused") return fmt.Errorf("download paused") } if err != nil { logger.LogError("Download File", fmt.Sprintf("Error executing download command: %v", err)) return fmt.Errorf("error executing download command: %v", err) } } logger.LogInfo("Download File", "Download completed successfully") return nil } func getDownloadCommand(item Item, mpdPath string, tempDir string) string { metadata := parseMetadata(item.Metadata) keys := getKeys(item.Keys) command := fmt.Sprintf("%s %s", config.N_m3u8DLRE.Path, mpdPath) for _, key := range keys { if key != "" { command += fmt.Sprintf(" --key %s", key) } } command += " --auto-select" sanitizedFilename := sanitizeFilename(item.Filename) filename := fmt.Sprintf("\"%s\"", sanitizedFilename) command += fmt.Sprintf(" --save-name %s", filename) command += fmt.Sprintf(" --mux-after-done format=%s", config.Format) saveDir := config.BaseDir if metadata.Type == "serie" { saveDir = filepath.Join(saveDir, "Series", metadata.Title, metadata.Season) } else { saveDir = filepath.Join(saveDir, "Movies", metadata.Title) } command += fmt.Sprintf(" --save-dir \"%s\"", saveDir) command += fmt.Sprintf(" --tmp-dir \"%s\"", tempDir) fmt.Println(command) return command }