This commit is contained in:
stefanodvx 2025-04-14 13:05:43 +02:00
parent 264c97183e
commit 3faede7b1c
74 changed files with 6228 additions and 1 deletions

182
util/parser/m3u8.go Normal file
View file

@ -0,0 +1,182 @@
package parser
import (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/pkg/errors"
"govd/enums"
"govd/models"
"github.com/grafov/m3u8"
)
var httpClient = &http.Client{
Timeout: 30 * time.Second,
}
func ParseM3U8Content(
content []byte,
baseURL string,
) ([]*models.MediaFormat, error) {
baseURLObj, err := url.Parse(baseURL)
if err != nil {
return nil, fmt.Errorf("invalid base url: %w", err)
}
buf := bytes.NewBuffer(content)
playlist, listType, err := m3u8.DecodeFrom(buf, true)
if err != nil {
return nil, fmt.Errorf("failed parsing m3u8: %w", err)
}
var formats []*models.MediaFormat
if listType == m3u8.MASTER {
masterpl := playlist.(*m3u8.MasterPlaylist)
for _, variant := range masterpl.Variants {
if variant == nil || variant.URI == "" {
continue
}
width, height := int64(0), int64(0)
if variant.Resolution != "" {
var w, h int
if _, err := fmt.Sscanf(variant.Resolution, "%dx%d", &w, &h); err == nil {
width, height = int64(w), int64(h)
}
}
format := &models.MediaFormat{
Type: enums.MediaTypeVideo,
FormatID: fmt.Sprintf("hls-%d", variant.Bandwidth/1000),
VideoCodec: getCodecFromCodecs(variant.Codecs),
AudioCodec: getAudioCodecFromCodecs(variant.Codecs),
Bitrate: int64(variant.Bandwidth),
Width: width,
Height: height,
}
variantURL := resolveURL(baseURLObj, variant.URI)
format.URL = []string{variantURL}
variantContent, err := fetchContent(variantURL)
if err == nil {
variantFormats, err := ParseM3U8Content(variantContent, variantURL)
if err == nil && len(variantFormats) > 0 {
format.Segments = variantFormats[0].Segments
if variantFormats[0].Duration > 0 {
format.Duration = variantFormats[0].Duration
}
}
}
formats = append(formats, format)
}
return formats, nil
}
if listType == m3u8.MEDIA {
mediapl := playlist.(*m3u8.MediaPlaylist)
var segments []string
var totalDuration float64
for _, segment := range mediapl.Segments {
if segment != nil && segment.URI != "" {
segmentURL := segment.URI
if !strings.HasPrefix(segmentURL, "http://") && !strings.HasPrefix(segmentURL, "https://") {
segmentURL = resolveURL(baseURLObj, segmentURL)
}
segments = append(segments, segmentURL)
totalDuration += segment.Duration
}
}
format := &models.MediaFormat{
Type: enums.MediaTypeVideo,
FormatID: "hls",
VideoCodec: enums.MediaCodecAVC,
AudioCodec: enums.MediaCodecAAC,
Duration: int64(totalDuration),
URL: []string{baseURL},
Segments: segments,
}
return []*models.MediaFormat{format}, nil
}
return nil, errors.New("unsupported m3u8 playlist type")
}
func ParseM3U8FromURL(url string) ([]*models.MediaFormat, error) {
body, err := fetchContent(url)
if err != nil {
return nil, fmt.Errorf("failed to fetch m3u8 content: %w", err)
}
return ParseM3U8Content(body, url)
}
func fetchContent(url string) ([]byte, error) {
resp, err := httpClient.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to fetch content: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server returned status code: %d", resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
func getCodecFromCodecs(codecs string) enums.MediaCodec {
if strings.Contains(codecs, "avc") || strings.Contains(codecs, "h264") {
return enums.MediaCodecAVC
} else if strings.Contains(codecs, "hvc") || strings.Contains(codecs, "h265") {
return enums.MediaCodecHEVC
} else if strings.Contains(codecs, "av01") {
return enums.MediaCodecAV1
} else if strings.Contains(codecs, "vp9") {
return enums.MediaCodecVP9
} else if strings.Contains(codecs, "vp8") {
return enums.MediaCodecVP8
}
return enums.MediaCodecAVC
}
func getAudioCodecFromCodecs(codecs string) enums.MediaCodec {
if strings.Contains(codecs, "mp4a") {
return enums.MediaCodecAAC
} else if strings.Contains(codecs, "opus") {
return enums.MediaCodecOpus
} else if strings.Contains(codecs, "mp3") {
return enums.MediaCodecMP3
} else if strings.Contains(codecs, "flac") {
return enums.MediaCodecFLAC
} else if strings.Contains(codecs, "vorbis") {
return enums.MediaCodecVorbis
}
return enums.MediaCodecAAC
}
func resolveURL(base *url.URL, uri string) string {
if strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://") {
return uri
}
ref, err := url.Parse(uri)
if err != nil {
return uri
}
return base.ResolveReference(ref).String()
}

1
util/parser/mpd.go Normal file
View file

@ -0,0 +1 @@
package parser