git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/bunny/stream.go (about)

     1  package bunny
     2  
     3  import (
     4  	"context"
     5  	"crypto/sha256"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/url"
    11  	"strconv"
    12  	"time"
    13  )
    14  
    15  type VideoStatus int64
    16  
    17  const (
    18  	VideoStatusCreated = iota
    19  	VideoStatusUploaded
    20  	VideoStatusProcessing
    21  	VideoStatusTranscoding
    22  	// Video is ready
    23  	VideoStatusFinished
    24  	VideoStatusError
    25  	VideoStatusUploadFailed
    26  )
    27  
    28  type FetchVideoInput struct {
    29  	Url string `json:"url"`
    30  }
    31  
    32  type FetchVideoOutput struct {
    33  	Success    bool   `json:"success"`
    34  	Message    string `json:"message"`
    35  	StatusCode int64  `json:"statusCode"`
    36  
    37  	ID string `json:"id"`
    38  }
    39  
    40  type UploadVideoOutput struct {
    41  	Success    bool   `json:"success"`
    42  	Message    string `json:"message"`
    43  	StatusCode int64  `json:"statusCode"`
    44  }
    45  
    46  // See https://docs.bunny.net/reference/video_getvideo for details
    47  type Video struct {
    48  	VideoLibraryID int64  `json:"videoLibraryId"`
    49  	Guid           string `json:"guid"`
    50  	Title          string `json:"title"`
    51  	// "dateUploaded": "2023-08-17T12:45:11.381",
    52  	Views    int64 `json:"views"`
    53  	IsPublic bool  `json:"isPublic"`
    54  	// Length is the duration of the video in seconds
    55  	Length    int64       `json:"length"`
    56  	Status    VideoStatus `json:"status"`
    57  	Framerate float64     `json:"framerate"`
    58  	// "rotation": 0,
    59  	Width  int64 `json:"width"`
    60  	Height int64 `json:"height"`
    61  	// The available resolutions of the video. "360p,720p,1080p" for example
    62  	AvailableResolutions string `json:"availableResolutions"`
    63  	ThumbnailCount       int64  `json:"thumbnailCount"`
    64  	// The current encode progress of the video
    65  	EncodeProgress int64 `json:"encodeProgress"`
    66  	// The amount of storage used by this video
    67  	StorageSize int64 `json:"storageSize"`
    68  	// "captions": [],
    69  	HasMP4Fallback bool `json:"hasMP4Fallback"`
    70  	// The ID of the collection where the video belongs. Can be empty.
    71  	CollectionID string `json:"collectionId"`
    72  	// The file name of the thumbnail inside of the storage
    73  	ThumbnailFileName string `json:"thumbnailFileName"`
    74  	// The average watch time of the video in seconds
    75  	AverageWatchTime int64 `json:"averageWatchTime"`
    76  	// The total video watch time in seconds
    77  	TotalWatchTime int64 `json:"totalWatchTime"`
    78  	// The automatically detected category of the video. Default: "unknown"
    79  	Category string         `json:"category"`
    80  	Chapters []VideoChapter `json:"chapters"`
    81  	Moments  []VideoMoment  `json:"moments"`
    82  	MetaTags []VideoMetaTag `json:"metaTags"`
    83  	// "transcodingMessages": []
    84  }
    85  
    86  type VideoChapter struct {
    87  	Title string `json:"title"`
    88  	Start int64  `json:"start"`
    89  	End   int64  `json:"end"`
    90  }
    91  
    92  type VideoMoment struct {
    93  	Label     string `json:"label"`
    94  	Timestamp int64  `json:"timestamp"`
    95  }
    96  
    97  type VideoMetaTag struct {
    98  	Property string `json:"property"`
    99  	Value    string `json:"value"`
   100  }
   101  
   102  type UpdateVideoInput struct {
   103  	LibraryID    string         `json:"-"`
   104  	VideoID      string         `json:"-"`
   105  	Title        *string        `json:"title,omitempty"`
   106  	CollectionID *string        `json:"collectionId,omitempty"`
   107  	Chapters     []VideoChapter `json:"chapters,omitempty"`
   108  	Moments      []VideoMoment  `json:"moments,omitempty"`
   109  	MetaTags     []VideoMetaTag `json:"metaTags,omitempty"`
   110  }
   111  
   112  type CreateVideoInput struct {
   113  	LibraryID     string  `json:"-"`
   114  	Title         string  `json:"title"`
   115  	CollectionID  *string `json:"collectionId,omitempty"`
   116  	ThumbnailTime *int64  `json:"thumbnailTime,omitempty"`
   117  }
   118  
   119  type UploadVideoInput struct {
   120  	LibraryID string
   121  	VideoID   string
   122  	Data      io.Reader
   123  }
   124  
   125  // See https://docs.bunny.net/docs/stream-embed-token-authentication
   126  func (client *Client) SignVideoUrl(videoId string, expiresAt time.Time) (token, expiresAtStr string) {
   127  	expiresAtStr = strconv.FormatInt(expiresAt.Unix(), 10)
   128  
   129  	data := append([]byte(client.streamApiKey), []byte(videoId)...)
   130  	data = append(data, []byte(expiresAtStr)...)
   131  	signature := sha256.Sum256(data)
   132  
   133  	token = hex.EncodeToString(signature[:])
   134  	return
   135  }
   136  
   137  // See here for the avilable options: https://docs.bunny.net/docs/stream-embedding-videos#parameters
   138  type GenerateIframeVideoUrlOptions struct {
   139  	Signed bool
   140  	// If Signed is true and Expires is nil we use a default value of 24 hours
   141  	Expires  *time.Time
   142  	Autoplay *bool
   143  	// We recommend to set to false
   144  	TrackView *bool
   145  	// We recommend to set to true
   146  	Preload        *bool
   147  	Captions       *string
   148  	VideoStartTime *uint64
   149  	Refresh        *bool
   150  }
   151  
   152  // GenerateIframeVideoUrl generates the URL of the given video to be embeded with an iframe
   153  // See https://docs.bunny.net/docs/stream-embedding-videos for more information
   154  // See https://docs.bunny.net/docs/stream-embed-token-authentication for documentation about authentication
   155  func (client *Client) GenerateIframeVideoUrl(libraryID string, videoID string, options *GenerateIframeVideoUrlOptions) (videoUrl string) {
   156  	var queryValue string
   157  	videoUrl = fmt.Sprintf("https://iframe.mediadelivery.net/embed/%s/%s", libraryID, videoID)
   158  
   159  	if options != nil {
   160  		queryParms := url.Values{}
   161  
   162  		if options.TrackView != nil {
   163  			queryParms.Set("trackView", strconv.FormatBool(*options.TrackView))
   164  		}
   165  		if options.Refresh != nil {
   166  			queryParms.Set("refresh", strconv.FormatBool(*options.Refresh))
   167  		}
   168  		if options.Autoplay != nil {
   169  			queryParms.Set("autoplay", strconv.FormatBool(*options.Autoplay))
   170  		}
   171  		if options.Preload != nil {
   172  			queryParms.Set("preload", strconv.FormatBool(*options.Preload))
   173  		}
   174  		if options.Captions != nil {
   175  			queryParms.Set("captions", *options.Captions)
   176  		}
   177  		if options.VideoStartTime != nil {
   178  			queryParms.Set("t", strconv.FormatUint(*options.VideoStartTime, 10))
   179  		}
   180  
   181  		if options.Signed {
   182  			var expiresAt time.Time
   183  			if options.Expires != nil {
   184  				expiresAt = *options.Expires
   185  			} else {
   186  				expiresAt = time.Now().UTC().Add(24 * time.Hour)
   187  			}
   188  			token, expiresAtStr := client.SignVideoUrl(videoID, expiresAt)
   189  			queryParms.Set("token", token)
   190  			queryParms.Set("expires", expiresAtStr)
   191  		}
   192  
   193  		queryValue = queryParms.Encode()
   194  	}
   195  
   196  	if queryValue != "" {
   197  		videoUrl += "?" + queryValue
   198  	}
   199  
   200  	return
   201  }
   202  
   203  // https://docs.bunny.net/reference/video_fetchnewvideo
   204  func (client *Client) FetchVideo(ctx context.Context, libraryID, videoUrl string) (output FetchVideoOutput, err error) {
   205  	err = client.request(ctx, requestParams{
   206  		Payload: FetchVideoInput{
   207  			Url: videoUrl,
   208  		},
   209  		Method:          http.MethodPost,
   210  		URL:             fmt.Sprintf("%s/library/%s/videos/fetch", client.streamApiBaseUrl, libraryID),
   211  		useStreamApiKey: true,
   212  	}, &output)
   213  
   214  	return
   215  }
   216  
   217  // https://docs.bunny.net/reference/video_deletevideo
   218  func (client *Client) DeleteVideo(ctx context.Context, libraryID, videoID string) (err error) {
   219  	err = client.request(ctx, requestParams{
   220  		Payload:         nil,
   221  		Method:          http.MethodDelete,
   222  		URL:             fmt.Sprintf("%s/library/%s/videos/%s", client.streamApiBaseUrl, libraryID, videoID),
   223  		useStreamApiKey: true,
   224  	}, nil)
   225  
   226  	return
   227  }
   228  
   229  // https://docs.bunny.net/reference/video_reencodevideo
   230  func (client *Client) ReencodeVideo(ctx context.Context, libraryID, videoID string) (video Video, err error) {
   231  	err = client.request(ctx, requestParams{
   232  		Payload:         nil,
   233  		Method:          http.MethodPost,
   234  		URL:             fmt.Sprintf("%s/library/%s/videos/%s/reencode", client.streamApiBaseUrl, libraryID, videoID),
   235  		useStreamApiKey: true,
   236  	}, &video)
   237  
   238  	return
   239  }
   240  
   241  // https://docs.bunny.net/reference/video_getvideo
   242  func (client *Client) GetVideo(ctx context.Context, libraryID, videoID string) (video Video, err error) {
   243  	err = client.request(ctx, requestParams{
   244  		Payload:         nil,
   245  		Method:          http.MethodGet,
   246  		URL:             fmt.Sprintf("%s/library/%s/videos/%s", client.streamApiBaseUrl, libraryID, videoID),
   247  		useStreamApiKey: true,
   248  	}, &video)
   249  
   250  	return
   251  }
   252  
   253  // https://docs.bunny.net/reference/video_updatevideo
   254  func (client *Client) UpdateVideo(ctx context.Context, input UpdateVideoInput) (err error) {
   255  	err = client.request(ctx, requestParams{
   256  		Payload:         nil,
   257  		Method:          http.MethodPost,
   258  		URL:             fmt.Sprintf("%s/library/%s/videos/%s", client.streamApiBaseUrl, input.LibraryID, input.VideoID),
   259  		useStreamApiKey: true,
   260  	}, nil)
   261  
   262  	return
   263  }
   264  
   265  // https://docs.bunny.net/reference/video_createvideo
   266  func (client *Client) CreateVideo(ctx context.Context, input CreateVideoInput) (output Video, err error) {
   267  	err = client.request(ctx, requestParams{
   268  		Payload:         input,
   269  		Method:          http.MethodPost,
   270  		URL:             fmt.Sprintf("%s/library/%s/videos", client.streamApiBaseUrl, input.LibraryID),
   271  		useStreamApiKey: true,
   272  	}, &output)
   273  
   274  	return
   275  }
   276  
   277  // https://docs.bunny.net/reference/video_createvideo
   278  func (client *Client) UploadVideo(ctx context.Context, input UploadVideoInput) (output UploadVideoOutput, err error) {
   279  	err = client.upload(ctx, uploadParams{
   280  		Data:            input.Data,
   281  		Method:          http.MethodPut,
   282  		URL:             fmt.Sprintf("%s/library/%s/videos/%s", client.streamApiBaseUrl, input.LibraryID, input.VideoID),
   283  		useStreamApiKey: true,
   284  	}, &output)
   285  
   286  	return
   287  }