From 460e4596f700ebd8b6a3e855d9cdb9e76b989531 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 18 Jul 2025 23:38:59 +0200 Subject: [PATCH] Generate swagger for transcoder --- api/src/index.ts | 1 + transcoder/docs/docs.go | 547 +++++++++++++++++++++++++++++++++ transcoder/docs/swagger.json | 523 +++++++++++++++++++++++++++++++ transcoder/src/api/metadata.go | 4 +- 4 files changed, 1073 insertions(+), 2 deletions(-) create mode 100644 transcoder/docs/docs.go create mode 100644 transcoder/docs/swagger.json diff --git a/api/src/index.ts b/api/src/index.ts index 2ba8c418..e8288af0 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -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: { diff --git a/transcoder/docs/docs.go b/transcoder/docs/docs.go new file mode 100644 index 00000000..5d56bc50 --- /dev/null +++ b/transcoder/docs/docs.go @@ -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) +} diff --git a/transcoder/docs/swagger.json b/transcoder/docs/swagger.json new file mode 100644 index 00000000..a7cecb23 --- /dev/null +++ b/transcoder/docs/swagger.json @@ -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" + } + } +} \ No newline at end of file diff --git a/transcoder/src/api/metadata.go b/transcoder/src/api/metadata.go index f9d903f5..91877a24 100644 --- a/transcoder/src/api/metadata.go +++ b/transcoder/src/api/metadata.go @@ -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. //