Skip to content

Commit

Permalink
Add AppleHLS format to AutoFormat and test files
Browse files Browse the repository at this point in the history
  • Loading branch information
Mtillmann committed Feb 1, 2024
1 parent f4a4d4f commit 631b6f4
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 4 deletions.
96 changes: 96 additions & 0 deletions src/Formats/AppleHLS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { secondsToTimestamp, timestampToSeconds } from '../util.js';
import { FormatBase } from './FormatBase.js';


export class AppleHLS extends FormatBase {

filename = 'apple-hls.json';
mimeType = 'application/json';

titleLanguage = 'en';
imageDims = [1280, 720];

detect(inputString) {
try {
const data = JSON.parse(inputString);
const { errors } = this.test(data);
if (errors.length > 0) {
throw new Error('data test failed');
}
} catch (e) {
return false;
}
return true;
}

test(data) {
if (!Array.isArray(data)) {
return { errors: ['JSON Structure: must be an array'] };
}

if (data.length === 0) {
return { errors: ['JSON Structure: must not be empty'] };
}

if (!data.every(chapter => 'chapter' in chapter && 'start-time' in chapter)) {
return { errors: ['JSON Structure: every chapter must have a chapter and a start-time property'] };
}

return { errors: [] };
}


parse(string) {
const data = JSON.parse(string);
const { errors } = this.test(data);
if (errors.length > 0) {
throw new Error(errors.join(''));
}

this.chapters = data.map(raw => {
const chapter = {
startTime: parseFloat(raw['start-time'])
}

if ('titles' in raw && raw.titles.length > 0) {
chapter.title = raw.titles[0].title;
}

if ('images' in raw && raw.images.length > 0) {
chapter.img = raw.images[0].url;
}

return chapter;
});
}

toString(pretty = false) {
return JSON.stringify(this.chapters.map((c, i) => {

const chapter = {
'start-time': c.startTime,
chapter: i + 1,
titles: [
{
title: c.title || `Chapter ${i + 1}`,
language: this.titleLanguage
}
]
}

if (c.img) {
chapter.images = [
{
'image-category': "chapter",
url: c.img,
'pixel-width': this.imageDims[0],
'pixel-height': this.imageDims[1]
}
]
}

return chapter;
}), null, pretty ? 2 : 0);
}

}
4 changes: 3 additions & 1 deletion src/Formats/AutoFormat.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ShutterEDL } from "./ShutterEDL.js";
import { PodloveSimpleChapters } from "./PodloveSimpleChapters.js";
import { MP4Chaps } from "./MP4Chaps.js";
import { PodloveJson } from "./PodloveJson.js";
import { AppleHLS } from "./AppleHLS.js";

export const AutoFormat = {
classMap: {
Expand All @@ -30,7 +31,8 @@ export const AutoFormat = {
shutteredl: ShutterEDL,
psc: PodloveSimpleChapters,
mp4chaps: MP4Chaps,
podlovejson: PodloveJson
podlovejson: PodloveJson,
applehls: AppleHLS
},

detect(inputString, returnWhat = 'instance') {
Expand Down
3 changes: 2 additions & 1 deletion tests/conversions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { VorbisComment } from "../src/Formats/VorbisComment.js";
import { PodloveSimpleChapters } from "../src/Formats/PodloveSimpleChapters.js";
import { MP4Chaps } from "../src/Formats/MP4Chaps.js";
import { PodloveJson } from "../src/Formats/PodloveJson.js";
import { AppleHLS } from "../src/Formats/AppleHLS.js";
import { readFileSync } from "fs";
import { sep } from "path";

Expand All @@ -21,7 +22,7 @@ describe('conversions from one format to any other', () => {
MatroskaXML, MKVMergeXML, MKVMergeSimple,
PySceneDetect, AppleChapters, ShutterEDL,
VorbisComment, PodloveSimpleChapters, MP4Chaps,
PodloveJson
PodloveJson, AppleHLS
];

const content = readFileSync(module.path + sep + 'samples' + sep + 'chapters.json', 'utf-8');
Expand Down
60 changes: 60 additions & 0 deletions tests/format_applehls.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

import { readFileSync } from "fs";
import { sep } from "path";
import { ChaptersJson } from "../src/Formats/ChaptersJson.js";
import { AppleHLS } from "../src/Formats/AppleHLS.js";


describe('AppleHLS Format Handler', () => {
it('accepts no arguments', () => {
expect(() => {
new AppleHLS();
}).not.toThrowError(TypeError);
});


it('fails on malformed input', () => {
expect(() => {
new AppleHLS('asdf');
}).toThrowError(Error);
});

const content = readFileSync(module.path + sep + 'samples' + sep + 'applehls.json', 'utf-8');

it('parses well-formed input', () => {
expect(() => {
new AppleHLS(content);
}).not.toThrow(Error);
});

const instance = new AppleHLS(content);

it('has the correct number of chapters from content', () => {
expect(instance.chapters.length).toEqual(3);
});

it('has parsed the timestamps correctly', () => {
expect(instance.chapters[1].startTime).toBe(500.1)
});

it('has parsed the chapter titles correctly', () => {
expect(instance.chapters[0].title).toBe('birth')
});

it('exports to correct format', () => {
expect(instance.toString()).toContain('start-time":');
});

it('export includes correct timestamp', () => {
expect(instance.toString()).toContain('1200.2');
});

it('can import previously generated export', () => {
expect(new AppleHLS(instance.toString()).chapters[2].startTime).toEqual(1200.2);
});

it('can convert into other format', () => {
expect(instance.to(ChaptersJson)).toBeInstanceOf(ChaptersJson)
});

});
4 changes: 3 additions & 1 deletion tests/format_autodetection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import { PySceneDetect } from "../src/Formats/PySceneDetect.js";
import { ShutterEDL } from "../src/Formats/ShutterEDL.js";
import { VorbisComment } from "../src/Formats/VorbisComment.js";
import { PodloveJson } from "../src/Formats/PodloveJson.js";
import { AppleHLS } from "../src/Formats/AppleHLS.js";

describe('autodetection of sample files', () => {

const filesAndKeysAndHandlers = [
['applechapters.xml', 'applechapters', AppleChapters],
['applehls.json', 'applehls', AppleHLS],
['chapters.json', 'chaptersjson', ChaptersJson],
['FFMetadata.txt', 'ffmetadata', FFMetadata],
['ffmpeginfo.txt', 'ffmpeginfo', FFMpegInfo],
Expand All @@ -34,7 +36,7 @@ describe('autodetection of sample files', () => {
['shutter.edl', 'shutteredl', ShutterEDL],
['vorbiscomment.txt', 'vorbiscomment', VorbisComment],
['webvtt.txt', 'webvtt', WebVTT],
['youtube-chapters.txt', 'youtube', Youtube]
['youtube-chapters.txt', 'youtube', Youtube],
];

filesAndKeysAndHandlers.forEach(item => {
Expand Down
3 changes: 2 additions & 1 deletion tests/format_detection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { VorbisComment } from "../src/Formats/VorbisComment.js";
import { PodloveSimpleChapters } from "../src/Formats/PodloveSimpleChapters.js";
import { MP4Chaps } from "../src/Formats/MP4Chaps.js";
import { PodloveJson } from "../src/Formats/PodloveJson.js";
import { AppleHLS } from "../src/Formats/AppleHLS.js";
import { readFileSync } from "fs";
import { sep } from "path";

Expand All @@ -21,7 +22,7 @@ describe('detection of input strings', () => {
MatroskaXML, MKVMergeXML, MKVMergeSimple,
PySceneDetect, AppleChapters, ShutterEDL,
VorbisComment, PodloveSimpleChapters, MP4Chaps,
PodloveJson
PodloveJson, AppleHLS
];

const content = readFileSync(module.path + sep + 'samples' + sep + 'chapters.json', 'utf-8');
Expand Down
44 changes: 44 additions & 0 deletions tests/samples/applehls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[
{
"chapter": 1,
"start-time": 0,
"titles": [
{
"language": "en",
"title": "birth"
},
{
"language": "es",
"title": "nacimiento"
}
]
},
{
"chapter": 2,
"start-time": 500.1,
"titles": [
{
"language": "en",
"title": "life"
},
{
"language": "es",
"title": "vida"
}
]
},
{
"chapter": 3,
"start-time": 1200.2,
"titles": [
{
"language": "en",
"title": "death"
},
{
"language": "es",
"title": "muerte"
}
]
}
]
102 changes: 102 additions & 0 deletions tests/schema/applehls.json-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "HLS Chapter Data",
"description": "HLS chapter data format",
"type": "array",
"items": {
"description": "chapter entry",
"type": "object",
"properties": {
"chapter": {
"description": "Chapter number (optional)",
"type": "number",
"minimum": 1
},
"start-time": {
"description": "Chapter start time",
"type": "number",
"minimum": 0
},
"duration": {
"description": "Chapter duration (optional)",
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"titles": {
"description": "List of titles by language for chapter (optional)",
"type": "array",
"items": {
"description": "Title object",
"type": "object",
"properties": {
"language": {
"description": "BCP 47 language code; und for undefined",
"type": "string"
},
"title": {
"description": "Chapter title string",
"type": "string"
}
},
"required": ["language", "title"]
}
},
"images": {
"description": "List of images for chapter (optional)",
"type": "array",
"items": {
"description": "Image object",
"type": "object",
"properties": {
"image-category": {
"description": "Image category",
"type": "string"
},
"pixel-width": {
"description": "Pixel width",
"type": "integer",
"minimum": 0,
"exclusiveMinimum": true
},
"pixel-height": {
"description": "Pixel height",
"type": "integer",
"minimum": 0,
"exclusiveMinimum": true
},
"url": {
"description": "URL to image (relative or absolute)",
"type": "string"
}
},
"required": ["image-category", "pixel-width", "pixel-height", "url"]
}
},
"metadata": {
"description": "List of metadata entries for chapter (optional)",
"type": "array",
"items": {
"description": "Metadata object",
"type": "object",
"properties": {
"key": {
"description": "Key value name",
"type": "string"
},
"value": {
"description": "Metadata value",
"type": ["string", "number", "boolean", "array", "object"]
},
"language": {
"description": "BCP 47 language code (optional)",
"type": "string"
}
},
"required": ["key", "value"]
}
}
},
"required": ["start-time"]
}
}

0 comments on commit 631b6f4

Please sign in to comment.