2026-07-29 02:17:57 +08:00
|
|
|
package media
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"ffmpeg-gui/internal/ffmpeg"
|
|
|
|
|
"fmt"
|
|
|
|
|
)
|
|
|
|
|
|
2026-07-29 19:33:46 +08:00
|
|
|
// StreamTags holds tag metadata from ffprobe's nested JSON output.
|
|
|
|
|
type StreamTags struct {
|
|
|
|
|
Language string `json:"language"`
|
|
|
|
|
Title string `json:"title"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-29 02:17:57 +08:00
|
|
|
// StreamInfo holds information about a single stream.
|
|
|
|
|
type StreamInfo struct {
|
2026-07-29 19:33:46 +08:00
|
|
|
Index int `json:"index"`
|
|
|
|
|
CodecType string `json:"codec_type"`
|
|
|
|
|
CodecName string `json:"codec_name"`
|
|
|
|
|
Width int `json:"width,omitempty"`
|
|
|
|
|
Height int `json:"height,omitempty"`
|
|
|
|
|
Duration string `json:"duration,omitempty"`
|
|
|
|
|
BitRate string `json:"bit_rate,omitempty"`
|
|
|
|
|
FPS string `json:"r_frame_rate,omitempty"`
|
|
|
|
|
Tags StreamTags `json:"tags"`
|
2026-07-29 02:17:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FormatInfo holds container format information.
|
|
|
|
|
type FormatInfo struct {
|
|
|
|
|
Filename string `json:"filename"`
|
|
|
|
|
Format string `json:"format_name"`
|
|
|
|
|
Duration string `json:"duration,omitempty"`
|
|
|
|
|
Size string `json:"size,omitempty"`
|
|
|
|
|
BitRate string `json:"bit_rate,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MediaInfo is the top-level ffprobe result.
|
|
|
|
|
type MediaInfo struct {
|
|
|
|
|
Streams []StreamInfo `json:"streams"`
|
|
|
|
|
Format FormatInfo `json:"format"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetInfo runs ffprobe and returns parsed media information.
|
|
|
|
|
func GetInfo(exec *ffmpeg.Executor, inputFile string) (*MediaInfo, error) {
|
|
|
|
|
out, err := exec.Probe(
|
|
|
|
|
"-v", "quiet",
|
|
|
|
|
"-print_format", "json",
|
|
|
|
|
"-show_format",
|
|
|
|
|
"-show_streams",
|
|
|
|
|
inputFile,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("ffprobe: %w (output: %s)", err, out)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var info MediaInfo
|
|
|
|
|
if err := json.Unmarshal([]byte(out), &info); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("parse ffprobe json: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return &info, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetDurationSeconds returns the duration in seconds as a float64.
|
|
|
|
|
func (mi *MediaInfo) GetDurationSeconds() float64 {
|
|
|
|
|
if mi.Format.Duration == "" {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
var secs float64
|
|
|
|
|
fmt.Sscanf(mi.Format.Duration, "%f", &secs)
|
|
|
|
|
return secs
|
|
|
|
|
}
|