Init V4 community edition (#2265)
* Init V4 community edition * Init V4 community edition
This commit is contained in:
245
pkg/mediameta/ffprobe.go
Normal file
245
pkg/mediameta/ffprobe.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package mediameta
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager/entitysource"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
ffprobeExts = []string{
|
||||
"mp3", "m4a", "ogg", "flac", "3g2", "3gp", "asf", "asx", "avi", "divx", "flv", "m2ts", "m2v", "m4v", "mkv", "mov", "mp4",
|
||||
"mpeg", "mpg", "mts", "mxf", "ogv", "rm", "swf", "webm", "wmv",
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
FFProbeMeta struct {
|
||||
Format *Format `json:"format"`
|
||||
Streams []Stream `json:"streams"`
|
||||
Chapters []Chapter `json:"chapters"`
|
||||
}
|
||||
|
||||
Stream struct {
|
||||
Index int `json:"index"`
|
||||
CodecName string `json:"codec_name"`
|
||||
CodecLongName string `json:"codec_long_name"`
|
||||
CodecType string `json:"codec_type"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Duration string `json:"duration"`
|
||||
Bitrate string `json:"bit_rate"`
|
||||
}
|
||||
Chapter struct {
|
||||
Id int `json:"id"`
|
||||
StartTime string `json:"start_time"`
|
||||
EndTime string `json:"end_time"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
}
|
||||
Format struct {
|
||||
FormatName string `json:"format_name"`
|
||||
FormatLongName string `json:"format_long_name"`
|
||||
Duration string `json:"duration"`
|
||||
Bitrate string `json:"bit_rate"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
UrlExpire = time.Duration(60) * time.Hour
|
||||
StreamMediaFormat = "format"
|
||||
StreamMediaFormatLong = "formatLong"
|
||||
StreamMediaDuration = "duration"
|
||||
StreamMediaBitrate = "bitrate"
|
||||
StreamMediaStreamPrefix = "stream_"
|
||||
StreamMediaChapterPrefix = "chapter_"
|
||||
StreamMediaCodec = "codec"
|
||||
StreamMediaCodecLongName = "codec_long_name"
|
||||
StreamMediaWidth = "width"
|
||||
StreamMediaHeight = "height"
|
||||
StreamMediaStartTime = "start_time"
|
||||
StreamMediaEndTime = "end_time"
|
||||
StreamMediaChapterName = "name"
|
||||
StreamMetaTitle = "title"
|
||||
StreamMetaDescription = "description"
|
||||
)
|
||||
|
||||
func newFFProbeExtractor(settings setting.Provider, l logging.Logger) *ffprobeExtractor {
|
||||
return &ffprobeExtractor{
|
||||
l: l,
|
||||
settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
type ffprobeExtractor struct {
|
||||
settings setting.Provider
|
||||
l logging.Logger
|
||||
}
|
||||
|
||||
func (f *ffprobeExtractor) Exts() []string {
|
||||
return ffprobeExts
|
||||
}
|
||||
|
||||
func (f *ffprobeExtractor) Extract(ctx context.Context, ext string, source entitysource.EntitySource) ([]driver.MediaMeta, error) {
|
||||
localLimit, remoteLimit := f.settings.MediaMetaFFProbeSizeLimit(ctx)
|
||||
if err := checkFileSize(localLimit, remoteLimit, source); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var input string
|
||||
if source.IsLocal() {
|
||||
input = source.LocalPath(ctx)
|
||||
} else {
|
||||
expire := time.Now().Add(UrlExpire)
|
||||
srcUrl, err := source.Url(driver.WithForcePublicEndpoint(ctx, false), entitysource.WithNoInternalProxy(), entitysource.WithExpire(&expire))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get entity url: %w", err)
|
||||
}
|
||||
input = srcUrl.Url
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx,
|
||||
f.settings.MediaMetaFFProbePath(ctx),
|
||||
"-v", "quiet",
|
||||
"-print_format", "json",
|
||||
"-show_format",
|
||||
"-show_streams",
|
||||
"-show_chapters",
|
||||
input,
|
||||
)
|
||||
|
||||
res, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to invoke ffprobe: %w", err)
|
||||
}
|
||||
|
||||
f.l.Debug("ffprobe output: %s", res)
|
||||
var meta FFProbeMeta
|
||||
if err := json.Unmarshal(res, &meta); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse ffprobe output: %w", err)
|
||||
}
|
||||
|
||||
return ProbeMetaTransform(&meta), nil
|
||||
}
|
||||
|
||||
func ProbeMetaTransform(meta *FFProbeMeta) []driver.MediaMeta {
|
||||
if meta.Format == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
res := []driver.MediaMeta{}
|
||||
if meta.Format.FormatName != "" {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: StreamMediaFormat,
|
||||
Value: meta.Format.FormatName,
|
||||
})
|
||||
}
|
||||
if meta.Format.FormatLongName != "" {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: StreamMediaFormatLong,
|
||||
Value: meta.Format.FormatLongName,
|
||||
})
|
||||
}
|
||||
if meta.Format.Duration != "" {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: StreamMediaDuration,
|
||||
Value: meta.Format.Duration,
|
||||
})
|
||||
}
|
||||
if meta.Format.Bitrate != "" {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: StreamMediaBitrate,
|
||||
Value: meta.Format.Bitrate,
|
||||
})
|
||||
}
|
||||
|
||||
for _, stream := range meta.Streams {
|
||||
keyPrefix := fmt.Sprintf("%s%d_%s_", StreamMediaStreamPrefix, stream.Index, stream.CodecType)
|
||||
if stream.CodecName != "" {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: keyPrefix + StreamMediaCodec,
|
||||
Value: stream.CodecName,
|
||||
})
|
||||
}
|
||||
if stream.CodecLongName != "" {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: keyPrefix + StreamMediaCodecLongName,
|
||||
Value: stream.CodecLongName,
|
||||
})
|
||||
}
|
||||
if stream.Width > 0 {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: keyPrefix + StreamMediaWidth,
|
||||
Value: strconv.Itoa(stream.Width),
|
||||
})
|
||||
}
|
||||
if stream.Height > 0 {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: keyPrefix + StreamMediaHeight,
|
||||
Value: strconv.Itoa(stream.Height),
|
||||
})
|
||||
}
|
||||
if stream.Duration != "" {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: keyPrefix + StreamMediaDuration,
|
||||
Value: stream.Duration,
|
||||
})
|
||||
}
|
||||
if stream.Bitrate != "" {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: keyPrefix + StreamMediaBitrate,
|
||||
Value: stream.Bitrate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, chapter := range meta.Chapters {
|
||||
keyPrefix := fmt.Sprintf("%s%d_", StreamMediaChapterPrefix, chapter.Id)
|
||||
if chapter.StartTime != "" {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: keyPrefix + StreamMediaStartTime,
|
||||
Value: chapter.StartTime,
|
||||
})
|
||||
}
|
||||
if chapter.EndTime != "" {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: keyPrefix + StreamMediaEndTime,
|
||||
Value: chapter.EndTime,
|
||||
})
|
||||
}
|
||||
if title, ok := chapter.Tags["title"]; ok {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: keyPrefix + StreamMediaChapterName,
|
||||
Value: title,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if title, ok := meta.Format.Tags["title"]; ok {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: StreamMetaTitle,
|
||||
Value: title,
|
||||
})
|
||||
}
|
||||
|
||||
if description, ok := meta.Format.Tags["description"]; ok {
|
||||
res = append(res, driver.MediaMeta{
|
||||
Key: StreamMetaDescription,
|
||||
Value: description[0:min(len(description), 255)],
|
||||
})
|
||||
}
|
||||
|
||||
for i := 0; i < len(res); i++ {
|
||||
res[i].Type = driver.MetaTypeStreamMedia
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
Reference in New Issue
Block a user