First commit
This commit is contained in:
parent
84a9b530d4
commit
0ea31a2e56
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -6,3 +6,5 @@ lib
|
||||||
coverage
|
coverage
|
||||||
@types
|
@types
|
||||||
|
|
||||||
|
public/bundle.js
|
||||||
|
/public/bundle.js.map
|
||||||
|
|
14
captain-definition
Normal file
14
captain-definition
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"dockerfileLines": [
|
||||||
|
"FROM node:20-alpine",
|
||||||
|
"RUN mkdir -p /usr/src/app",
|
||||||
|
"WORKDIR /usr/src/app",
|
||||||
|
"COPY ./ /usr/src/app",
|
||||||
|
"RUN npm install && npm cache clean --force && npm run build",
|
||||||
|
"ENV NODE_ENV production",
|
||||||
|
"ENV PORT 80",
|
||||||
|
"EXPOSE 80",
|
||||||
|
"CMD [ \"npm\", \"start\" ]"
|
||||||
|
]
|
||||||
|
}
|
780
package-lock.json
generated
780
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
@ -5,12 +5,13 @@
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha",
|
"test": "mocha",
|
||||||
"build": "tsc",
|
"build": "esbuild src/browser.ts --bundle --minify --sourcemap --target=firefox120 --outfile=public/bundle.js",
|
||||||
"prepare": "npm run build",
|
"prepare": "npm run build",
|
||||||
"clean-coverage": "rm -rf coverage .nyc_output .xunit",
|
"clean-coverage": "rm -rf coverage .nyc_output .xunit",
|
||||||
"coverage": "npm run clean-coverage && nyc mocha",
|
"coverage": "npm run clean-coverage && nyc mocha",
|
||||||
"test-reports": "npm run clean-coverage && nyc --reporter clover mocha --reporter xunit --reporter-option output=.xunit",
|
"test-reports": "npm run clean-coverage && nyc --reporter clover mocha --reporter xunit --reporter-option output=.xunit",
|
||||||
"coverage-html": "npm run test-reports && nyc report --reporter lcov && xdg-open coverage/lcov-report/index.html"
|
"coverage-html": "npm run test-reports && nyc report --reporter lcov && xdg-open coverage/lcov-report/index.html",
|
||||||
|
"start": "http-server public -p 8080"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
"@types/mocha": "^10.0.1",
|
"@types/mocha": "^10.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
||||||
"@typescript-eslint/parser": "^5.58.0",
|
"@typescript-eslint/parser": "^5.58.0",
|
||||||
|
"esbuild": "^0.19.9",
|
||||||
"eslint": "^8.38.0",
|
"eslint": "^8.38.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
@ -31,5 +33,8 @@
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
},
|
},
|
||||||
"types": "./@types/index.d.ts"
|
"types": "./@types/index.d.ts",
|
||||||
|
"dependencies": {
|
||||||
|
"http-server": "^14.1.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
81
public/index.html
Normal file
81
public/index.html
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Podcast chapter converter</title>
|
||||||
|
<style>
|
||||||
|
#container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="/bundle.js"></script>
|
||||||
|
<script>
|
||||||
|
function convert() {
|
||||||
|
output.value = window.encoders[to.value](
|
||||||
|
window.parsers[from.value](input.value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function download(data, filename, type) {
|
||||||
|
var file = new Blob([data], { type: type });
|
||||||
|
var a = document.createElement("a"),
|
||||||
|
url = URL.createObjectURL(file);
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
setTimeout(function () {
|
||||||
|
document.body.removeChild(a);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function download_output() {
|
||||||
|
download(
|
||||||
|
output.value,
|
||||||
|
"chapters." + to.value,
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<h1>Convert podcast chapters</h1>
|
||||||
|
<div id="container">
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
From:
|
||||||
|
<select id="from">
|
||||||
|
<option value="audacity">Audacity labels export</option>
|
||||||
|
<option value="youtube">
|
||||||
|
Youtube plaintext timestamps
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<textarea rows="40" cols="60" id="input"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onClick="convert()">Convert→</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
To:
|
||||||
|
<select id="to" onchange="convert()">
|
||||||
|
<option value="json">
|
||||||
|
JSON Chapters (for Castapod)
|
||||||
|
</option>
|
||||||
|
<option value="youtube">
|
||||||
|
Youtube plaintext timestamps
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button onclick="download_output()">download</button>
|
||||||
|
<div>
|
||||||
|
<textarea rows="40" cols="60" id="output"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
4
src/browser.ts
Normal file
4
src/browser.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { encoders, parsers } from ".";
|
||||||
|
|
||||||
|
(window as any).parsers = parsers;
|
||||||
|
(window as any).encoders = encoders;
|
|
@ -1,11 +0,0 @@
|
||||||
import { Example } from "./index";
|
|
||||||
import * as assert from "assert";
|
|
||||||
|
|
||||||
describe("Example", () => {
|
|
||||||
describe("example", () => {
|
|
||||||
it("should equal 'example'", () => {
|
|
||||||
const example = new Example();
|
|
||||||
assert.equal(example.example(), "example");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
69
src/index.ts
69
src/index.ts
|
@ -1,5 +1,68 @@
|
||||||
export class Example {
|
import fs from "fs";
|
||||||
example(): string {
|
|
||||||
return "example";
|
type Timestamp = {
|
||||||
|
timestamp: number;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function parse_audacity(content: string): Timestamp[] {
|
||||||
|
const lines = content.split("\n");
|
||||||
|
return lines
|
||||||
|
.filter((line) => line.length > 4)
|
||||||
|
.map((line) => line.split("\t"))
|
||||||
|
.map(([start_s, _, title]) => ({
|
||||||
|
timestamp: parseFloat(start_s),
|
||||||
|
title,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseTimestamp(s: string): number {
|
||||||
|
return s
|
||||||
|
.split(":")
|
||||||
|
.map((e, i, all) => Math.pow(60, all.length - i - 1) * parseInt(e))
|
||||||
|
.reduce((a, b) => a + b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toTimestamp(n: number): string {
|
||||||
|
return `${Math.floor(n / 60)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")}:${(n % 60).toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parse_youtube(content: string): Timestamp[] {
|
||||||
|
console.log(
|
||||||
|
content
|
||||||
|
.split("\n")
|
||||||
|
.filter((l) => l.length > 4)
|
||||||
|
.map((l) => l.match(/([0-9]{1,}:[0-9]{2})\s(.*)/))
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
content
|
||||||
|
.split("\n")
|
||||||
|
.filter((l) => l.length > 4)
|
||||||
|
.map((l) => l.match(/([0-9]{1,}:[0-9]{2})\s(.*)/))
|
||||||
|
.filter((m) => m != null) as string[][]
|
||||||
|
).map(([_, time_s, title]) => ({
|
||||||
|
title,
|
||||||
|
timestamp: parseTimestamp(time_s),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function to_youtube(timestamps: Timestamp[]): string {
|
||||||
|
return timestamps
|
||||||
|
.map((ts) => `${toTimestamp(ts.timestamp)} ${ts.title}\n`)
|
||||||
|
.join("\n"); // yes, double newlines
|
||||||
|
}
|
||||||
|
|
||||||
|
export function to_json(timestamps: Timestamp[]): string {
|
||||||
|
return JSON.stringify({
|
||||||
|
version: "1.2",
|
||||||
|
chapters: timestamps.map((ts) => ({
|
||||||
|
startTime: ts.timestamp,
|
||||||
|
title: ts.title,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parsers = { youtube: parse_youtube, audacity: parse_audacity };
|
||||||
|
export const encoders = { youtube: to_youtube, json: to_json };
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["ES6", "ESNext"],
|
"lib": ["ES6", "ESNext", "DOM"],
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
@ -17,6 +17,6 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", ],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules/**"]
|
"exclude": ["node_modules/**"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user