Release v0.2.34
This commit is contained in:
@@ -373,6 +373,21 @@ func (c *Client) SetPlayed(ctx context.Context, cred Credentials, itemID string,
|
||||
return c.userDataCall(ctx, method, path, cred)
|
||||
}
|
||||
|
||||
// HideFromResume removes an item from resume and next-up feeds without marking it played.
|
||||
func (c *Client) HideFromResume(ctx context.Context, cred Credentials, itemID string) (json.RawMessage, error) {
|
||||
params := url.Values{"Hide": {"true"}}
|
||||
path := "/Users/" + url.PathEscape(cred.UserID) + "/Items/" + url.PathEscape(itemID) + "/HideFromResume"
|
||||
req, err := c.newRequest(ctx, http.MethodPost, path, params, cred, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var raw json.RawMessage
|
||||
if err := c.do(req, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// RefreshItem asks Emby to re-scan one item's files.
|
||||
//
|
||||
// It exists for the subtitle download: Bazarr writes the new .srt beside the media file
|
||||
@@ -501,6 +516,43 @@ func (c *Client) TrickplayBytes(
|
||||
return io.ReadAll(io.LimitReader(resp.Body, to-from+1))
|
||||
}
|
||||
|
||||
// MaxSubtitleBytes caps a subtitle read. A feature-length SubRip file is tens of
|
||||
// kilobytes; a megabyte is far past anything genuine and is what an Emby that answered
|
||||
// with the media file instead would send.
|
||||
const MaxSubtitleBytes = 4 << 20
|
||||
|
||||
// SubtitleBytes reads a text subtitle track's content.
|
||||
//
|
||||
// This is the one place the gateway pulls a subtitle *out* of Emby rather than pointing
|
||||
// the television at it, and it exists so a track can be used as a timing reference. SubRip
|
||||
// is asked for because that is what the sync reads and writes; Emby converts from whatever
|
||||
// the container holds.
|
||||
func (c *Client) SubtitleBytes(
|
||||
ctx context.Context, cred Credentials, itemID, mediaSourceID string, index int,
|
||||
) ([]byte, error) {
|
||||
if mediaSourceID == "" {
|
||||
mediaSourceID = itemID
|
||||
}
|
||||
path := fmt.Sprintf(
|
||||
"/Videos/%s/%s/Subtitles/%d/Stream.srt",
|
||||
url.PathEscape(itemID), url.PathEscape(mediaSourceID), index,
|
||||
)
|
||||
req, err := c.newRequest(ctx, http.MethodGet, path, nil, cred, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 400 {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return nil, &APIError{StatusCode: resp.StatusCode, Body: string(body)}
|
||||
}
|
||||
return io.ReadAll(io.LimitReader(resp.Body, MaxSubtitleBytes))
|
||||
}
|
||||
|
||||
// StreamURL is the direct-play URL handed to the TV. It points at the *public* Emby
|
||||
// address: video never flows through the gateway, only metadata does.
|
||||
func (c *Client) StreamURL(cred Credentials, itemID string) string {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package emby
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHideFromResumeUsesDedicatedEmbyEndpoint(t *testing.T) {
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "unexpected method: "+r.Method, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if r.URL.Path != "/Users/user-1/Items/item-2/HideFromResume" {
|
||||
http.Error(w, "unexpected path: "+r.URL.Path, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if r.URL.Query().Get("Hide") != "true" {
|
||||
http.Error(w, "Hide must be true", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"Played":false,"PlaybackPositionTicks":0}`))
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
client := New(upstream.URL, upstream.URL, "MbyATV", time.Second)
|
||||
got, err := client.HideFromResume(
|
||||
context.Background(),
|
||||
Credentials{UserID: "user-1", Token: "token"},
|
||||
"item-2",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(got) != `{"Played":false,"PlaybackPositionTicks":0}` {
|
||||
t.Fatalf("response = %s", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user