Init
This commit is contained in:
parent
264c97183e
commit
3faede7b1c
74 changed files with 6228 additions and 1 deletions
182
util/parser/m3u8.go
Normal file
182
util/parser/m3u8.go
Normal 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
1
util/parser/mpd.go
Normal file
|
@ -0,0 +1 @@
|
|||
package parser
|
Loading…
Add table
Add a link
Reference in a new issue