Feat: emby support transcode

This commit is contained in:
zijiren233 2024-05-07 22:58:19 +08:00
parent b2b00ca3c0
commit 4cee111d04
11 changed files with 152 additions and 50 deletions

4
go.mod
View File

@ -33,10 +33,10 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/soheilhy/cmux v0.1.5
github.com/spf13/cobra v1.8.0
github.com/synctv-org/vendors v0.3.3-0.20240418153252-2198604d72f2
github.com/synctv-org/vendors v0.3.3-0.20240507143426-41159dcb43e3
github.com/ulule/limiter/v3 v3.11.2
github.com/zencoder/go-dash/v3 v3.0.3
github.com/zijiren233/gencontainer v0.0.0-20240331174346-b5e420773df7
github.com/zijiren233/gencontainer v0.0.0-20240507135401-7a9c8355bb2c
github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb
github.com/zijiren233/go-uhc v0.2.3
github.com/zijiren233/livelib v0.3.1

8
go.sum
View File

@ -365,8 +365,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/synctv-org/vendors v0.3.3-0.20240418153252-2198604d72f2 h1:EiooxruEJSuMlDe4PAD+mlMz8ChvSWeql4Z9kdc7/Rg=
github.com/synctv-org/vendors v0.3.3-0.20240418153252-2198604d72f2/go.mod h1:8ONGycZGgULyvajYbmjwGo8fEN6g9c+Q1lhoeDn6rqY=
github.com/synctv-org/vendors v0.3.3-0.20240507143426-41159dcb43e3 h1:+KV0W7224USi8KqXVG3aAmJQEAbumzLi9/UhwALvkAk=
github.com/synctv-org/vendors v0.3.3-0.20240507143426-41159dcb43e3/go.mod h1:8ONGycZGgULyvajYbmjwGo8fEN6g9c+Q1lhoeDn6rqY=
github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8=
github.com/tetratelabs/wazero v1.7.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@ -381,8 +381,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zencoder/go-dash/v3 v3.0.3 h1:xqwGJ2fJCSArwONGx6sY26Z1lxQ7zTURoxdRjCpuodM=
github.com/zencoder/go-dash/v3 v3.0.3/go.mod h1:30R5bKy1aUYY45yesjtZ9l8trNc2TwNqbS17WVQmCzk=
github.com/zijiren233/gencontainer v0.0.0-20240331174346-b5e420773df7 h1:ymsEhM4NrTiZx/nyJb5CQRVOmdgZm6L6hHcRUErXYVQ=
github.com/zijiren233/gencontainer v0.0.0-20240331174346-b5e420773df7/go.mod h1:V5oL7PrZxgisuLCblFWd89Jg99O8vM1n58llcxZ2hDY=
github.com/zijiren233/gencontainer v0.0.0-20240507135401-7a9c8355bb2c h1:xpQLguenF4bPTrbtCz+kMHpDtUDFIn/iZKd4VRfxe3Y=
github.com/zijiren233/gencontainer v0.0.0-20240507135401-7a9c8355bb2c/go.mod h1:V5oL7PrZxgisuLCblFWd89Jg99O8vM1n58llcxZ2hDY=
github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb h1:0DyOxf/TbbGodHhOVHNoPk+7v/YBJACs22gKpKlatWw=
github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb/go.mod h1:6TCzjDiQ8+5gWZiwsC3pnA5M0vUy2jV2Y7ciHJh729g=
github.com/zijiren233/go-uhc v0.2.3 h1:mBtTqLpdFzGSmeSYYUn+y0w1J+qolVd8UoS6h6ROyUs=

142
internal/cache/emby.go vendored
View File

@ -8,6 +8,7 @@ import (
"net/http"
"net/url"
log "github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/db"
"github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/vendor"
@ -23,6 +24,7 @@ type EmbyUserCacheData struct {
Host string
ServerID string
ApiKey string
UserID string
Backend string
}
@ -47,14 +49,16 @@ func EmbyAuthorizationCacheWithUserIDInitFunc(userID, serverID string) (*EmbyUse
Host: v.Host,
ServerID: v.ServerID,
ApiKey: v.ApiKey,
UserID: v.UserID,
Backend: v.Backend,
}, nil
}
type EmbySource struct {
URLs []struct {
URL string
Name string
URL string
IsTranscode bool
Name string
}
Subtitles []struct {
URL string
@ -65,13 +69,52 @@ type EmbySource struct {
}
type EmbyMovieCacheData struct {
Sources []EmbySource
Sources []EmbySource
TranscodeSessionID string
}
type EmbyMovieCache = refreshcache.RefreshCache[*EmbyMovieCacheData, *EmbyUserCache]
func NewEmbyMovieCache(movie *model.Movie) *EmbyMovieCache {
return refreshcache.NewRefreshCache(NewEmbyMovieCacheInitFunc(movie), 0)
cache := refreshcache.NewRefreshCache(NewEmbyMovieCacheInitFunc(movie), 0)
cache.SetClearFunc(NewEmbyMovieClearCacheFunc(movie))
return cache
}
func NewEmbyMovieClearCacheFunc(movie *model.Movie) func(ctx context.Context, args ...*EmbyUserCache) error {
return func(ctx context.Context, args ...*MapCache[*EmbyUserCacheData, struct{}]) error {
if !movie.Base.VendorInfo.Emby.Transcode {
return nil
}
serverID, _, err := model.GetEmbyServerIdFromPath(movie.Base.VendorInfo.Emby.Path)
if err != nil {
return err
}
oldVal, ok := ctx.Value(refreshcache.OldValKey).(*EmbyMovieCacheData)
if !ok {
return nil
}
aucd, err := args[0].LoadOrStore(ctx, serverID)
if err != nil {
return err
}
if aucd.Host == "" || aucd.ApiKey == "" {
return errors.New("not bind emby vendor")
}
cli := vendor.LoadEmbyClient(aucd.Backend)
_, err = cli.DeleteActiveEncodeings(ctx, &emby.DeleteActiveEncodeingsReq{
Host: aucd.Host,
Token: aucd.ApiKey,
PalySessionId: oldVal.TranscodeSessionID,
})
if err != nil {
log.Errorf("delete active encodeings: %v", err)
}
return nil
}
}
func NewEmbyMovieCacheInitFunc(movie *model.Movie) func(ctx context.Context, args ...*EmbyUserCache) (*EmbyMovieCacheData, error) {
@ -97,51 +140,72 @@ func NewEmbyMovieCacheInitFunc(movie *model.Movie) func(ctx context.Context, arg
if aucd.Host == "" || aucd.ApiKey == "" {
return nil, errors.New("not bind emby vendor")
}
cli := vendor.LoadEmbyClient(aucd.Backend)
data, err := cli.PlaybackInfo(ctx, &emby.PlaybackInfoReq{
Host: aucd.Host,
Token: aucd.ApiKey,
UserId: aucd.UserID,
ItemId: truePath,
})
if err != nil {
return nil, fmt.Errorf("playback info: %w", err)
}
var resp EmbyMovieCacheData = EmbyMovieCacheData{
Sources: make([]EmbySource, len(data.MediaSourceInfo)),
TranscodeSessionID: data.PlaySessionID,
}
u, err := url.Parse(aucd.Host)
if err != nil {
return nil, err
}
cli := vendor.LoadEmbyClient(aucd.Backend)
data, err := cli.GetItem(ctx, &emby.GetItemReq{
Host: aucd.Host,
Token: aucd.ApiKey,
ItemId: truePath,
})
if err != nil {
return nil, err
}
if data.IsFolder {
return nil, errors.New("path is dir")
}
var resp EmbyMovieCacheData = EmbyMovieCacheData{
Sources: make([]EmbySource, len(data.MediaSourceInfo)),
}
for i, v := range data.MediaSourceInfo {
if v.Container == "" {
continue
if movie.Base.VendorInfo.Emby.Transcode && v.TranscodingUrl != "" {
resp.Sources[i].URLs = append(resp.Sources[i].URLs, struct {
URL string
IsTranscode bool
Name string
}{
URL: fmt.Sprintf("%s/emby%s", aucd.Host, v.TranscodingUrl),
Name: v.Name,
IsTranscode: true,
})
} else if v.DirectPlayUrl != "" {
resp.Sources[i].URLs = append(resp.Sources[i].URLs, struct {
URL string
IsTranscode bool
Name string
}{
URL: fmt.Sprintf("%s/emby%s", aucd.Host, v.DirectPlayUrl),
Name: v.Name,
})
} else {
if v.Container == "" {
continue
}
result, err := url.JoinPath("emby", "Videos", truePath, fmt.Sprintf("stream.%s", v.Container))
if err != nil {
return nil, err
}
u.Path = result
query := url.Values{}
query.Set("api_key", aucd.ApiKey)
query.Set("Static", "true")
query.Set("MediaSourceId", v.Id)
u.RawQuery = query.Encode()
resp.Sources[i].URLs = append(resp.Sources[i].URLs, struct {
URL string
IsTranscode bool
Name string
}{
URL: u.String(),
Name: v.Name,
})
}
result, err := url.JoinPath("emby", "Videos", data.Id, fmt.Sprintf("stream.%s", v.Container))
if err != nil {
return nil, err
}
u.Path = result
query := url.Values{}
query.Set("api_key", aucd.ApiKey)
query.Set("Static", "true")
query.Set("MediaSourceId", v.Id)
u.RawQuery = query.Encode()
resp.Sources[i].URLs = append(resp.Sources[i].URLs, struct {
URL string
Name string
}{
URL: u.String(),
Name: v.Name,
})
for _, msi := range v.MediaStreamInfo {
switch msi.Type {
case "Subtitle":
subtutleType := "srt"
result, err = url.JoinPath("emby", "Videos", data.Id, v.Id, "Subtitles", fmt.Sprintf("%d", msi.Index), fmt.Sprintf("Stream.%s", subtutleType))
result, err := url.JoinPath("emby", "Videos", truePath, v.Id, "Subtitles", fmt.Sprintf("%d", msi.Index), fmt.Sprintf("Stream.%s", subtutleType))
if err != nil {
return nil, err
}

View File

@ -136,7 +136,8 @@ func (a *AlistStreamingInfo) AfterFind(tx *gorm.DB) error {
type EmbyStreamingInfo struct {
// {/}serverId/ItemId
Path string `gorm:"type:varchar(52)" json:"path,omitempty"`
Path string `gorm:"type:varchar(52)" json:"path,omitempty"`
Transcode bool `json:"transcode,omitempty"`
}
func GetEmbyServerIdFromPath(path string) (serverID string, filePath string, err error) {

View File

@ -1,6 +1,7 @@
package op
import (
"context"
"errors"
"fmt"
"hash/crc32"
@ -56,7 +57,7 @@ func (m *Movie) CheckExpired(expireId uint64) bool {
}
}
func (m *Movie) ClearCache() {
func (m *Movie) ClearCache() error {
m.alistCache.Store(nil)
bmc := m.bilibiliCache.Swap(nil)
@ -64,7 +65,19 @@ func (m *Movie) ClearCache() {
bmc.NoSharedMovie.Clear()
}
m.embyCache.Store(nil)
emc := m.embyCache.Swap(nil)
if emc != nil {
u, err := LoadOrInitUserByID(m.CreatorID)
if err != nil {
return err
}
err = emc.Clear(context.Background(), u.Value().EmbyCache())
if err != nil {
return err
}
}
return nil
}
func (m *Movie) AlistCache() *cache.AlistMovieCache {

View File

@ -387,6 +387,17 @@ func (r *Room) CheckCurrentExpired(expireId uint64) (bool, error) {
}
func (r *Room) SetCurrentMovie(movieID string, play bool) error {
currentMovie, err := r.CurrentMovie()
if err != nil {
if err != ErrNoCurrentMovie {
return err
}
} else {
err = currentMovie.ClearCache()
if err != nil {
return fmt.Errorf("clear cache failed: %w", err)
}
}
if movieID == "" {
r.current.SetMovie("", false, play)
return nil

View File

@ -78,3 +78,11 @@ func (e *grpcEmby) Logout(ctx context.Context, req *emby.LogoutReq) (*emby.Empty
func (e *grpcEmby) Me(ctx context.Context, req *emby.MeReq) (*emby.MeResp, error) {
return e.client.Me(ctx, req)
}
func (e *grpcEmby) PlaybackInfo(ctx context.Context, req *emby.PlaybackInfoReq) (*emby.PlaybackInfoResp, error) {
return e.client.PlaybackInfo(ctx, req)
}
func (e *grpcEmby) DeleteActiveEncodeings(ctx context.Context, req *emby.DeleteActiveEncodeingsReq) (*emby.Empty, error) {
return e.client.DeleteActiveEncodeings(ctx, req)
}

View File

@ -1040,6 +1040,10 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("id out of range"))
return
}
if embyC.Sources[source].URLs[id].IsTranscode {
ctx.Redirect(http.StatusFound, embyC.Sources[source].URLs[id].URL)
return
}
err = proxyURL(ctx, embyC.Sources[source].URLs[id].URL, nil)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)

View File

@ -115,7 +115,7 @@ func Logout(ctx *gin.Context) {
}
if rc, ok := user.AlistCache().LoadCache(req.ServerID); ok {
rc.Clear()
rc.Clear(ctx)
}
ctx.Status(http.StatusNoContent)

View File

@ -233,6 +233,6 @@ func Logout(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
user.BilibiliCache().Clear()
user.BilibiliCache().Clear(ctx)
ctx.Status(http.StatusNoContent)
}

View File

@ -136,6 +136,7 @@ EmbyFSListResp:
Host: aucd.Host,
Path: req.Path,
Token: aucd.ApiKey,
UserId: aucd.UserID,
Limit: uint64(size),
StartIndex: uint64((page - 1) * size),
SearchTerm: req.Keywords,