mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-10-30 18:22:41 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			310 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"github.com/zoriya/kyoo/transcoder/src"
 | |
| 
 | |
| 	"github.com/labstack/echo/v4"
 | |
| 	"github.com/labstack/echo/v4/middleware"
 | |
| )
 | |
| 
 | |
| // Direct video
 | |
| //
 | |
| // Retrieve the raw video stream, in the same container as the one on the server. No transcoding or
 | |
| // transmuxing is done.
 | |
| //
 | |
| // Path: /:path/direct
 | |
| func DirectStream(c echo.Context) error {
 | |
| 	path, _, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return c.File(path)
 | |
| }
 | |
| 
 | |
| // Get master playlist
 | |
| //
 | |
| // Get a master playlist containing all possible video qualities and audios available for this resource.
 | |
| // Note that the direct stream is missing (since the direct is not an hls stream) and
 | |
| // subtitles/fonts are not included to support more codecs than just webvtt.
 | |
| //
 | |
| // Path: /:path/master.m3u8
 | |
| func (h *Handler) GetMaster(c echo.Context) error {
 | |
| 	client, err := GetClientId(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	path, sha, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	ret, err := h.transcoder.GetMaster(path, client, sha)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return c.String(http.StatusOK, ret)
 | |
| }
 | |
| 
 | |
| // Transcode video
 | |
| //
 | |
| // Transcode the video to the selected quality.
 | |
| // This route can take a few seconds to respond since it will way for at least one segment to be
 | |
| // available.
 | |
| //
 | |
| // Path: /:path/:quality/index.m3u8
 | |
| func (h *Handler) GetVideoIndex(c echo.Context) error {
 | |
| 	quality, err := src.QualityFromString(c.Param("quality"))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client, err := GetClientId(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	path, sha, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	ret, err := h.transcoder.GetVideoIndex(path, quality, client, sha)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return c.String(http.StatusOK, ret)
 | |
| }
 | |
| 
 | |
| // Transcode audio
 | |
| //
 | |
| // Get the selected audio
 | |
| // This route can take a few seconds to respond since it will way for at least one segment to be
 | |
| // available.
 | |
| //
 | |
| // Path: /:path/audio/:audio/index.m3u8
 | |
| func (h *Handler) GetAudioIndex(c echo.Context) error {
 | |
| 	audio, err := strconv.ParseInt(c.Param("audio"), 10, 32)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client, err := GetClientId(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	path, sha, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	ret, err := h.transcoder.GetAudioIndex(path, int32(audio), client, sha)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return c.String(http.StatusOK, ret)
 | |
| }
 | |
| 
 | |
| // Get transmuxed chunk
 | |
| //
 | |
| // Retrieve a chunk of a transmuxed video.
 | |
| //
 | |
| // Path: /:path/:quality/segments-:chunk.ts
 | |
| func (h *Handler) GetVideoSegment(c echo.Context) error {
 | |
| 	quality, err := src.QualityFromString(c.Param("quality"))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	segment, err := ParseSegment(c.Param("chunk"))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client, err := GetClientId(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	path, sha, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	ret, err := h.transcoder.GetVideoSegment(path, quality, segment, client, sha)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return c.File(ret)
 | |
| }
 | |
| 
 | |
| // Get audio chunk
 | |
| //
 | |
| // Retrieve a chunk of a transcoded audio.
 | |
| //
 | |
| // Path: /:path/audio/:audio/segments-:chunk.ts
 | |
| func (h *Handler) GetAudioSegment(c echo.Context) error {
 | |
| 	audio, err := strconv.ParseInt(c.Param("audio"), 10, 32)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	segment, err := ParseSegment(c.Param("chunk"))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client, err := GetClientId(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	path, sha, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	ret, err := h.transcoder.GetAudioSegment(path, int32(audio), segment, client, sha)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return c.File(ret)
 | |
| }
 | |
| 
 | |
| // Identify
 | |
| //
 | |
| // Identify metadata about a file.
 | |
| //
 | |
| // Path: /:path/info
 | |
| func (h *Handler) GetInfo(c echo.Context) error {
 | |
| 	path, sha, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	ret, err := src.GetInfo(path, sha)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	// Run extractors to have them in cache
 | |
| 	src.Extract(ret.Path, sha)
 | |
| 	go src.ExtractThumbnail(ret.Path, sha)
 | |
| 	return c.JSON(http.StatusOK, ret)
 | |
| }
 | |
| 
 | |
| // Get attachments
 | |
| //
 | |
| // Get a specific attachment.
 | |
| //
 | |
| // Path: /:path/attachment/:name
 | |
| func (h *Handler) GetAttachment(c echo.Context) error {
 | |
| 	path, sha, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	name := c.Param("name")
 | |
| 	if err := SanitizePath(name); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	wait, err := src.Extract(path, sha)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	<-wait
 | |
| 
 | |
| 	ret := fmt.Sprintf("%s/%s/att/%s", src.Settings.Metadata, sha, name)
 | |
| 	return c.File(ret)
 | |
| }
 | |
| 
 | |
| // Get subtitle
 | |
| //
 | |
| // Get a specific subtitle.
 | |
| //
 | |
| // Path: /:path/subtitle/:name
 | |
| func (h *Handler) GetSubtitle(c echo.Context) error {
 | |
| 	path, sha, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	name := c.Param("name")
 | |
| 	if err := SanitizePath(name); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	wait, err := src.Extract(path, sha)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	<-wait
 | |
| 
 | |
| 	ret := fmt.Sprintf("%s/%s/sub/%s", src.Settings.Metadata, sha, name)
 | |
| 	return c.File(ret)
 | |
| }
 | |
| 
 | |
| // Get thumbnail sprite
 | |
| //
 | |
| // Get a sprite file containing all the thumbnails of the show.
 | |
| //
 | |
| // Path: /:path/thumbnails.png
 | |
| func (h *Handler) GetThumbnails(c echo.Context) error {
 | |
| 	path, sha, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	out, err := src.ExtractThumbnail(path, sha)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return c.File(fmt.Sprintf("%s/sprite.png", out))
 | |
| }
 | |
| 
 | |
| // Get thumbnail vtt
 | |
| //
 | |
| // Get a vtt file containing timing/position of thumbnails inside the sprite file.
 | |
| // https://developer.bitmovin.com/playback/docs/webvtt-based-thumbnails for more info.
 | |
| //
 | |
| // Path: /:path/:resource/:slug/thumbnails.vtt
 | |
| func (h *Handler) GetThumbnailsVtt(c echo.Context) error {
 | |
| 	path, sha, err := GetPath(c)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	out, err := src.ExtractThumbnail(path, sha)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return c.File(fmt.Sprintf("%s/sprite.vtt", out))
 | |
| }
 | |
| 
 | |
| type Handler struct {
 | |
| 	transcoder *src.Transcoder
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	e := echo.New()
 | |
| 	e.Use(middleware.Logger())
 | |
| 	e.HTTPErrorHandler = ErrorHandler
 | |
| 
 | |
| 	transcoder, err := src.NewTranscoder()
 | |
| 	if err != nil {
 | |
| 		e.Logger.Fatal(err)
 | |
| 		return
 | |
| 	}
 | |
| 	h := Handler{
 | |
| 		transcoder: transcoder,
 | |
| 	}
 | |
| 
 | |
| 	e.GET("/:path/direct", DirectStream)
 | |
| 	e.GET("/:path/master.m3u8", h.GetMaster)
 | |
| 	e.GET("/:path/:quality/index.m3u8", h.GetVideoIndex)
 | |
| 	e.GET("/:path/audio/:audio/index.m3u8", h.GetAudioIndex)
 | |
| 	e.GET("/:path/:quality/:chunk", h.GetVideoSegment)
 | |
| 	e.GET("/:path/audio/:audio/:chunk", h.GetAudioSegment)
 | |
| 	e.GET("/:path/info", h.GetInfo)
 | |
| 	e.GET("/:path/thumbnails.png", h.GetThumbnails)
 | |
| 	e.GET("/:path/thumbnails.vtt", h.GetThumbnailsVtt)
 | |
| 	e.GET("/:path/attachment/:name", h.GetAttachment)
 | |
| 	e.GET("/:path/subtitle/:name", h.GetSubtitle)
 | |
| 
 | |
| 	e.Logger.Fatal(e.Start(":7666"))
 | |
| }
 |