Generate swagger for transcoder

This commit is contained in:
Zoe Roux 2025-07-18 23:38:59 +02:00
parent c340a9b559
commit 460e4596f7
4 changed files with 1073 additions and 2 deletions

View File

@ -18,6 +18,7 @@ const app = new Elysia()
{ slug: "kyoo", url: "/swagger/json" },
{ slug: "keibi", url: "/auth/swagger/doc.json" },
{ slug: "scanner", url: "/scanner/openapi.json" },
{ slug: "transcoder", url: "/video/swagger/doc.json" },
],
},
documentation: {

547
transcoder/docs/docs.go Normal file
View File

@ -0,0 +1,547 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {
"name": "Repository",
"url": "https://github.com/zoriya/kyoo"
},
"license": {
"name": "GPL-3.0",
"url": "https://www.gnu.org/licenses/gpl-3.0.en.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/:path/attachment/:name": {
"get": {
"description": "Get a specific attachment.",
"tags": [
"metadata"
],
"summary": "Get attachments",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
},
{
"type": "string",
"example": "font.ttf",
"description": "Name of the attachment",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Requested attachment",
"schema": {
"type": "file"
}
}
}
}
},
"/:path/direct": {
"get": {
"description": "Retrieve the raw video stream, in the same container as the one on the server.\nNo transcoding or transmuxing is done.",
"tags": [
"streams"
],
"summary": "Direct video",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
},
{
"type": "string",
"example": "bubble.mkv",
"description": "anything, this can be used for the automatic file name when downloading from the browser",
"name": "identifier",
"in": "path"
}
],
"responses": {
"206": {
"description": "Video file (supports byte-requests)",
"schema": {
"type": "file"
}
}
}
}
},
"/:path/info": {
"get": {
"description": "Identify metadata about a file.",
"tags": [
"metadata"
],
"summary": "Identify",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Metadata info of the video.",
"schema": {
"$ref": "#/definitions/src.MediaInfo"
}
}
}
}
},
"/:path/master.m3u8": {
"get": {
"description": "Get a master playlist containing all possible video qualities and audios available for this resource.\nNote that the direct stream is missing (since the direct is not an hls stream) and\nsubtitles/fonts are not included to support more codecs than just webvtt.",
"tags": [
"streams"
],
"summary": "Get master playlist",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Master playlist with all available stream qualities",
"schema": {
"type": "file"
}
}
}
}
},
"/:path/subtitle/:name": {
"get": {
"description": "Get a specific subtitle.",
"tags": [
"metadata"
],
"summary": "Get subtitle",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
},
{
"type": "string",
"example": "en.srt",
"description": "Name of the subtitle",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Requested subtitle",
"schema": {
"type": "file"
}
}
}
}
},
"/:path/thumbnails.png": {
"get": {
"description": "Get a sprite file containing all the thumbnails of the show.",
"tags": [
"metadata"
],
"summary": "Get thumbnail sprite",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "sprite",
"schema": {
"type": "file"
}
}
}
}
},
"/:path/thumbnails.vtt": {
"get": {
"description": "Get a vtt file containing timing/position of thumbnails inside the sprite file.\nhttps://developer.bitmovin.com/playback/docs/webvtt-based-thumbnails for more info.",
"tags": [
"metadata"
],
"summary": "Get thumbnail vtt",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "sprite",
"schema": {
"type": "file"
}
}
}
}
}
},
"definitions": {
"src.Audio": {
"type": "object",
"properties": {
"bitrate": {
"description": "/ The average bitrate of the audio in bytes/s",
"type": "integer"
},
"codec": {
"description": "/ The human readable codec name.",
"type": "string"
},
"index": {
"description": "/ The index of this track on the media.",
"type": "integer"
},
"isDefault": {
"description": "/ Is this stream the default one of it's type?",
"type": "boolean"
},
"isForced": {
"description": "TODO: remove this in next major",
"type": "boolean"
},
"language": {
"description": "/ The language of this stream (as a IETF-BCP-47 language code)",
"type": "string"
},
"mimeCodec": {
"description": "/ The codec of this stream (defined as the RFC 6381).",
"type": "string"
},
"title": {
"description": "/ The title of the stream.",
"type": "string"
}
}
},
"src.Chapter": {
"type": "object",
"properties": {
"endTime": {
"description": "/ The end time of the chapter (in second from the start of the episode).",
"type": "number"
},
"name": {
"description": "/ The name of this chapter. This should be a human-readable name that could be presented to the user.",
"type": "string"
},
"startTime": {
"description": "/ The start time of the chapter (in second from the start of the episode).",
"type": "number"
},
"type": {
"description": "/ The type value is used to mark special chapters (openning/credits...)",
"allOf": [
{
"$ref": "#/definitions/src.ChapterType"
}
]
}
}
},
"src.ChapterType": {
"type": "string",
"enum": [
"content",
"recap",
"intro",
"credits",
"preview"
],
"x-enum-varnames": [
"Content",
"Recap",
"Intro",
"Credits",
"Preview"
]
},
"src.MediaInfo": {
"type": "object",
"properties": {
"audios": {
"description": "/ The list of audio tracks.",
"type": "array",
"items": {
"$ref": "#/definitions/src.Audio"
}
},
"chapters": {
"description": "/ The list of chapters. See Chapter for more information.",
"type": "array",
"items": {
"$ref": "#/definitions/src.Chapter"
}
},
"container": {
"description": "/ The container of the video file of this episode.",
"type": "string"
},
"duration": {
"description": "/ The length of the media in seconds.",
"type": "number"
},
"extension": {
"description": "/ The extension currently used to store this video file",
"type": "string"
},
"fonts": {
"description": "/ The list of fonts that can be used to display subtitles.",
"type": "array",
"items": {
"type": "string"
}
},
"mimeCodec": {
"description": "/ The whole mimetype (defined as the RFC 6381). ex: ` + "`" + `video/mp4; codecs=\"avc1.640028, mp4a.40.2\"` + "`" + `",
"type": "string"
},
"path": {
"description": "/ The internal path of the video file.",
"type": "string"
},
"sha": {
"description": "The sha1 of the video file.",
"type": "string"
},
"size": {
"description": "/ The file size of the video file.",
"type": "integer"
},
"subtitles": {
"description": "/ The list of subtitles tracks.",
"type": "array",
"items": {
"$ref": "#/definitions/src.Subtitle"
}
},
"versions": {
"description": "/ Version of the metadata. This can be used to invalidate older metadata from db if the extraction code has changed.",
"allOf": [
{
"$ref": "#/definitions/src.Versions"
}
]
},
"video": {
"description": "TODO: remove on next major",
"allOf": [
{
"$ref": "#/definitions/src.Video"
}
]
},
"videos": {
"description": "/ The list of videos if there are multiples.",
"type": "array",
"items": {
"$ref": "#/definitions/src.Video"
}
}
}
},
"src.Subtitle": {
"type": "object",
"properties": {
"codec": {
"description": "/ The codec of this stream.",
"type": "string"
},
"extension": {
"description": "/ The extension for the codec.",
"type": "string"
},
"index": {
"description": "/ The index of this track on the media.",
"type": "integer"
},
"isDefault": {
"description": "/ Is this stream the default one of it's type?",
"type": "boolean"
},
"isExternal": {
"description": "/ Is this an external subtitle (as in stored in a different file)",
"type": "boolean"
},
"isForced": {
"description": "/ Is this stream tagged as forced?",
"type": "boolean"
},
"isHearingImpaired": {
"description": "/ Is this stream tagged as hearing impaired?",
"type": "boolean"
},
"language": {
"description": "/ The language of this stream (as a IETF-BCP-47 language code)",
"type": "string"
},
"link": {
"description": "/ The link to access this subtitle.",
"type": "string"
},
"path": {
"description": "/ Where the subtitle is stored (null if stored inside the video)",
"type": "string"
},
"title": {
"description": "/ The title of the stream.",
"type": "string"
}
}
},
"src.Versions": {
"type": "object",
"properties": {
"extract": {
"type": "integer"
},
"info": {
"type": "integer"
},
"keyframes": {
"type": "integer"
},
"thumbs": {
"type": "integer"
}
}
},
"src.Video": {
"type": "object",
"properties": {
"bitrate": {
"description": "/ The average bitrate of the video in bytes/s",
"type": "integer"
},
"codec": {
"description": "/ The human readable codec name.",
"type": "string"
},
"height": {
"description": "/ The height of the video stream",
"type": "integer"
},
"index": {
"description": "/ The index of this track on the media.",
"type": "integer"
},
"isDefault": {
"description": "/ Is this stream the default one of it's type?",
"type": "boolean"
},
"language": {
"description": "/ The language of this stream (as a ISO-639-2 language code)",
"type": "string"
},
"mimeCodec": {
"description": "/ The codec of this stream (defined as the RFC 6381).",
"type": "string"
},
"title": {
"description": "/ The title of the stream.",
"type": "string"
},
"width": {
"description": "/ The width of the video stream",
"type": "integer"
}
}
}
},
"securityDefinitions": {
"Jwt": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"Token": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "kyoo.zoriya.dev",
BasePath: "/video",
Schemes: []string{},
Title: "gocoder - Kyoo's transcoder",
Description: "Real time transcoder.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

View File

@ -0,0 +1,523 @@
{
"swagger": "2.0",
"info": {
"description": "Real time transcoder.",
"title": "gocoder - Kyoo's transcoder",
"contact": {
"name": "Repository",
"url": "https://github.com/zoriya/kyoo"
},
"license": {
"name": "GPL-3.0",
"url": "https://www.gnu.org/licenses/gpl-3.0.en.html"
},
"version": "1.0"
},
"host": "kyoo.zoriya.dev",
"basePath": "/video",
"paths": {
"/:path/attachment/:name": {
"get": {
"description": "Get a specific attachment.",
"tags": [
"metadata"
],
"summary": "Get attachments",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
},
{
"type": "string",
"example": "font.ttf",
"description": "Name of the attachment",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Requested attachment",
"schema": {
"type": "file"
}
}
}
}
},
"/:path/direct": {
"get": {
"description": "Retrieve the raw video stream, in the same container as the one on the server.\nNo transcoding or transmuxing is done.",
"tags": [
"streams"
],
"summary": "Direct video",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
},
{
"type": "string",
"example": "bubble.mkv",
"description": "anything, this can be used for the automatic file name when downloading from the browser",
"name": "identifier",
"in": "path"
}
],
"responses": {
"206": {
"description": "Video file (supports byte-requests)",
"schema": {
"type": "file"
}
}
}
}
},
"/:path/info": {
"get": {
"description": "Identify metadata about a file.",
"tags": [
"metadata"
],
"summary": "Identify",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Metadata info of the video.",
"schema": {
"$ref": "#/definitions/src.MediaInfo"
}
}
}
}
},
"/:path/master.m3u8": {
"get": {
"description": "Get a master playlist containing all possible video qualities and audios available for this resource.\nNote that the direct stream is missing (since the direct is not an hls stream) and\nsubtitles/fonts are not included to support more codecs than just webvtt.",
"tags": [
"streams"
],
"summary": "Get master playlist",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Master playlist with all available stream qualities",
"schema": {
"type": "file"
}
}
}
}
},
"/:path/subtitle/:name": {
"get": {
"description": "Get a specific subtitle.",
"tags": [
"metadata"
],
"summary": "Get subtitle",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
},
{
"type": "string",
"example": "en.srt",
"description": "Name of the subtitle",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Requested subtitle",
"schema": {
"type": "file"
}
}
}
}
},
"/:path/thumbnails.png": {
"get": {
"description": "Get a sprite file containing all the thumbnails of the show.",
"tags": [
"metadata"
],
"summary": "Get thumbnail sprite",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "sprite",
"schema": {
"type": "file"
}
}
}
}
},
"/:path/thumbnails.vtt": {
"get": {
"description": "Get a vtt file containing timing/position of thumbnails inside the sprite file.\nhttps://developer.bitmovin.com/playback/docs/webvtt-based-thumbnails for more info.",
"tags": [
"metadata"
],
"summary": "Get thumbnail vtt",
"parameters": [
{
"type": "string",
"format": "base64",
"example": "L3ZpZGVvL2J1YmJsZS5ta3YK",
"description": "Base64 of a video's path",
"name": "path",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "sprite",
"schema": {
"type": "file"
}
}
}
}
}
},
"definitions": {
"src.Audio": {
"type": "object",
"properties": {
"bitrate": {
"description": "/ The average bitrate of the audio in bytes/s",
"type": "integer"
},
"codec": {
"description": "/ The human readable codec name.",
"type": "string"
},
"index": {
"description": "/ The index of this track on the media.",
"type": "integer"
},
"isDefault": {
"description": "/ Is this stream the default one of it's type?",
"type": "boolean"
},
"isForced": {
"description": "TODO: remove this in next major",
"type": "boolean"
},
"language": {
"description": "/ The language of this stream (as a IETF-BCP-47 language code)",
"type": "string"
},
"mimeCodec": {
"description": "/ The codec of this stream (defined as the RFC 6381).",
"type": "string"
},
"title": {
"description": "/ The title of the stream.",
"type": "string"
}
}
},
"src.Chapter": {
"type": "object",
"properties": {
"endTime": {
"description": "/ The end time of the chapter (in second from the start of the episode).",
"type": "number"
},
"name": {
"description": "/ The name of this chapter. This should be a human-readable name that could be presented to the user.",
"type": "string"
},
"startTime": {
"description": "/ The start time of the chapter (in second from the start of the episode).",
"type": "number"
},
"type": {
"description": "/ The type value is used to mark special chapters (openning/credits...)",
"allOf": [
{
"$ref": "#/definitions/src.ChapterType"
}
]
}
}
},
"src.ChapterType": {
"type": "string",
"enum": [
"content",
"recap",
"intro",
"credits",
"preview"
],
"x-enum-varnames": [
"Content",
"Recap",
"Intro",
"Credits",
"Preview"
]
},
"src.MediaInfo": {
"type": "object",
"properties": {
"audios": {
"description": "/ The list of audio tracks.",
"type": "array",
"items": {
"$ref": "#/definitions/src.Audio"
}
},
"chapters": {
"description": "/ The list of chapters. See Chapter for more information.",
"type": "array",
"items": {
"$ref": "#/definitions/src.Chapter"
}
},
"container": {
"description": "/ The container of the video file of this episode.",
"type": "string"
},
"duration": {
"description": "/ The length of the media in seconds.",
"type": "number"
},
"extension": {
"description": "/ The extension currently used to store this video file",
"type": "string"
},
"fonts": {
"description": "/ The list of fonts that can be used to display subtitles.",
"type": "array",
"items": {
"type": "string"
}
},
"mimeCodec": {
"description": "/ The whole mimetype (defined as the RFC 6381). ex: `video/mp4; codecs=\"avc1.640028, mp4a.40.2\"`",
"type": "string"
},
"path": {
"description": "/ The internal path of the video file.",
"type": "string"
},
"sha": {
"description": "The sha1 of the video file.",
"type": "string"
},
"size": {
"description": "/ The file size of the video file.",
"type": "integer"
},
"subtitles": {
"description": "/ The list of subtitles tracks.",
"type": "array",
"items": {
"$ref": "#/definitions/src.Subtitle"
}
},
"versions": {
"description": "/ Version of the metadata. This can be used to invalidate older metadata from db if the extraction code has changed.",
"allOf": [
{
"$ref": "#/definitions/src.Versions"
}
]
},
"video": {
"description": "TODO: remove on next major",
"allOf": [
{
"$ref": "#/definitions/src.Video"
}
]
},
"videos": {
"description": "/ The list of videos if there are multiples.",
"type": "array",
"items": {
"$ref": "#/definitions/src.Video"
}
}
}
},
"src.Subtitle": {
"type": "object",
"properties": {
"codec": {
"description": "/ The codec of this stream.",
"type": "string"
},
"extension": {
"description": "/ The extension for the codec.",
"type": "string"
},
"index": {
"description": "/ The index of this track on the media.",
"type": "integer"
},
"isDefault": {
"description": "/ Is this stream the default one of it's type?",
"type": "boolean"
},
"isExternal": {
"description": "/ Is this an external subtitle (as in stored in a different file)",
"type": "boolean"
},
"isForced": {
"description": "/ Is this stream tagged as forced?",
"type": "boolean"
},
"isHearingImpaired": {
"description": "/ Is this stream tagged as hearing impaired?",
"type": "boolean"
},
"language": {
"description": "/ The language of this stream (as a IETF-BCP-47 language code)",
"type": "string"
},
"link": {
"description": "/ The link to access this subtitle.",
"type": "string"
},
"path": {
"description": "/ Where the subtitle is stored (null if stored inside the video)",
"type": "string"
},
"title": {
"description": "/ The title of the stream.",
"type": "string"
}
}
},
"src.Versions": {
"type": "object",
"properties": {
"extract": {
"type": "integer"
},
"info": {
"type": "integer"
},
"keyframes": {
"type": "integer"
},
"thumbs": {
"type": "integer"
}
}
},
"src.Video": {
"type": "object",
"properties": {
"bitrate": {
"description": "/ The average bitrate of the video in bytes/s",
"type": "integer"
},
"codec": {
"description": "/ The human readable codec name.",
"type": "string"
},
"height": {
"description": "/ The height of the video stream",
"type": "integer"
},
"index": {
"description": "/ The index of this track on the media.",
"type": "integer"
},
"isDefault": {
"description": "/ Is this stream the default one of it's type?",
"type": "boolean"
},
"language": {
"description": "/ The language of this stream (as a ISO-639-2 language code)",
"type": "string"
},
"mimeCodec": {
"description": "/ The codec of this stream (defined as the RFC 6381).",
"type": "string"
},
"title": {
"description": "/ The title of the stream.",
"type": "string"
},
"width": {
"description": "/ The width of the video stream",
"type": "integer"
}
}
}
},
"securityDefinitions": {
"Jwt": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"Token": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}

View File

@ -34,7 +34,7 @@ func RegisterMetadataHandlers(e *echo.Group, metadata *src.MetadataService) {
// @Param path path string true "Base64 of a video's path" format(base64) example(L3ZpZGVvL2J1YmJsZS5ta3YK)
//
// @Success 200 {object} src.MediaInfo "Metadata info of the video."
// @Router /:path/info [get]
// @Router /:path/info [get]
func (h *mhandler) GetInfo(c echo.Context) error {
path, sha, err := getPath(c)
if err != nil {
@ -52,7 +52,7 @@ func (h *mhandler) GetInfo(c echo.Context) error {
return c.JSON(http.StatusOK, ret)
}
// @Summary Get subtitle
// @Summary Get subtitle
//
// @Description Get a specific subtitle.
//