@@ -4,8 +4,11 @@ import (
44 "fmt"
55 "math"
66 "os"
7+ "os/exec"
8+ "strconv"
9+ "strings"
10+ "time"
711
8- "github.com/go-flac/go-flac"
912 mewflac "github.com/mewkiz/flac"
1013)
1114
@@ -17,6 +20,7 @@ type AnalysisResult struct {
1720 BitsPerSample uint8 `json:"bits_per_sample"`
1821 TotalSamples uint64 `json:"total_samples"`
1922 Duration float64 `json:"duration"`
23+ Bitrate int `json:"bit_rate"`
2024 BitDepth string `json:"bit_depth"`
2125 DynamicRange float64 `json:"dynamic_range"`
2226 PeakAmplitude float64 `json:"peak_amplitude"`
@@ -29,53 +33,97 @@ func AnalyzeTrack(filepath string) (*AnalysisResult, error) {
2933 return nil , fmt .Errorf ("file does not exist: %s" , filepath )
3034 }
3135
32- fileInfo , err := os .Stat (filepath )
33- if err != nil {
34- return nil , fmt .Errorf ("failed to get file info: %w" , err )
36+ return GetMetadataWithFFprobe (filepath )
37+ }
38+
39+ func GetTrackMetadata (filepath string ) (* AnalysisResult , error ) {
40+ if ! fileExists (filepath ) {
41+ return nil , fmt .Errorf ("file does not exist: %s" , filepath )
3542 }
3643
37- f , err := flac .ParseFile (filepath )
44+ return GetMetadataWithFFprobe (filepath )
45+ }
46+
47+ func GetMetadataWithFFprobe (filePath string ) (* AnalysisResult , error ) {
48+ ffprobePath , err := GetFFprobePath ()
3849 if err != nil {
39- return nil , fmt .Errorf ("failed to parse FLAC file: %w" , err )
40- }
41-
42- result := & AnalysisResult {
43- FilePath : filepath ,
44- FileSize : fileInfo .Size (),
45- }
46-
47- if len (f .Meta ) > 0 {
48- streamInfo := f .Meta [0 ]
49- if streamInfo .Type == flac .StreamInfo {
50- data := streamInfo .Data
51- if len (data ) >= 18 {
52- result .SampleRate = uint32 (data [10 ])<< 12 | uint32 (data [11 ])<< 4 | uint32 (data [12 ])>> 4
53- result .Channels = ((data [12 ] >> 1 ) & 0x07 ) + 1
54- result .BitsPerSample = ((data [12 ]& 0x01 )<< 4 | data [13 ]>> 4 ) + 1
55- result .TotalSamples = uint64 (data [13 ]& 0x0F )<< 32 |
56- uint64 (data [14 ])<< 24 |
57- uint64 (data [15 ])<< 16 |
58- uint64 (data [16 ])<< 8 |
59- uint64 (data [17 ])
60-
61- if result .SampleRate > 0 {
62- result .Duration = float64 (result .TotalSamples ) / float64 (result .SampleRate )
63- }
64- }
50+ return nil , err
51+ }
52+
53+ for i := 0 ; i < 5 ; i ++ {
54+ if f , err := os .Open (filePath ); err == nil {
55+ f .Close ()
56+ break
6557 }
58+ time .Sleep (200 * time .Millisecond )
6659 }
6760
68- spectrum , err := AnalyzeSpectrum (filepath )
69- if err != nil {
70- fmt .Printf ("Warning: failed to analyze spectrum: %v\n " , err )
61+ infoMap := make (map [string ]string )
62+ args := []string {
63+ "-v" , "error" ,
64+ "-select_streams" , "a:0" ,
65+ "-show_entries" , "stream=sample_rate,channels,bits_per_raw_sample,bits_per_sample,duration,bit_rate" ,
66+ "-of" , "default=noprint_wrappers=0" ,
67+ filePath ,
68+ }
69+ cmd := exec .Command (ffprobePath , args ... )
70+ setHideWindow (cmd )
71+ output , err := cmd .CombinedOutput ()
72+ if err == nil {
73+ lines := strings .Split (string (output ), "\n " )
74+ for _ , line := range lines {
75+ if strings .Contains (line , "=" ) {
76+ parts := strings .SplitN (line , "=" , 2 )
77+ infoMap [strings .TrimSpace (parts [0 ])] = strings .TrimSpace (parts [1 ])
78+ }
79+ }
7180 } else {
72- result .Spectrum = spectrum
73- calculateRealAudioMetrics (result , filepath )
81+ return nil , fmt .Errorf ("ffprobe failed: %v - %s" , err , string (output ))
7482 }
7583
76- result .BitDepth = fmt .Sprintf ("%d-bit" , result .BitsPerSample )
84+ res := & AnalysisResult {
85+ FilePath : filePath ,
86+ }
87+
88+ if info , err := os .Stat (filePath ); err == nil {
89+ res .FileSize = info .Size ()
90+ }
91+
92+ if val , ok := infoMap ["sample_rate" ]; ok {
93+ s , _ := strconv .Atoi (val )
94+ res .SampleRate = uint32 (s )
95+ }
96+ if val , ok := infoMap ["channels" ]; ok {
97+ c , _ := strconv .Atoi (val )
98+ res .Channels = uint8 (c )
99+ }
100+ if val , ok := infoMap ["duration" ]; ok {
101+ d , _ := strconv .ParseFloat (val , 64 )
102+ res .Duration = d
103+ }
104+ if val , ok := infoMap ["bit_rate" ]; ok && val != "N/A" {
105+ br , _ := strconv .Atoi (val )
106+ res .Bitrate = br
107+ }
108+
109+ bits := 0
110+ if val , ok := infoMap ["bits_per_raw_sample" ]; ok && val != "N/A" {
111+ bits , _ = strconv .Atoi (val )
112+ }
113+ if bits == 0 {
114+ if val , ok := infoMap ["bits_per_sample" ]; ok && val != "N/A" {
115+ bits , _ = strconv .Atoi (val )
116+ }
117+ }
118+
119+ res .BitsPerSample = uint8 (bits )
120+ if bits > 0 {
121+ res .BitDepth = fmt .Sprintf ("%d-bit" , bits )
122+ } else {
123+ res .BitDepth = "Unknown"
124+ }
77125
78- return result , nil
126+ return res , nil
79127}
80128
81129func calculateRealAudioMetrics (result * AnalysisResult , filepath string ) {
0 commit comments