Add album/track releases with audio analysis, AnalyzeAlbumRelease RPC, Docker path auto-resolution, release parsing decision tree

This commit is contained in:
Alexander
2026-05-09 23:16:59 +02:00
parent 1e8506f146
commit 2740585261
25 changed files with 1841 additions and 125 deletions
+35
View File
@@ -0,0 +1,35 @@
package audio
import (
"fmt"
"path/filepath"
"strings"
)
type TrackInfo struct {
Format string
BitDepth int
SampleRate int
Channels int
DurationMs int
BitrateKbps int
IsLossless bool
}
func Analyze(filePath string) (*TrackInfo, error) {
ext := strings.ToLower(filepath.Ext(filePath))
switch ext {
case ".flac":
return analyzeFLAC(filePath)
case ".mp3":
return analyzeMP3(filePath)
case ".aac", ".m4a", ".ogg", ".wav", ".ape", ".wv", ".alac":
return &TrackInfo{
Format: strings.ToUpper(strings.TrimPrefix(ext, ".")),
IsLossless: ext == ".wav" || ext == ".ape" || ext == ".wv" || ext == ".alac",
}, nil
default:
return nil, fmt.Errorf("unsupported audio format: %s", ext)
}
}
+27
View File
@@ -0,0 +1,27 @@
package audio
import (
"fmt"
"github.com/mewkiz/flac"
)
func analyzeFLAC(filePath string) (*TrackInfo, error) {
stream, err := flac.ParseFile(filePath)
if err != nil {
return nil, fmt.Errorf("parsing FLAC: %w", err)
}
defer stream.Close()
info := stream.Info
durationMs := int(info.NSamples * 1000 / uint64(info.SampleRate))
return &TrackInfo{
Format: "FLAC",
BitDepth: int(info.BitsPerSample),
SampleRate: int(info.SampleRate),
Channels: int(info.NChannels),
DurationMs: durationMs,
IsLossless: true,
}, nil
}
+54
View File
@@ -0,0 +1,54 @@
package audio
import (
"fmt"
"os"
"time"
"github.com/tcolgate/mp3"
)
func analyzeMP3(filePath string) (*TrackInfo, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("opening MP3: %w", err)
}
defer f.Close()
decoder := mp3.NewDecoder(f)
var frame mp3.Frame
var skipped int
var totalDuration time.Duration
var sampleRate, channels, bitrate int
var frameCount int
for {
err := decoder.Decode(&frame, &skipped)
if err != nil {
break
}
if frameCount == 0 {
sampleRate = int(frame.Header().SampleRate())
channels = channelCount(frame.Header().ChannelMode())
bitrate = int(frame.Header().BitRate()) / 1000
}
totalDuration += frame.Duration()
frameCount++
}
return &TrackInfo{
Format: "MP3",
SampleRate: sampleRate,
Channels: channels,
DurationMs: int(totalDuration.Milliseconds()),
BitrateKbps: bitrate,
IsLossless: false,
}, nil
}
func channelCount(mode mp3.FrameChannelMode) int {
if mode == mp3.SingleChannel {
return 1
}
return 2
}