mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-02-25 18:55:32 -06:00
Add video aspect ratio in server
This commit is contained in:
@@ -103,9 +103,14 @@ function calculateBitrate (options: {
|
||||
VideoResolution.H_NOVIDEO
|
||||
]
|
||||
|
||||
const size1 = resolution
|
||||
const size2 = ratio < 1 && ratio > 0
|
||||
? resolution / ratio // Portrait mode
|
||||
: resolution * ratio
|
||||
|
||||
for (const toTestResolution of resolutionsOrder) {
|
||||
if (toTestResolution <= resolution) {
|
||||
return Math.floor(resolution * resolution * ratio * fps * bitPerPixel[toTestResolution])
|
||||
return Math.floor(size1 * size2 * fps * bitPerPixel[toTestResolution])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { VideoDetails, VideoPrivacy, VideoStreamingPlaylistType } from '@peertube/peertube-models'
|
||||
|
||||
function getAllPrivacies () {
|
||||
export function getAllPrivacies () {
|
||||
return [ VideoPrivacy.PUBLIC, VideoPrivacy.INTERNAL, VideoPrivacy.PRIVATE, VideoPrivacy.UNLISTED, VideoPrivacy.PASSWORD_PROTECTED ]
|
||||
}
|
||||
|
||||
function getAllFiles (video: Partial<Pick<VideoDetails, 'files' | 'streamingPlaylists'>>) {
|
||||
export function getAllFiles (video: Partial<Pick<VideoDetails, 'files' | 'streamingPlaylists'>>) {
|
||||
const files = video.files
|
||||
|
||||
const hls = getHLS(video)
|
||||
@@ -13,12 +13,13 @@ function getAllFiles (video: Partial<Pick<VideoDetails, 'files' | 'streamingPlay
|
||||
return files
|
||||
}
|
||||
|
||||
function getHLS (video: Partial<Pick<VideoDetails, 'streamingPlaylists'>>) {
|
||||
export function getHLS (video: Partial<Pick<VideoDetails, 'streamingPlaylists'>>) {
|
||||
return video.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
|
||||
}
|
||||
|
||||
export {
|
||||
getAllPrivacies,
|
||||
getAllFiles,
|
||||
getHLS
|
||||
export function buildAspectRatio (options: { width: number, height: number }) {
|
||||
const { width, height } = options
|
||||
if (!width || !height) return null
|
||||
|
||||
return Math.round((width / height) * 10000) / 10000 // 4 decimals precision
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'
|
||||
import { forceNumber } from '@peertube/peertube-core-utils'
|
||||
import { buildAspectRatio, forceNumber } from '@peertube/peertube-core-utils'
|
||||
import { VideoResolution } from '@peertube/peertube-models'
|
||||
|
||||
/**
|
||||
@@ -123,7 +123,7 @@ async function getVideoStreamDimensionsInfo (path: string, existingProbe?: Ffpro
|
||||
return {
|
||||
width: videoStream.width,
|
||||
height: videoStream.height,
|
||||
ratio: Math.max(videoStream.height, videoStream.width) / Math.min(videoStream.height, videoStream.width),
|
||||
ratio: buildAspectRatio({ width: videoStream.width, height: videoStream.height }),
|
||||
resolution: Math.min(videoStream.height, videoStream.width),
|
||||
isPortraitMode: videoStream.height > videoStream.width
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ export interface ActivityIconObject {
|
||||
type: 'Image'
|
||||
url: string
|
||||
mediaType: string
|
||||
width?: number
|
||||
height?: number
|
||||
width: number
|
||||
height: number | null
|
||||
}
|
||||
|
||||
export type ActivityVideoUrlObject = {
|
||||
@@ -19,6 +19,7 @@ export type ActivityVideoUrlObject = {
|
||||
mediaType: 'video/mp4' | 'video/webm' | 'video/ogg' | 'audio/mp4'
|
||||
href: string
|
||||
height: number
|
||||
width: number | null
|
||||
size: number
|
||||
fps: number
|
||||
}
|
||||
@@ -35,6 +36,7 @@ export type ActivityVideoFileMetadataUrlObject = {
|
||||
rel: [ 'metadata', any ]
|
||||
mediaType: 'application/json'
|
||||
height: number
|
||||
width: number | null
|
||||
href: string
|
||||
fps: number
|
||||
}
|
||||
@@ -63,6 +65,8 @@ export type ActivityBitTorrentUrlObject = {
|
||||
mediaType: 'application/x-bittorrent' | 'application/x-bittorrent;x-scheme-handler/magnet'
|
||||
href: string
|
||||
height: number
|
||||
width: number | null
|
||||
fps: number | null
|
||||
}
|
||||
|
||||
export type ActivityMagnetUrlObject = {
|
||||
@@ -70,6 +74,8 @@ export type ActivityMagnetUrlObject = {
|
||||
mediaType: 'application/x-bittorrent;x-scheme-handler/magnet'
|
||||
href: string
|
||||
height: number
|
||||
width: number | null
|
||||
fps: number | null
|
||||
}
|
||||
|
||||
export type ActivityHtmlUrlObject = {
|
||||
|
||||
@@ -44,6 +44,8 @@ export interface VideoObject {
|
||||
|
||||
support: string
|
||||
|
||||
aspectRatio: number
|
||||
|
||||
icon: ActivityIconObject[]
|
||||
|
||||
url: ActivityUrlObject[]
|
||||
|
||||
@@ -7,6 +7,9 @@ export interface VideoFile {
|
||||
resolution: VideoConstant<number>
|
||||
size: number // Bytes
|
||||
|
||||
width?: number
|
||||
height?: number
|
||||
|
||||
torrentUrl: string
|
||||
torrentDownloadUrl: string
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ export interface Video extends Partial<VideoAdditionalAttributes> {
|
||||
isLocal: boolean
|
||||
name: string
|
||||
|
||||
aspectRatio: number | null
|
||||
|
||||
isLive: boolean
|
||||
|
||||
thumbnailPath: string
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 48 KiB |
@@ -115,6 +115,8 @@ describe('Test live', function () {
|
||||
|
||||
expect(video.isLive).to.be.true
|
||||
|
||||
expect(video.aspectRatio).to.not.exist
|
||||
|
||||
expect(video.nsfw).to.be.false
|
||||
expect(video.waitTranscoding).to.be.false
|
||||
expect(video.name).to.equal('my super live')
|
||||
@@ -552,6 +554,7 @@ describe('Test live', function () {
|
||||
|
||||
expect(video.state.id).to.equal(VideoState.PUBLISHED)
|
||||
expect(video.duration).to.be.greaterThan(1)
|
||||
expect(video.aspectRatio).to.equal(1.7778)
|
||||
expect(video.files).to.have.lengthOf(0)
|
||||
|
||||
const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { expect } from 'chai'
|
||||
import { readdir } from 'fs/promises'
|
||||
import { decode as magnetUriDecode } from 'magnet-uri'
|
||||
import { basename, join } from 'path'
|
||||
import { wait } from '@peertube/peertube-core-utils'
|
||||
import {
|
||||
@@ -25,12 +24,13 @@ import {
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { checkSegmentHash } from '@tests/shared/streaming-playlists.js'
|
||||
import { checkVideoFilesWereRemoved, saveVideoInServers } from '@tests/shared/videos.js'
|
||||
import { magnetUriDecode } from '@tests/shared/webtorrent.js'
|
||||
|
||||
let servers: PeerTubeServer[] = []
|
||||
let video1Server2: VideoDetails
|
||||
|
||||
async function checkMagnetWebseeds (file: VideoFile, baseWebseeds: string[], server: PeerTubeServer) {
|
||||
const parsed = magnetUriDecode(file.magnetUri)
|
||||
const parsed = await magnetUriDecode(file.magnetUri)
|
||||
|
||||
for (const ws of baseWebseeds) {
|
||||
const found = parsed.urlList.find(url => url === `${ws}${basename(file.fileUrl)}`)
|
||||
|
||||
@@ -479,6 +479,8 @@ describe('Test follows', function () {
|
||||
files: [
|
||||
{
|
||||
resolution: 720,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
size: 218910
|
||||
}
|
||||
]
|
||||
|
||||
@@ -69,6 +69,8 @@ describe('Test handle downs', function () {
|
||||
fixture: 'video_short1.webm',
|
||||
files: [
|
||||
{
|
||||
height: 720,
|
||||
width: 1280,
|
||||
resolution: 720,
|
||||
size: 572456
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await,@typescript-eslint/no-floating-promises */
|
||||
|
||||
import { decode as magnetUriDecode, encode as magnetUriEncode } from 'magnet-uri'
|
||||
import WebTorrent from 'webtorrent'
|
||||
import {
|
||||
cleanupTests,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
PeerTubeServer,
|
||||
setAccessTokensToServers
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { magnetUriDecode, magnetUriEncode } from '@tests/shared/webtorrent.js'
|
||||
|
||||
describe('Test tracker', function () {
|
||||
let server: PeerTubeServer
|
||||
@@ -25,10 +25,10 @@ describe('Test tracker', function () {
|
||||
const video = await server.videos.get({ id: uuid })
|
||||
goodMagnet = video.files[0].magnetUri
|
||||
|
||||
const parsed = magnetUriDecode(goodMagnet)
|
||||
const parsed = await magnetUriDecode(goodMagnet)
|
||||
parsed.infoHash = '010597bb88b1968a5693a4fa8267c592ca65f2e9'
|
||||
|
||||
badMagnet = magnetUriEncode(parsed)
|
||||
badMagnet = await magnetUriEncode(parsed)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -401,10 +401,14 @@ function runTest (withObjectStorage: boolean) {
|
||||
files: [
|
||||
{
|
||||
resolution: 720,
|
||||
height: 720,
|
||||
width: 1280,
|
||||
size: 61000
|
||||
},
|
||||
{
|
||||
resolution: 240,
|
||||
height: 240,
|
||||
width: 426,
|
||||
size: 23000
|
||||
}
|
||||
],
|
||||
|
||||
@@ -118,6 +118,8 @@ describe('Test multiple servers', function () {
|
||||
files: [
|
||||
{
|
||||
resolution: 720,
|
||||
height: 720,
|
||||
width: 1280,
|
||||
size: 572456
|
||||
}
|
||||
]
|
||||
@@ -205,18 +207,26 @@ describe('Test multiple servers', function () {
|
||||
files: [
|
||||
{
|
||||
resolution: 240,
|
||||
height: 240,
|
||||
width: 426,
|
||||
size: 270000
|
||||
},
|
||||
{
|
||||
resolution: 360,
|
||||
height: 360,
|
||||
width: 640,
|
||||
size: 359000
|
||||
},
|
||||
{
|
||||
resolution: 480,
|
||||
height: 480,
|
||||
width: 854,
|
||||
size: 465000
|
||||
},
|
||||
{
|
||||
resolution: 720,
|
||||
height: 720,
|
||||
width: 1280,
|
||||
size: 750000
|
||||
}
|
||||
],
|
||||
@@ -312,6 +322,8 @@ describe('Test multiple servers', function () {
|
||||
files: [
|
||||
{
|
||||
resolution: 720,
|
||||
height: 720,
|
||||
width: 1280,
|
||||
size: 292677
|
||||
}
|
||||
]
|
||||
@@ -344,6 +356,8 @@ describe('Test multiple servers', function () {
|
||||
files: [
|
||||
{
|
||||
resolution: 720,
|
||||
height: 720,
|
||||
width: 1280,
|
||||
size: 218910
|
||||
}
|
||||
]
|
||||
@@ -654,6 +668,8 @@ describe('Test multiple servers', function () {
|
||||
files: [
|
||||
{
|
||||
resolution: 720,
|
||||
height: 720,
|
||||
width: 1280,
|
||||
size: 292677
|
||||
}
|
||||
],
|
||||
@@ -1061,18 +1077,26 @@ describe('Test multiple servers', function () {
|
||||
files: [
|
||||
{
|
||||
resolution: 720,
|
||||
height: 720,
|
||||
width: 1280,
|
||||
size: 61000
|
||||
},
|
||||
{
|
||||
resolution: 480,
|
||||
height: 480,
|
||||
width: 854,
|
||||
size: 40000
|
||||
},
|
||||
{
|
||||
resolution: 360,
|
||||
height: 360,
|
||||
width: 640,
|
||||
size: 32000
|
||||
},
|
||||
{
|
||||
resolution: 240,
|
||||
height: 240,
|
||||
width: 426,
|
||||
size: 23000
|
||||
}
|
||||
]
|
||||
|
||||
@@ -50,6 +50,8 @@ describe('Test a single server', function () {
|
||||
files: [
|
||||
{
|
||||
resolution: 720,
|
||||
height: 720,
|
||||
width: 1280,
|
||||
size: 218910
|
||||
}
|
||||
]
|
||||
@@ -81,6 +83,8 @@ describe('Test a single server', function () {
|
||||
files: [
|
||||
{
|
||||
resolution: 720,
|
||||
height: 720,
|
||||
width: 1280,
|
||||
size: 292677
|
||||
}
|
||||
]
|
||||
|
||||
@@ -105,7 +105,8 @@ describe('Test videos files', function () {
|
||||
const video = await servers[0].videos.get({ id: webVideoId })
|
||||
const files = video.files
|
||||
|
||||
await servers[0].videos.removeWebVideoFile({ videoId: webVideoId, fileId: files[0].id })
|
||||
const toDelete = files[0]
|
||||
await servers[0].videos.removeWebVideoFile({ videoId: webVideoId, fileId: toDelete.id })
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
@@ -113,7 +114,7 @@ describe('Test videos files', function () {
|
||||
const video = await server.videos.get({ id: webVideoId })
|
||||
|
||||
expect(video.files).to.have.lengthOf(files.length - 1)
|
||||
expect(video.files.find(f => f.id === files[0].id)).to.not.exist
|
||||
expect(video.files.find(f => f.resolution.id === toDelete.resolution.id)).to.not.exist
|
||||
}
|
||||
})
|
||||
|
||||
@@ -151,7 +152,7 @@ describe('Test videos files', function () {
|
||||
const video = await server.videos.get({ id: hlsId })
|
||||
|
||||
expect(video.streamingPlaylists[0].files).to.have.lengthOf(files.length - 1)
|
||||
expect(video.streamingPlaylists[0].files.find(f => f.id === toDelete.id)).to.not.exist
|
||||
expect(video.streamingPlaylists[0].files.find(f => f.resolution.id === toDelete.resolution.id)).to.not.exist
|
||||
|
||||
const { text } = await makeRawRequest({ url: video.streamingPlaylists[0].playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import { expect } from 'chai'
|
||||
import { decode } from 'magnet-uri'
|
||||
import { getAllFiles, wait } from '@peertube/peertube-core-utils'
|
||||
import { HttpStatusCode, HttpStatusCodeType, LiveVideo, VideoDetails, VideoPrivacy } from '@peertube/peertube-models'
|
||||
import {
|
||||
@@ -18,7 +17,7 @@ import {
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { expectStartWith } from '@tests/shared/checks.js'
|
||||
import { checkVideoFileTokenReinjection } from '@tests/shared/streaming-playlists.js'
|
||||
import { parseTorrentVideo } from '@tests/shared/webtorrent.js'
|
||||
import { magnetUriDecode, parseTorrentVideo } from '@tests/shared/webtorrent.js'
|
||||
|
||||
describe('Test video static file privacy', function () {
|
||||
let server: PeerTubeServer
|
||||
@@ -48,7 +47,7 @@ describe('Test video static file privacy', function () {
|
||||
const torrent = await parseTorrentVideo(server, file)
|
||||
expect(torrent.urlList).to.have.lengthOf(0)
|
||||
|
||||
const magnet = decode(file.magnetUri)
|
||||
const magnet = await magnetUriDecode(file.magnetUri)
|
||||
expect(magnet.urlList).to.have.lengthOf(0)
|
||||
|
||||
await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
|
||||
@@ -74,7 +73,7 @@ describe('Test video static file privacy', function () {
|
||||
const torrent = await parseTorrentVideo(server, file)
|
||||
expect(torrent.urlList[0]).to.not.include('private')
|
||||
|
||||
const magnet = decode(file.magnetUri)
|
||||
const magnet = await magnetUriDecode(file.magnetUri)
|
||||
expect(magnet.urlList[0]).to.not.include('private')
|
||||
|
||||
await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
import { expect } from 'chai'
|
||||
import snakeCase from 'lodash-es/snakeCase.js'
|
||||
import validator from 'validator'
|
||||
import { getAverageTheoreticalBitrate, getMaxTheoreticalBitrate, parseChapters, timeToInt } from '@peertube/peertube-core-utils'
|
||||
import {
|
||||
buildAspectRatio,
|
||||
getAverageTheoreticalBitrate,
|
||||
getMaxTheoreticalBitrate,
|
||||
parseChapters,
|
||||
timeToInt
|
||||
} from '@peertube/peertube-core-utils'
|
||||
import { VideoResolution } from '@peertube/peertube-models'
|
||||
import { objectConverter, parseBytes, parseDurationToMs, parseSemVersion } from '@peertube/peertube-server/core/helpers/core-utils.js'
|
||||
|
||||
@@ -169,6 +175,18 @@ describe('Bitrate', function () {
|
||||
expect(getAverageTheoreticalBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000)
|
||||
}
|
||||
})
|
||||
|
||||
describe('Ratio', function () {
|
||||
|
||||
it('Should have the correct aspect ratio in landscape', function () {
|
||||
expect(buildAspectRatio({ width: 1920, height: 1080 })).to.equal(1.7778)
|
||||
expect(buildAspectRatio({ width: 1000, height: 1000 })).to.equal(1)
|
||||
})
|
||||
|
||||
it('Should have the correct aspect ratio in portrait', function () {
|
||||
expect(buildAspectRatio({ width: 1080, height: 1920 })).to.equal(0.5625)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Parse semantic version string', function () {
|
||||
|
||||
@@ -103,9 +103,15 @@ async function testImage (url: string, imageName: string, imageHTTPPath: string,
|
||||
? PNG.sync.read(data)
|
||||
: JPEG.decode(data)
|
||||
|
||||
const result = pixelmatch(img1.data, img2.data, null, img1.width, img1.height, { threshold: 0.1 })
|
||||
const errorMsg = `${imageHTTPPath} image is not the same as ${imageName}${extension}`
|
||||
|
||||
expect(result).to.equal(0, `${imageHTTPPath} image is not the same as ${imageName}${extension}`)
|
||||
try {
|
||||
const result = pixelmatch(img1.data, img2.data, null, img1.width, img1.height, { threshold: 0.1 })
|
||||
|
||||
expect(result).to.equal(0, errorMsg)
|
||||
} catch (err) {
|
||||
throw new Error(`${errorMsg}: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function testFileExistsOrNot (server: PeerTubeServer, directory: string, filePath: string, exist: boolean) {
|
||||
|
||||
@@ -66,6 +66,8 @@ async function testLiveVideoResolutions (options: {
|
||||
expect(data.find(v => v.uuid === liveVideoId)).to.exist
|
||||
|
||||
const video = await server.videos.get({ id: liveVideoId })
|
||||
|
||||
expect(video.aspectRatio).to.equal(1.7778)
|
||||
expect(video.streamingPlaylists).to.have.lengthOf(1)
|
||||
|
||||
const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
|
||||
|
||||
@@ -145,6 +145,9 @@ async function completeCheckHlsPlaylist (options: {
|
||||
expect(file.resolution.label).to.equal(resolution + 'p')
|
||||
}
|
||||
|
||||
expect(Math.min(file.height, file.width)).to.equal(resolution)
|
||||
expect(Math.max(file.height, file.width)).to.be.greaterThan(resolution)
|
||||
|
||||
expect(file.magnetUri).to.have.lengthOf.above(2)
|
||||
await checkWebTorrentWorks(file.magnetUri)
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ export async function completeWebVideoFilesCheck (options: {
|
||||
fixture: string
|
||||
files: {
|
||||
resolution: number
|
||||
width?: number
|
||||
height?: number
|
||||
size?: number
|
||||
}[]
|
||||
objectStorageBaseUrl?: string
|
||||
@@ -84,7 +86,9 @@ export async function completeWebVideoFilesCheck (options: {
|
||||
makeRawRequest({
|
||||
url: file.fileDownloadUrl,
|
||||
token,
|
||||
expectedStatus: objectStorageBaseUrl ? HttpStatusCode.FOUND_302 : HttpStatusCode.OK_200
|
||||
expectedStatus: objectStorageBaseUrl
|
||||
? HttpStatusCode.FOUND_302
|
||||
: HttpStatusCode.OK_200
|
||||
})
|
||||
])
|
||||
}
|
||||
@@ -97,6 +101,12 @@ export async function completeWebVideoFilesCheck (options: {
|
||||
expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
|
||||
}
|
||||
|
||||
if (attributeFile.width !== undefined) expect(file.width).to.equal(attributeFile.width)
|
||||
if (attributeFile.height !== undefined) expect(file.height).to.equal(attributeFile.height)
|
||||
|
||||
expect(Math.min(file.height, file.width)).to.equal(file.resolution.id)
|
||||
expect(Math.max(file.height, file.width)).to.be.greaterThan(file.resolution.id)
|
||||
|
||||
if (attributeFile.size) {
|
||||
const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
|
||||
const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
|
||||
@@ -156,6 +166,8 @@ export async function completeVideoCheck (options: {
|
||||
files?: {
|
||||
resolution: number
|
||||
size: number
|
||||
width: number
|
||||
height: number
|
||||
}[]
|
||||
|
||||
hls?: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { basename, join } from 'path'
|
||||
import type { Instance, Torrent } from 'webtorrent'
|
||||
import { VideoFile } from '@peertube/peertube-models'
|
||||
import { PeerTubeServer } from '@peertube/peertube-server-commands'
|
||||
import type { Instance as MagnetUriInstance } from 'magnet-uri'
|
||||
|
||||
let webtorrent: Instance
|
||||
|
||||
@@ -28,6 +29,14 @@ export async function parseTorrentVideo (server: PeerTubeServer, file: VideoFile
|
||||
return (await import('parse-torrent')).default(data)
|
||||
}
|
||||
|
||||
export async function magnetUriDecode (data: string) {
|
||||
return (await import('magnet-uri')).decode(data)
|
||||
}
|
||||
|
||||
export async function magnetUriEncode (data: MagnetUriInstance) {
|
||||
return (await import('magnet-uri')).encode(data)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user