Files
MeowBox-Core/helper.go
2025-09-07 14:21:48 +08:00

284 lines
7.2 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
// Helper function to compress and segment audio file
func compressAndSegmentAudio(inputFile, outputDir string) error {
fmt.Printf("[Info] Compress and segment audio file %s\n", inputFile)
// Compress music files
outputFile := filepath.Join(outputDir, "music.mp3")
cmd := exec.Command("ffmpeg", "-i", inputFile, "-ac", "1", "-ab", "32k", "-ar", "16000", outputFile)
err := cmd.Run()
if err != nil {
return err
}
// Split music files
chunkDir := filepath.Join(outputDir, "chunk")
err = os.MkdirAll(chunkDir, 0755)
if err != nil {
return err
}
// Using ffmpeg for segmentation
segmentedFilePattern := filepath.Join(chunkDir, "%03d.mp3") // e.g. 001.mp3, 002.mp3, ...
cmd = exec.Command("ffmpeg", "-i", outputFile, "-ac", "1", "-ab", "32k", "-ar", "16000", "-f", "segment", "-segment_time", "10", segmentedFilePattern)
err = cmd.Run()
if err != nil {
return err
}
return nil
}
// Helper function to create M3U8 playlist file
func createM3U8Playlist(outputDir string) error {
fmt.Printf("[Info] Create M3U8 playlist file for %s\n", outputDir)
playlistFile := filepath.Join(outputDir, "music.m3u8")
file, err := os.Create(playlistFile)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString("#EXTM3U\n")
if err != nil {
return err
}
_, err = file.WriteString("#EXT-X-VERSION:3\n")
if err != nil {
return err
}
_, err = file.WriteString("#EXT-X-TARGETDURATION:10\n")
if err != nil {
return err
}
chunkDir := filepath.Join(outputDir, "chunk")
files, err := ioutil.ReadDir(chunkDir)
if err != nil {
return err
}
var chunkFiles []string
for _, file := range files {
if strings.HasSuffix(file.Name(), ".mp3") {
chunkFiles = append(chunkFiles, file.Name())
}
}
// Sort by file name
for i := 0; i < len(chunkFiles); i++ {
for j := i + 1; j < len(chunkFiles); j++ {
if chunkFiles[i] > chunkFiles[j] {
chunkFiles[i], chunkFiles[j] = chunkFiles[j], chunkFiles[i]
}
}
}
for _, chunkFile := range chunkFiles {
_, err = file.WriteString("#EXTINF:10.000\n")
if err != nil {
return err
}
url := fmt.Sprintf("%s/cache/music/%s/%s/%s\n", os.Getenv("WEBSITE_URL"), filepath.Base(outputDir), "chunk", chunkFile)
_, err = file.WriteString(url)
}
return err
}
// Helper function to download files from URL
func downloadFile(filename string, url string) error {
fmt.Printf("[Info] Download file %s from URL %s\n", filename, url)
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
out, err := os.Create(filename)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
// Helper function to get duration of obtaining music files
func getMusicDuration(filePath string) int {
fmt.Printf("[Info] Get duration of obtaining music file %s\n", filePath)
// Use ffprobe to get audio duration
output, err := exec.Command("ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", filePath).Output()
if err != nil {
fmt.Println("[Error] Error getting audio duration:", err)
return 0
}
duration, err := strconv.ParseFloat(strings.TrimSpace(string(output)), 64)
if err != nil {
fmt.Println("[Error] Error converting duration to float:", err)
return 0
}
return int(duration)
}
// Function for identifying file formats
func getMusicFileExtension(url string) (string, error) {
resp, err := http.Head(url)
if err != nil {
return "", err
}
// Get file format from Content-Type header
contentType := resp.Header.Get("Content-Type")
ext, _, err := mime.ParseMediaType(contentType)
if err != nil {
return "", err
}
// Identify file extension based on file format
switch ext {
case "audio/mpeg":
return ".mp3", nil
case "audio/flac":
return ".flac", nil
case "audio/x-flac":
return ".flac", nil
case "audio/wav":
return ".wav", nil
case "audio/aac":
return ".aac", nil
case "audio/ogg":
return ".ogg", nil
case "application/octet-stream":
// Try to guess file format from URL or other information
if strings.Contains(url, ".mp3") {
return ".mp3", nil
} else if strings.Contains(url, ".flac") {
return ".flac", nil
} else if strings.Contains(url, ".wav") {
return ".wav", nil
} else if strings.Contains(url, ".aac") {
return ".aac", nil
} else if strings.Contains(url, ".ogg") {
return ".ogg", nil
} else {
return "", fmt.Errorf("unknown file format from Content-Type and URL: %s", contentType)
}
default:
return "", fmt.Errorf("unknown file format: %s", ext)
}
}
// Helper function to request and cache music from API sources
func requestAndCacheMusic(song, singer string) {
fmt.Printf("[Info] Requesting and caching music for %s", song)
// Create cache directory if it doesn't exist
err := os.MkdirAll("./cache", 0755)
if err != nil {
fmt.Println("[Error] Error creating cache directory:", err)
return
}
// Get API_SOURCES and any subsequent environment variables (e.g. API_SOURCES_1, API_SOURCES_2, etc.)
var sources []string
for i := 0; ; i++ {
var key string
if i == 0 {
key = "API_SOURCES"
} else {
key = "API_SOURCES_" + strconv.Itoa(i)
}
source := os.Getenv(key)
if source == "" {
break
}
sources = append(sources, source)
}
// Request and cache music from each source in turn
var musicItem MusicItem
for _, source := range sources {
fmt.Printf("[Info] Requesting music from source: %s\n", source)
musicItem = YuafengAPIResponseHandler(strings.TrimSpace(source), song, singer)
if musicItem.Title != "" {
// If music item is valid, stop searching for sources
break
}
}
// If no valid music item was found, return an empty MusicItem
if musicItem.Title == "" {
fmt.Println("[Warning] No valid music item retrieved.")
return
}
// Create cache file path based on artist and title
cacheFile := fmt.Sprintf("./cache/%s-%s.json", musicItem.Artist, musicItem.Title)
// Write cache data to cache file
cacheData, err := json.MarshalIndent(musicItem, "", " ")
if err != nil {
fmt.Println("[Error] Error marshalling cache data:", err)
return
}
err = ioutil.WriteFile(cacheFile, cacheData, 0644)
if err != nil {
fmt.Println("[Error] Error writing cache file:", err)
return
}
fmt.Println("[Info] Music request and caching completed successfully.")
}
// Helper function to read music data from cache file
func readFromCache(filePath string) (MusicItem, bool) {
data, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Println("[Error] Failed to read cache file:", err)
return MusicItem{}, false
}
var musicItem MusicItem
err = json.Unmarshal(data, &musicItem)
if err != nil {
fmt.Println("[Error] Failed to parse cache file:", err)
return MusicItem{}, false
}
return musicItem, true
}
// Helper function to obtain IP address of the client
func IPhandler(r *http.Request) (string, error) {
ip := r.Header.Get("X-Real-IP")
if ip != "" {
return ip, nil
}
ip = r.Header.Get("X-Forwarded-For")
if ip != "" {
ips := strings.Split(ip, ",")
return strings.TrimSpace(ips[0]), nil
}
ip = r.RemoteAddr
if ip != "" {
return strings.Split(ip, ":")[0], nil
}
return "", fmt.Errorf("unable to obtain IP address information")
}