diff --git a/src/custom-media.js b/src/custom-media.js index 22e182fd..5a8d4e74 100644 --- a/src/custom-media.js +++ b/src/custom-media.js @@ -1,6 +1,8 @@ import { ValidationError } from './errors'; import { parse as parseURL } from 'url'; import net from 'net'; +import Media from './media'; +import { hash } from './util/hash'; const SOURCE_QUALITIES = new Set([ 240, @@ -23,6 +25,40 @@ const SOURCE_CONTENT_TYPES = new Set([ 'video/webm' ]); +export function convert(data) { + validate(data); + + if (data.live) data.duration = 0; + + const sources = {}; + + for (let source of data.sources) { + if (!sources.hasOwnProperty(source.quality)) + sources[source.quality] = []; + + sources[source.quality].push({ + link: source.url, + contentType: source.contentType, + quality: source.quality + }); + } + + const meta = { + direct: sources, + textTracks: data.textTracks, + thumbnail: data.thumbnail, // Currently ignored by Media + live: !!data.live // Currently ignored by Media + }; + + const id = hash('sha256', JSON.stringify([ + data.title, + data.duration, + meta + ]), 'base64'); + + return new Media(id, data.title, data.duration, 'cm', meta); +} + export function validate(data) { if (typeof data.title !== 'string') throw new ValidationError('title must be a string'); diff --git a/src/media.js b/src/media.js index a07416e2..b9503645 100644 --- a/src/media.js +++ b/src/media.js @@ -38,7 +38,8 @@ Media.prototype = { bitrate: this.meta.bitrate, scuri: this.meta.scuri, embed: this.meta.embed, - gdrive_subtitles: this.meta.gdrive_subtitles + gdrive_subtitles: this.meta.gdrive_subtitles, + textTracks: this.meta.textTracks } }; }, diff --git a/src/util/hash.js b/src/util/hash.js new file mode 100644 index 00000000..ed9428e1 --- /dev/null +++ b/src/util/hash.js @@ -0,0 +1,7 @@ +import { createHash } from 'crypto'; + +export function hash(algo, input, digest) { + const h = createHash(algo); + h.update(input); + return h.digest(digest); +} diff --git a/test/custom-media.js b/test/custom-media.js index 9234563a..e3f24149 100644 --- a/test/custom-media.js +++ b/test/custom-media.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const { validate } = require('../lib/custom-media'); +const { validate, convert } = require('../lib/custom-media'); describe('custom-media', () => { let valid, invalid; @@ -203,4 +203,71 @@ describe('custom-media', () => { assert.throws(() => validate(invalid), /URL hostname must be a domain name/); }); }); + + describe('#convert', () => { + let expected; + + beforeEach(() => { + expected = { + title: 'Test Video', + seconds: 10, + duration: '00:10', + type: 'cm', + meta: { + direct: { + 1080: [ + { + link: 'https://example.com/video.mp4', + contentType: 'video/mp4', + quality: 1080 + } + ] + }, + textTracks: [ + { + url: 'https://example.com/subtitles.vtt', + contentType: 'text/vtt', + name: 'English Subtitles' + } + ] + } + }; + }); + + function cleanForComparison(actual) { + actual = actual.pack(); + delete actual.id; + + // Strip out extraneous undefineds + for (let key in actual.meta) { + if (actual.meta[key] === undefined) delete actual.meta[key]; + } + + return actual; + } + + it('converts custom metadata to a CyTube Media object', () => { + const media = convert(valid); + + assert(media.id != null, 'should have generated id'); + + const actual = cleanForComparison(media); + + assert.deepStrictEqual(actual, expected); + }); + + it('sets duration to 0 if live = true', () => { + valid.live = true; + expected.duration = '00:00'; + expected.seconds = 0; + + const media = convert(valid); + + assert(media.id != null, 'should have generated id'); + + const actual = cleanForComparison(media); + + assert.deepStrictEqual(actual, expected); + }); + }); }); diff --git a/test/util/hash.js b/test/util/hash.js new file mode 100644 index 00000000..2e6c67c3 --- /dev/null +++ b/test/util/hash.js @@ -0,0 +1,18 @@ +const { hash } = require('../../lib/util/hash'); +const assert = require('assert'); + +describe('hash', () => { + describe('#hash', () => { + const input = 'this is a test'; + + it('hashes input correctly', () => { + const sha256_hex = '2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c'; + assert.strictEqual(hash('sha256', input, 'hex'), sha256_hex); + }); + + it('hashes input to base64', () => { + const sha256_base64 = 'Lpl1hUiXKo6IIq1H+hAX/3Lwbz/2oBaFH0XDmHMrxQw='; + assert.strictEqual(hash('sha256', input, 'base64'), sha256_base64); + }); + }); +});