stremio

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 27, 2020 License: AGPL-3.0 Imports: 19 Imported by: 4

README

go-stremio

Stremio addon SDK for Go

Contents

  1. Introduction
  2. About this SDK
  3. Features
  4. Example
  5. Advantages
  6. Related projects

Introduction

Stremio is a modern media center that's a one-stop solution for your video entertainment. You discover, watch and organize video content from easy to install addons.

These addons run remotely as a web service, so they can't do any harm to your computer. This is different from how addons work for Kodi for example, where they run locally on your computer and have dozens of third party dependencies. There have been several security incidents with Kodi addons like this one and even the Kodi developers themselves warn of the dangers of third party Kodi addons.

About this SDK

When developing a Stremio addon, you're essentially developing a web service. But there are some defined routes, expected behavior, JSON structure etc., so instead of having to figure all of this out on your own before you've got even a basic addon running, using an SDK can get you up to speed much faster, as it takes care of all of this automatically.

But the official Stremio addon SDK is for Node.js only.

This SDK is for Go!

It provides the most important parts of the Node.js SDK and depending on the requirements of you, the libary users, it will be extended to provide more in the future.

Features

  • All required types for building catalog and stream addons
  • Web server with graceful shutdown
  • CORS middleware to allow requests from Stremio
  • Health check endpoint
  • Optional profiling endpoints (for go pprof)
  • Optional request logging
  • Cache control and ETag handling

Upcoming features:

  • Custom user data in URLs
  • Custom service endpoints

Current non-features, as they're usually part of a reverse proxy deployed in front of the service:

  • TLS termination (for using HTTPS)
  • Rate limiting (against DoS attacks)
  • Compression (like gzip)

Example

Full examples can be found in examples. Here's a part of the one for a stream addon:

package main

import (
    "github.com/deflix-tv/go-stremio"
)

var (
    manifest = stremio.Manifest{
        ID:          "com.example.blender-streams",
        Name:        "Blender movie streams",
        Description: "Stream addon for free movies that were made with Blender",
        // ...
    }
)

func main() {
    streamHandlers := map[string]stremio.StreamHandler{"movie": movieHandler}

    addon, err := stremio.NewAddon(manifest, nil, streamHandlers, stremio.DefaultOptions)
    if err != nil {
        addon.Logger().Sugar().Fatalf("Couldn't create addon: %v", err)
    }

    addon.Run()
}

func movieHandler(id string) ([]stremio.StreamItem, error) {
    // We only serve Big Buck Bunny and Sintel
    if id == "tt1254207" {
        return []stremio.StreamItem{
            // Torrent stream
            {
                InfoHash: "dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c",
                // Stremio recommends to set the quality as title, as the streams
                // are shown for a specific movie so the user knows the title.
                Title:     "1080p (torrent)",
                FileIndex: 1,
            },
            // HTTP stream
            {
                URL:   "http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_normal.mp4",
                Title: "1080p (HTTP stream)",
            },
        }, nil
    } else if id == "tt1727587" {
        // ...
    }
    return nil, stremio.NotFound
}

Advantages

Some reasons why you might want to consider developing an addon in Go with this SDK:

Criterium Node.js addon Go addon
Direct SDK dependencies 9 5
Transitive SDK dependencies 85 8
Size of a runnable addon 27 MB¹ 15 MB
Number of artifacts to deploy depends² 1
Runtime dependencies Node.js -
Concurrency Single-threaded Multi-threaded

¹) du -h --max-depth=0 node_modules
²) All your JavaScript files and the package.json if you can install the depencencies with npm on the server, otherwise (like in a Docker container) you also need all the node_modules, which are hundreds to thousands of files.

Looking at the performance it depends a lot on what your addon does. Due to the single-threaded nature of Node.js, the more CPU-bound tasks your addon does, the bigger the performance difference will be (in favor of Go). Here we compare the simplest possible addon to be able to compare just the SDKs and not any additional overhead (like DB access):

Criterium Node.js addon Go addon
Startup time to 1st request¹ 150-230ms 5-20ms
Max rps² @ 1000 connections Local³: 6,000
Remote⁴: 3,000
Local³: 59,000
Remote⁴: 58,000
Memory usage @ 1000 connections Idle: 35 MB
Load⁵: 70 MB
Idle: 10 MB
Load⁵: 45 MB

¹) Measured using ttfok and the code in benchmark. This metric is relevant in case you want to use a "serverless functions" service (like AWS Lambda or Vercel (former ZEIT Now)) that doesn't keep your service running between requests.
²) Max number of requests per second where the p99 latency is still < 100ms
³) The load testing tool ran on a different server, but in the same datacenter and the requests were sent within a private network
⁴) The load testing tool ran on a different server in a different datacenter of another cloud provider in another city for more real world-like circumstances
⁵) At a request rate half of what we measured as maximum

The load tests were run under the following circumstances:

  • We used the addon code, load testing tool and setup described in benchmark
  • We ran the service on a DigitalOcean "Droplet" with 2 cores and 2 GB RAM, which costs $15/month
  • The load tests ran for 60s, with previous warmup
  • Virtualized servers of cloud providers vary in performance throughout the week and day, even when using the exact same machines, because the CPU cores of the virtualization host are shared between multiple VPS. We conducted the Node.js and Go service tests at the same time so their performance difference doesn't come from the performance variation due to running at different times.
  • The client server used to run the load testing tool was high-powered (4-8 dedicated cores, 8-32 GB RAM)

Additional observations:

  • The Go service's response times were generally lower across all request rates
  • The Go service's response times had a much lower deviation, i.e. they were more stable. With less than 60s of time for the load test the Node.js service fared even worse, because outliers lead to a higher p99 latency.
  • We also tested on a lower-powered server by a cheap cloud provider (also 2 core, 2 GB RAM, but the CPU was generally worse). In this case the difference between the Node.js and the Go service was even higher. The Go service is perfectly fitted for scaling out with multiple cheap servers.

Note:

  • This Go SDK is at its very beginning. Some features will be added in the future that might decrease its performance, while others will increase it.
  • The Node.js addon was run as a single instance. You can do more complex deployments with a load balancer like HAProxy and multiple instances of the same Node.js service on a single machine to take advantage of multiple CPU cores. But then you should also activate preforking in the Go addon for using several OS processes in parallel, which we didn't do.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultOptions = Options{
	BindAddr: "localhost",
	Port:     8080,
	LogLevel: "info",
}

DefaultOptions is an Options object with default values. For fields that aren't set here the zero value is the default value.

View Source
var (
	// NotFound signals that the catalog/meta/stream was not found.
	// It leads to a "404 Not Found" response.
	NotFound = errors.New("Not found")
)

Functions

This section is empty.

Types

type Addon

type Addon struct {
	// contains filtered or unexported fields
}

Addon represents a remote addon. You can create one with NewAddon() and then run it with Run().

func NewAddon

func NewAddon(manifest Manifest, catalogHandlers map[string]CatalogHandler, streamHandlers map[string]StreamHandler, opts Options) (Addon, error)

NewAddon creates a new Addon object that can be started with Run(). A proper manifest must be supplied, but all but one handler can be nil in case you only want to handle specific requests and opts can be the zero value of Options.

func (Addon) Logger added in v0.2.0

func (a Addon) Logger() *zap.Logger

Logger returns the addon's logger. It's recommended to use this logger for logging in addons so that the logging output is consistent. You can also change its configuration this way, as it's a pointer to the logger that's used by the SDK.

func (Addon) Run

func (a Addon) Run()

Run starts the remote addon. It sets up an HTTP server that handles requests to "/manifest.json" etc. and gracefully handles shutdowns.

type BehaviorHints

type BehaviorHints struct {
	// Note: Must include `omitempty`, otherwise it will be included if this struct is used in another one, even if the field of the containing struct is marked as `omitempty`
	Adult bool `json:"adult,omitempty"`
	P2P   bool `json:"p2p,omitempty"`
}

type CatalogHandler

type CatalogHandler func(id string) ([]MetaPreviewItem, error)

CatalogHandler is the callback for catalog requests for a specific type (like "movie"). The id parameter is the catalog ID that you specified yourself in the CatalogItem objects in the Manifest.

type CatalogItem

type CatalogItem struct {
	Type string `json:"type"`
	ID   string `json:"id"`
	Name string `json:"name"`

	// Optional
	Extra []ExtraItem `json:"extra,omitempty"`
}

CatalogItem represents a catalog.

type ExtraItem

type ExtraItem struct {
	Name string `json:"name"`

	// Optional
	IsRequired   bool     `json:"isRequired,omitempty"`
	Options      []string `json:"options,omitempty"`
	OptionsLimit int      `json:"optionsLimit,omitempty"`
}

type Manifest

type Manifest struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
	Version     string `json:"version"`

	// One of the following is required
	// Note: Can only have one in code because of how Go (de-)serialization works
	//Resources     []string       `json:"resources,omitempty"`
	ResourceItems []ResourceItem `json:"resources,omitempty"`

	Types    []string      `json:"types"`
	Catalogs []CatalogItem `json:"catalogs"`

	// Optional
	IDprefixes    []string      `json:"idPrefixes,omitempty"`
	Background    string        `json:"background,omitempty"` // URL
	ContactEmail  string        `json:"contactEmail,omitempty"`
	BehaviorHints BehaviorHints `json:"behaviorHints,omitempty"`
}

Manifest describes the capabilities of the addon. See https://github.com/Stremio/stremio-addon-sdk/blob/f6f1f2a8b627b9d4f2c62b003b251d98adadbebe/docs/api/responses/manifest.md

type MetaItem

type MetaItem struct {
	ID   string `json:"id"`
	Type string `json:"type"`
	Name string `json:"name"`

	// Optional
	Links       []MetaLinkItem `json:"links,omitempty"`  // For genres, director, cast, etc.
	Poster      string         `json:"poster,omitempty"` // URL
	PosterShape string         `json:"posterShape,omitempty"`
	Background  string         `json:"background,omitempty"` // URL
	Description string         `json:"description,omitempty"`
	ReleaseInfo string         `json:"releaseInfo,omitempty"`
	IMDbRating  string         `json:"imdbRating,omitempty"`
	Released    string         `json:"released,omitempty"` // Must be ISO 8601, e.g. "2010-12-06T05:00:00.000Z"
	Videos      []VideoItem    `json:"videos,omitempty"`
	Runtime     string         `json:"runtime,omitempty"`
	Language    string         `json:"language,omitempty"`
	Country     string         `json:"country,omitempty"`
	Awards      string         `json:"awards,omitempty"`
	Website     string         `json:"website,omitempty"` // URL

}

MetaItem represents a meta item and is meant to be used when info for a specific item was requested. See https://github.com/Stremio/stremio-addon-sdk/blob/f6f1f2a8b627b9d4f2c62b003b251d98adadbebe/docs/api/responses/meta.md

type MetaLinkItem

type MetaLinkItem struct {
	Name     string `json:"name"`
	Category string `json:"category"`
	URL      string `json:"url"` //  // URL. Can be "Meta Links" (see https://github.com/Stremio/stremio-addon-sdk/blob/f6f1f2a8b627b9d4f2c62b003b251d98adadbebe/docs/api/responses/meta.links.md)
}

type MetaPreviewItem

type MetaPreviewItem struct {
	ID     string `json:"id"`
	Type   string `json:"type"`
	Name   string `json:"name"`
	Poster string `json:"poster"` // URL

	// Optional
	PosterShape string `json:"posterShape,omitempty"`

	// Optional, used for the "Discover" page sidebar
	Links       []MetaLinkItem `json:"links,omitempty"` // For genres, director, cast, etc.
	IMDbRating  string         `json:"imdbRating,omitempty"`
	ReleaseInfo string         `json:"releaseInfo,omitempty"`
	Description string         `json:"description,omitempty"`
}

MetaPreviewItem represents a meta preview item and is meant to be used within catalog responses. See https://github.com/Stremio/stremio-addon-sdk/blob/f6f1f2a8b627b9d4f2c62b003b251d98adadbebe/docs/api/responses/meta.md#meta-preview-object

type Options

type Options struct {
	// The interface to bind to.
	// "0.0.0.0" to bind to all interfaces. "localhost" to *exclude* requests from other machines.
	// Default "localhost".
	BindAddr string
	// The port to listen on.
	// Default 8080.
	Port int
	// The log level.
	// Only logs with the same or a higher log level will be shown.
	// For example when you set it to "info", info, warn and error logs will be shown, but no debug logs.
	// Accepts "debug", "info", "warn" and "error".
	// Default "info".
	LogLevel string
	// URL to redirect to when someone requests the root of the handler instead of the manifest, catalog, stream etc.
	// When no value is set, it will lead to a "404 Not Found" response.
	// Default "".
	RedirectURL string
	// Flag for indicating whether requests should be logged.
	// Default false (meaning requests will be logged by default).
	DisableRequestLogging bool
	// Flag for indicating whether you want to expose URL handlers for the Go profiler.
	// The URLs are be the standard ones: "/debug/pprof/...".
	// Default false.
	Profiling bool
	// Duration of client/proxy-side cache for responses from the catalog endpoint.
	// Helps reducing number of requsts and transferred data volume to/from the server.
	// The result is not cached by the SDK on the server side, so if two *separate* users make a reqeust,
	// and no proxy cached the response, your CatalogHandler will be called twice.
	// Default 0.
	CacheAgeCatalogs time.Duration
	// Same as CacheAgeCatalogs, but for streams.
	CacheAgeStreams time.Duration
	// Flag for indicating to proxies whether they are allowed to cache responses from the catalog endpoint.
	// Default false.
	CachePublicCatalogs bool
	// Same as CachePublicCatalogs, but for streams.
	CachePublicStreams bool
	// Flag for indicating whether the "ETag" header should be set and the "If-None-Match" header checked.
	// Helps reducing the transferred data volume from the server even further.
	// Only makes sense when setting a non-zero CacheAgeCatalogs.
	// Leads to a slight computational overhead due to every CatalogHandler result being hashed.
	// Default false.
	HandleEtagCatalogs bool
	// Same as HandleEtagCatalogs, but for streams.
	HandleEtagStreams bool
}

Options are the options that can be used to configure the addon.

type ResourceItem

type ResourceItem struct {
	Name  string   `json:"name"`
	Types []string `json:"types"`

	// Optional
	IDprefixes []string `json:"idPrefixes,omitempty"`
}

type StreamHandler

type StreamHandler func(id string) ([]StreamItem, error)

StreamHandler is the callback for stream requests for a specific type (like "movie"). The id parameter can be for example an IMDb ID if your addon handles the "movie" type.

type StreamItem

type StreamItem struct {
	// One of the following is required
	URL         string `json:"url,omitempty"` // URL
	YoutubeID   string `json:"ytId,omitempty"`
	InfoHash    string `json:"infoHash,omitempty"`
	ExternalURL string `json:"externalUrl,omitempty"` // URL

	// Optional
	Title     string `json:"title,omitempty"`   // Usually used for stream quality
	FileIndex uint8  `json:"fileIdx,omitempty"` // Only when using InfoHash

}

StreamItem represents a stream for a MetaItem. See https://github.com/Stremio/stremio-addon-sdk/blob/f6f1f2a8b627b9d4f2c62b003b251d98adadbebe/docs/api/responses/stream.md

type VideoItem

type VideoItem struct {
	ID       string `json:"id"`
	Title    string `json:"title"`
	Released string `json:"released"` // Must be ISO 8601, e.g. "2010-12-06T05:00:00.000Z"

	// Optional
	Thumbnail string       `json:"thumbnail,omitempty"` // URL
	Streams   []StreamItem `json:"streams,omitempty"`
	Available bool         `json:"available,omitempty"`
	Episode   string       `json:"episode,omitempty"`
	Season    string       `json:"season,omitempty"`
	Trailer   string       `json:"trailer,omitempty"` // Youtube ID
	Overview  string       `json:"overview,omitempty"`
}

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL