@karaplay/file-coder 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BROWSER_API.md +318 -0
- package/README.md +216 -0
- package/REFACTORING_SUMMARY.txt +250 -0
- package/WORKFLOW_SUMMARY.txt +78 -0
- package/bin/ncntokar-cli.js +39 -0
- package/dist/client.d.ts +26 -0
- package/dist/client.js +74 -0
- package/dist/emk/client-decoder.d.ts +22 -0
- package/dist/emk/client-decoder.js +133 -0
- package/dist/emk/server-decode.d.ts +21 -0
- package/dist/emk/server-decode.js +123 -0
- package/dist/emk-to-kar.browser.d.ts +51 -0
- package/dist/emk-to-kar.browser.js +139 -0
- package/dist/emk-to-kar.d.ts +53 -0
- package/dist/emk-to-kar.js +210 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +64 -0
- package/dist/kar-reader.browser.d.ts +46 -0
- package/dist/kar-reader.browser.js +209 -0
- package/dist/kar-reader.d.ts +42 -0
- package/dist/kar-reader.js +197 -0
- package/dist/ncntokar.browser.d.ts +99 -0
- package/dist/ncntokar.browser.js +296 -0
- package/dist/ncntokar.d.ts +88 -0
- package/dist/ncntokar.js +340 -0
- package/examples/NextJSComponent.tsx +175 -0
- package/libs/emk/client-decoder.ts +142 -0
- package/libs/emk/server-decode.ts +133 -0
- package/libs/ncntokar.js +256 -0
- package/package.json +79 -0
- package/songs/.gitkeep +3 -0
- package/songs/cur/BPL3457.cur +0 -0
- package/songs/cur/Z2510006.cur +0 -0
- package/songs/cur/Z2510008.cur +0 -0
- package/songs/cur/Z2510136.cur +0 -0
- package/songs/cur/Z2510137.cur +0 -0
- package/songs/cur/Z2510138.cur +0 -0
- package/songs/cur/Z2510139.cur +0 -0
- package/songs/cur/Z2510140.cur +0 -0
- package/songs/cur/Z2510141.cur +0 -0
- package/songs/cur/song.cur +0 -0
- package/songs/emk/Z2510001.emk +0 -0
- package/songs/emk/Z2510002.emk +0 -0
- package/songs/emk/Z2510003.emk +0 -0
- package/songs/emk/Z2510004.emk +0 -0
- package/songs/emk/Z2510005.emk +0 -0
- package/songs/emk/Z2510006.emk +0 -0
- package/songs/kar/bpl3457.kar +0 -0
- package/songs/kar/z2510006.kar +0 -0
- package/songs/kar/z2510008.kar +0 -0
- package/songs/kar/z2510136.kar +0 -0
- package/songs/kar/z2510137.kar +0 -0
- package/songs/kar/z2510138.kar +0 -0
- package/songs/kar/z2510139.kar +0 -0
- package/songs/kar/z2510140.kar +0 -0
- package/songs/kar/z2510141.kar +0 -0
- package/songs/lyr/BPL3457.lyr +57 -0
- package/songs/lyr/Z2510006.lyr +53 -0
- package/songs/lyr/Z2510008.lyr +57 -0
- package/songs/lyr/Z2510136.lyr +54 -0
- package/songs/lyr/Z2510137.lyr +66 -0
- package/songs/lyr/Z2510138.lyr +62 -0
- package/songs/lyr/Z2510139.lyr +60 -0
- package/songs/lyr/Z2510140.lyr +41 -0
- package/songs/lyr/Z2510141.lyr +48 -0
- package/songs/lyr/song.lyr +37 -0
- package/songs/midi/BPL3457.MID +0 -0
- package/songs/midi/Z2510006.mid +0 -0
- package/songs/midi/Z2510008.mid +0 -0
- package/songs/midi/Z2510136.mid +0 -0
- package/songs/midi/Z2510137.mid +0 -0
- package/songs/midi/Z2510138.mid +0 -0
- package/songs/midi/Z2510139.mid +0 -0
- package/songs/midi/Z2510140.mid +0 -0
- package/songs/midi/Z2510141.mid +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Main decoding logic for .emk (Extreme Karaoke) files.
|
|
4
|
+
* This is the SERVER-SIDE implementation using Node.js's 'zlib'.
|
|
5
|
+
* It is used by the GET /api/emk/import endpoint for playback.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.xorDecrypt = xorDecrypt;
|
|
9
|
+
exports.looksLikeText = looksLikeText;
|
|
10
|
+
exports.decodeEmk = decodeEmk;
|
|
11
|
+
exports.parseSongInfo = parseSongInfo;
|
|
12
|
+
const zlib_1 = require("zlib");
|
|
13
|
+
const XOR_KEY = Buffer.from([0xAF, 0xF2, 0x4C, 0x9C, 0xE9, 0xEA, 0x99, 0x43]);
|
|
14
|
+
const MAGIC_SIGNATURE = '.SFDS';
|
|
15
|
+
const ZLIB_SECOND_BYTES = new Set([0x01, 0x5E, 0x9C, 0xDA, 0x7D, 0x20, 0xBB]);
|
|
16
|
+
function xorDecrypt(data) {
|
|
17
|
+
const decrypted = Buffer.alloc(data.length);
|
|
18
|
+
for (let i = 0; i < data.length; i++) {
|
|
19
|
+
decrypted[i] = data[i] ^ XOR_KEY[i % XOR_KEY.length];
|
|
20
|
+
}
|
|
21
|
+
return decrypted;
|
|
22
|
+
}
|
|
23
|
+
function looksLikeText(buf) {
|
|
24
|
+
const sample = buf.subarray(0, Math.min(64, buf.length));
|
|
25
|
+
for (let i = 0; i < sample.length; i++) {
|
|
26
|
+
const c = sample[i];
|
|
27
|
+
if (c === 0x0a || c === 0x0d || c === 0x09 || (c >= 0x20 && c <= 0x7e) || c >= 0x80) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function decodeEmk(fileBuffer) {
|
|
35
|
+
const decryptedBuffer = xorDecrypt(fileBuffer);
|
|
36
|
+
const magic = decryptedBuffer.subarray(0, MAGIC_SIGNATURE.length).toString('utf-8');
|
|
37
|
+
if (magic !== MAGIC_SIGNATURE) {
|
|
38
|
+
throw new Error(`Invalid EMK file signature. Expected '${MAGIC_SIGNATURE}' but got '${magic}'.`);
|
|
39
|
+
}
|
|
40
|
+
const result = {};
|
|
41
|
+
const inflatedParts = [];
|
|
42
|
+
for (let i = 0; i < decryptedBuffer.length - 2; i++) {
|
|
43
|
+
const b0 = decryptedBuffer[i];
|
|
44
|
+
const b1 = decryptedBuffer[i + 1];
|
|
45
|
+
if (b0 !== 0x78 || !ZLIB_SECOND_BYTES.has(b1))
|
|
46
|
+
continue;
|
|
47
|
+
try {
|
|
48
|
+
const inflated = (0, zlib_1.inflateSync)(decryptedBuffer.subarray(i));
|
|
49
|
+
inflatedParts.push(inflated);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (inflatedParts.length < 3) {
|
|
56
|
+
throw new Error(`Invalid EMK structure: expected at least 3 zlib blocks, found ${inflatedParts.length}.`);
|
|
57
|
+
}
|
|
58
|
+
for (const inflated of inflatedParts) {
|
|
59
|
+
const asciiPrefix = inflated.subarray(0, 16).toString('ascii');
|
|
60
|
+
if (asciiPrefix.startsWith('SIGNATURE=')) {
|
|
61
|
+
result.header = inflated;
|
|
62
|
+
}
|
|
63
|
+
else if (asciiPrefix.startsWith('CODE=')) {
|
|
64
|
+
result.songInfo = inflated;
|
|
65
|
+
}
|
|
66
|
+
else if (inflated.subarray(0, 4).toString('ascii') === 'MThd') {
|
|
67
|
+
result.midi = inflated;
|
|
68
|
+
}
|
|
69
|
+
else if (looksLikeText(inflated)) {
|
|
70
|
+
if (!result.lyric) {
|
|
71
|
+
result.lyric = inflated;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
result.cursor = inflated;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
if (!result.cursor) {
|
|
79
|
+
result.cursor = inflated;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!result.midi)
|
|
84
|
+
throw new Error('MIDI data block not found in EMK file.');
|
|
85
|
+
if (!result.lyric)
|
|
86
|
+
throw new Error('Lyric data block not found in EMK file.');
|
|
87
|
+
if (!result.cursor)
|
|
88
|
+
throw new Error('Cursor data block not found in EMK file.');
|
|
89
|
+
if (!result.songInfo) {
|
|
90
|
+
result.songInfo = Buffer.from('CODE=\nTITLE=Unknown Title\nARTIST=Unknown Artist');
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Parses the "song info" block from a Buffer.
|
|
96
|
+
* @param songInfoBuffer The buffer containing the song info data.
|
|
97
|
+
* @returns A record containing TITLE, ARTIST, and CODE.
|
|
98
|
+
*/
|
|
99
|
+
function parseSongInfo(songInfoBuffer) {
|
|
100
|
+
if (!songInfoBuffer)
|
|
101
|
+
return {};
|
|
102
|
+
try {
|
|
103
|
+
const text = new TextDecoder('windows-874', { fatal: false }).decode(songInfoBuffer);
|
|
104
|
+
const lines = text.split(/\r?\n/);
|
|
105
|
+
const info = {};
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
const parts = line.split('=');
|
|
108
|
+
if (parts.length >= 2) {
|
|
109
|
+
const key = parts[0].trim().toUpperCase();
|
|
110
|
+
const value = parts.slice(1).join('=').trim();
|
|
111
|
+
// Only store relevant keys to keep it focused
|
|
112
|
+
if (key === 'TITLE' || key === 'ARTIST' || key === 'CODE' || key === 'title' || key === 'artist' || key === 'code') {
|
|
113
|
+
info[key.toUpperCase()] = value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return info;
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
console.error("Failed to parse song info", e);
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-compatible EMK to KAR workflow
|
|
3
|
+
* Complete pipeline for client-side processing
|
|
4
|
+
*/
|
|
5
|
+
export interface BrowserEmkToKarOptions {
|
|
6
|
+
emkBuffer: Buffer;
|
|
7
|
+
outputFileName?: string;
|
|
8
|
+
autoDownload?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface BrowserEmkToKarResult {
|
|
11
|
+
success: boolean;
|
|
12
|
+
karBuffer: Buffer;
|
|
13
|
+
fileName: string;
|
|
14
|
+
intermediateBuffers?: {
|
|
15
|
+
midi: Buffer;
|
|
16
|
+
lyric: Buffer;
|
|
17
|
+
cursor: Buffer;
|
|
18
|
+
};
|
|
19
|
+
metadata: {
|
|
20
|
+
title: string;
|
|
21
|
+
artist: string;
|
|
22
|
+
code?: string;
|
|
23
|
+
};
|
|
24
|
+
warnings: string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Browser-compatible EMK to KAR conversion
|
|
28
|
+
* Complete pipeline: EMK buffer → decode → convert → KAR buffer
|
|
29
|
+
*/
|
|
30
|
+
export declare function convertEmkToKarBrowser(options: BrowserEmkToKarOptions): BrowserEmkToKarResult;
|
|
31
|
+
/**
|
|
32
|
+
* Process EMK File object directly (for file input in browser)
|
|
33
|
+
*/
|
|
34
|
+
export declare function convertEmkFileToKar(emkFile: File, options?: {
|
|
35
|
+
autoDownload?: boolean;
|
|
36
|
+
}): Promise<BrowserEmkToKarResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Process multiple EMK files in the browser
|
|
39
|
+
*/
|
|
40
|
+
export declare function convertEmkFilesBatch(emkFiles: File[], options?: {
|
|
41
|
+
autoDownload?: boolean;
|
|
42
|
+
}): Promise<BrowserEmkToKarResult[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Validate Thai text readability from buffer (browser version)
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateThaiLyricReadabilityBrowser(lyricBuffer: Buffer): {
|
|
47
|
+
readable: boolean;
|
|
48
|
+
encoding: string;
|
|
49
|
+
preview: string[];
|
|
50
|
+
charCount: number;
|
|
51
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Browser-compatible EMK to KAR workflow
|
|
4
|
+
* Complete pipeline for client-side processing
|
|
5
|
+
*/
|
|
6
|
+
'use client';
|
|
7
|
+
/**
|
|
8
|
+
* Browser-compatible EMK to KAR workflow
|
|
9
|
+
* Complete pipeline for client-side processing
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.convertEmkToKarBrowser = convertEmkToKarBrowser;
|
|
13
|
+
exports.convertEmkFileToKar = convertEmkFileToKar;
|
|
14
|
+
exports.convertEmkFilesBatch = convertEmkFilesBatch;
|
|
15
|
+
exports.validateThaiLyricReadabilityBrowser = validateThaiLyricReadabilityBrowser;
|
|
16
|
+
const client_decoder_1 = require("./emk/client-decoder");
|
|
17
|
+
const ncntokar_browser_1 = require("./ncntokar.browser");
|
|
18
|
+
/**
|
|
19
|
+
* Browser-compatible EMK to KAR conversion
|
|
20
|
+
* Complete pipeline: EMK buffer → decode → convert → KAR buffer
|
|
21
|
+
*/
|
|
22
|
+
function convertEmkToKarBrowser(options) {
|
|
23
|
+
const warnings = [];
|
|
24
|
+
try {
|
|
25
|
+
// Step 1: Decode EMK buffer
|
|
26
|
+
console.log('[Browser] [1/3] Decoding EMK buffer...');
|
|
27
|
+
const decoded = (0, client_decoder_1.decodeEmk)(options.emkBuffer);
|
|
28
|
+
// Verify all parts are present
|
|
29
|
+
if (!decoded.midi)
|
|
30
|
+
throw new Error('MIDI data not found in EMK file');
|
|
31
|
+
if (!decoded.lyric)
|
|
32
|
+
throw new Error('Lyric data not found in EMK file');
|
|
33
|
+
if (!decoded.cursor)
|
|
34
|
+
throw new Error('Cursor data not found in EMK file');
|
|
35
|
+
console.log('[Browser] [1/3] ✓ Decoded: MIDI, Lyric, Cursor');
|
|
36
|
+
// Extract metadata from EMK
|
|
37
|
+
const songInfo = (0, client_decoder_1.parseSongInfo)(decoded.songInfo);
|
|
38
|
+
const lyricMetadata = (0, ncntokar_browser_1.parseLyricBuffer)(decoded.lyric);
|
|
39
|
+
const metadata = {
|
|
40
|
+
title: lyricMetadata.title || songInfo.TITLE || 'Unknown Title',
|
|
41
|
+
artist: lyricMetadata.artist || songInfo.ARTIST || 'Unknown Artist',
|
|
42
|
+
code: songInfo.CODE
|
|
43
|
+
};
|
|
44
|
+
console.log(`[Browser] [2/3] Converting to KAR: ${metadata.title} - ${metadata.artist}`);
|
|
45
|
+
// Step 2: Convert NCN buffers to KAR
|
|
46
|
+
const conversionResult = (0, ncntokar_browser_1.convertNcnToKarBrowser)({
|
|
47
|
+
midiBuffer: decoded.midi,
|
|
48
|
+
lyricBuffer: decoded.lyric,
|
|
49
|
+
cursorBuffer: decoded.cursor,
|
|
50
|
+
outputFileName: options.outputFileName || 'output.kar'
|
|
51
|
+
});
|
|
52
|
+
warnings.push(...conversionResult.warnings);
|
|
53
|
+
console.log(`[Browser] [3/3] ✓ KAR buffer created`);
|
|
54
|
+
// Optionally auto-download
|
|
55
|
+
if (options.autoDownload) {
|
|
56
|
+
(0, ncntokar_browser_1.downloadBuffer)(conversionResult.karBuffer, conversionResult.fileName);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
karBuffer: conversionResult.karBuffer,
|
|
61
|
+
fileName: conversionResult.fileName,
|
|
62
|
+
intermediateBuffers: {
|
|
63
|
+
midi: decoded.midi,
|
|
64
|
+
lyric: decoded.lyric,
|
|
65
|
+
cursor: decoded.cursor
|
|
66
|
+
},
|
|
67
|
+
metadata,
|
|
68
|
+
warnings
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw new Error(`Browser EMK to KAR conversion failed: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Process EMK File object directly (for file input in browser)
|
|
77
|
+
*/
|
|
78
|
+
async function convertEmkFileToKar(emkFile, options) {
|
|
79
|
+
const emkBuffer = await (0, ncntokar_browser_1.fileToBuffer)(emkFile);
|
|
80
|
+
const fileName = emkFile.name.replace('.emk', '.kar');
|
|
81
|
+
return convertEmkToKarBrowser({
|
|
82
|
+
emkBuffer,
|
|
83
|
+
outputFileName: fileName,
|
|
84
|
+
autoDownload: options?.autoDownload ?? true
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Process multiple EMK files in the browser
|
|
89
|
+
*/
|
|
90
|
+
async function convertEmkFilesBatch(emkFiles, options) {
|
|
91
|
+
const results = [];
|
|
92
|
+
for (const emkFile of emkFiles) {
|
|
93
|
+
try {
|
|
94
|
+
const result = await convertEmkFileToKar(emkFile, {
|
|
95
|
+
autoDownload: options?.autoDownload ?? false
|
|
96
|
+
});
|
|
97
|
+
results.push(result);
|
|
98
|
+
console.log(`✓ ${emkFile.name}: ${result.metadata.title}`);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.error(`✗ ${emkFile.name}: ${error.message}`);
|
|
102
|
+
results.push({
|
|
103
|
+
success: false,
|
|
104
|
+
karBuffer: Buffer.alloc(0),
|
|
105
|
+
fileName: emkFile.name.replace('.emk', '.kar'),
|
|
106
|
+
metadata: {
|
|
107
|
+
title: 'Error',
|
|
108
|
+
artist: 'Error'
|
|
109
|
+
},
|
|
110
|
+
warnings: [error.message]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Download all if requested
|
|
115
|
+
if (options?.autoDownload) {
|
|
116
|
+
results
|
|
117
|
+
.filter(r => r.success)
|
|
118
|
+
.forEach(r => (0, ncntokar_browser_1.downloadBuffer)(r.karBuffer, r.fileName));
|
|
119
|
+
}
|
|
120
|
+
return results;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Validate Thai text readability from buffer (browser version)
|
|
124
|
+
*/
|
|
125
|
+
function validateThaiLyricReadabilityBrowser(lyricBuffer) {
|
|
126
|
+
const iconv = require('iconv-lite');
|
|
127
|
+
const text = iconv.decode(lyricBuffer, 'tis-620');
|
|
128
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
129
|
+
const thaiCharRegex = /[\u0E00-\u0E7F]/;
|
|
130
|
+
const hasThaiChars = thaiCharRegex.test(text);
|
|
131
|
+
const controlCharCount = (text.match(/[\x00-\x08\x0B-\x0C\x0E-\x1F]/g) || []).length;
|
|
132
|
+
const isReadable = controlCharCount < (text.length * 0.1);
|
|
133
|
+
return {
|
|
134
|
+
readable: isReadable,
|
|
135
|
+
encoding: 'TIS-620',
|
|
136
|
+
preview: lines.slice(0, 5),
|
|
137
|
+
charCount: text.length
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EMK to KAR Workflow
|
|
3
|
+
* Complete pipeline: EMK file → decode → MIDI + LYR + CUR → convert → KAR file
|
|
4
|
+
*/
|
|
5
|
+
export interface EmkToKarOptions {
|
|
6
|
+
inputEmk: string;
|
|
7
|
+
outputKar: string;
|
|
8
|
+
outputDir?: string;
|
|
9
|
+
keepIntermediateFiles?: boolean;
|
|
10
|
+
appendTitles?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface EmkToKarResult {
|
|
13
|
+
success: boolean;
|
|
14
|
+
outputKar: string;
|
|
15
|
+
intermediateFiles?: {
|
|
16
|
+
midi: string;
|
|
17
|
+
lyric: string;
|
|
18
|
+
cursor: string;
|
|
19
|
+
};
|
|
20
|
+
metadata: {
|
|
21
|
+
title: string;
|
|
22
|
+
artist: string;
|
|
23
|
+
code?: string;
|
|
24
|
+
};
|
|
25
|
+
warnings: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Complete workflow: Converts EMK file directly to KAR file
|
|
29
|
+
*
|
|
30
|
+
* Pipeline:
|
|
31
|
+
* 1. Decode EMK → MIDI + Lyric + Cursor files
|
|
32
|
+
* 2. Convert MIDI + Lyric + Cursor → KAR file
|
|
33
|
+
*
|
|
34
|
+
* @param options Configuration options
|
|
35
|
+
* @returns Result with metadata and warnings
|
|
36
|
+
*/
|
|
37
|
+
export declare function convertEmkToKar(options: EmkToKarOptions): EmkToKarResult;
|
|
38
|
+
/**
|
|
39
|
+
* Batch convert multiple EMK files to KAR
|
|
40
|
+
*/
|
|
41
|
+
export declare function convertEmkToKarBatch(emkFiles: string[], outputDirectory: string, options?: {
|
|
42
|
+
keepIntermediateFiles?: boolean;
|
|
43
|
+
appendTitles?: boolean;
|
|
44
|
+
}): EmkToKarResult[];
|
|
45
|
+
/**
|
|
46
|
+
* Validate Thai text readability from lyric buffer
|
|
47
|
+
*/
|
|
48
|
+
export declare function validateThaiLyricReadability(lyricBuffer: Buffer): {
|
|
49
|
+
readable: boolean;
|
|
50
|
+
encoding: string;
|
|
51
|
+
preview: string[];
|
|
52
|
+
charCount: number;
|
|
53
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* EMK to KAR Workflow
|
|
4
|
+
* Complete pipeline: EMK file → decode → MIDI + LYR + CUR → convert → KAR file
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.convertEmkToKar = convertEmkToKar;
|
|
41
|
+
exports.convertEmkToKarBatch = convertEmkToKarBatch;
|
|
42
|
+
exports.validateThaiLyricReadability = validateThaiLyricReadability;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const server_decode_1 = require("./emk/server-decode");
|
|
46
|
+
const ncntokar_1 = require("./ncntokar");
|
|
47
|
+
const iconv = __importStar(require("iconv-lite"));
|
|
48
|
+
/**
|
|
49
|
+
* Complete workflow: Converts EMK file directly to KAR file
|
|
50
|
+
*
|
|
51
|
+
* Pipeline:
|
|
52
|
+
* 1. Decode EMK → MIDI + Lyric + Cursor files
|
|
53
|
+
* 2. Convert MIDI + Lyric + Cursor → KAR file
|
|
54
|
+
*
|
|
55
|
+
* @param options Configuration options
|
|
56
|
+
* @returns Result with metadata and warnings
|
|
57
|
+
*/
|
|
58
|
+
function convertEmkToKar(options) {
|
|
59
|
+
const warnings = [];
|
|
60
|
+
// Validate input
|
|
61
|
+
if (!fs.existsSync(options.inputEmk)) {
|
|
62
|
+
throw new Error(`Input EMK file not found: ${options.inputEmk}`);
|
|
63
|
+
}
|
|
64
|
+
// Determine output directory for intermediate files
|
|
65
|
+
const outputDir = options.outputDir || path.join(path.dirname(options.outputKar), 'temp');
|
|
66
|
+
// Create temp directory if needed
|
|
67
|
+
if (!fs.existsSync(outputDir)) {
|
|
68
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
const baseFileName = path.basename(options.inputEmk, '.emk');
|
|
71
|
+
const midiFile = path.join(outputDir, `${baseFileName}.mid`);
|
|
72
|
+
const lyricFile = path.join(outputDir, `${baseFileName}.lyr`);
|
|
73
|
+
const cursorFile = path.join(outputDir, `${baseFileName}.cur`);
|
|
74
|
+
try {
|
|
75
|
+
// Step 1: Decode EMK file
|
|
76
|
+
console.log(`[1/3] Decoding EMK file: ${options.inputEmk}`);
|
|
77
|
+
const emkBuffer = fs.readFileSync(options.inputEmk);
|
|
78
|
+
const decoded = (0, server_decode_1.decodeEmk)(emkBuffer);
|
|
79
|
+
// Verify all parts are present
|
|
80
|
+
if (!decoded.midi)
|
|
81
|
+
throw new Error('MIDI data not found in EMK file');
|
|
82
|
+
if (!decoded.lyric)
|
|
83
|
+
throw new Error('Lyric data not found in EMK file');
|
|
84
|
+
if (!decoded.cursor)
|
|
85
|
+
throw new Error('Cursor data not found in EMK file');
|
|
86
|
+
// Write intermediate files
|
|
87
|
+
fs.writeFileSync(midiFile, decoded.midi);
|
|
88
|
+
fs.writeFileSync(lyricFile, decoded.lyric);
|
|
89
|
+
fs.writeFileSync(cursorFile, decoded.cursor);
|
|
90
|
+
console.log(`[1/3] ✓ Decoded to: MIDI, Lyric, Cursor`);
|
|
91
|
+
// Extract metadata from EMK
|
|
92
|
+
const songInfo = (0, server_decode_1.parseSongInfo)(decoded.songInfo);
|
|
93
|
+
// Parse lyric file to get title and artist (more reliable)
|
|
94
|
+
const lyricMetadata = (0, ncntokar_1.parseLyricFile)(lyricFile);
|
|
95
|
+
const metadata = {
|
|
96
|
+
title: lyricMetadata.title || songInfo.TITLE || 'Unknown Title',
|
|
97
|
+
artist: lyricMetadata.artist || songInfo.ARTIST || 'Unknown Artist',
|
|
98
|
+
code: songInfo.CODE
|
|
99
|
+
};
|
|
100
|
+
console.log(`[2/3] Converting to KAR: ${metadata.title} - ${metadata.artist}`);
|
|
101
|
+
// Step 2: Convert NCN to KAR
|
|
102
|
+
const conversionResult = (0, ncntokar_1.convertNcnToKar)({
|
|
103
|
+
inputMidi: midiFile,
|
|
104
|
+
inputLyr: lyricFile,
|
|
105
|
+
inputCur: cursorFile,
|
|
106
|
+
outputKar: options.outputKar,
|
|
107
|
+
appendTitles: options.appendTitles || false
|
|
108
|
+
});
|
|
109
|
+
warnings.push(...conversionResult.warnings);
|
|
110
|
+
console.log(`[3/3] ✓ KAR file created: ${options.outputKar}`);
|
|
111
|
+
// Clean up intermediate files if requested
|
|
112
|
+
if (!options.keepIntermediateFiles) {
|
|
113
|
+
fs.unlinkSync(midiFile);
|
|
114
|
+
fs.unlinkSync(lyricFile);
|
|
115
|
+
fs.unlinkSync(cursorFile);
|
|
116
|
+
// Remove temp directory if empty
|
|
117
|
+
try {
|
|
118
|
+
fs.rmdirSync(outputDir);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Directory not empty or doesn't exist, ignore
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
outputKar: options.outputKar,
|
|
127
|
+
intermediateFiles: options.keepIntermediateFiles ? {
|
|
128
|
+
midi: midiFile,
|
|
129
|
+
lyric: lyricFile,
|
|
130
|
+
cursor: cursorFile
|
|
131
|
+
} : undefined,
|
|
132
|
+
metadata,
|
|
133
|
+
warnings
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
// Clean up on error
|
|
138
|
+
try {
|
|
139
|
+
if (fs.existsSync(midiFile))
|
|
140
|
+
fs.unlinkSync(midiFile);
|
|
141
|
+
if (fs.existsSync(lyricFile))
|
|
142
|
+
fs.unlinkSync(lyricFile);
|
|
143
|
+
if (fs.existsSync(cursorFile))
|
|
144
|
+
fs.unlinkSync(cursorFile);
|
|
145
|
+
if (fs.existsSync(outputDir))
|
|
146
|
+
fs.rmdirSync(outputDir);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Ignore cleanup errors
|
|
150
|
+
}
|
|
151
|
+
throw new Error(`EMK to KAR conversion failed: ${error.message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Batch convert multiple EMK files to KAR
|
|
156
|
+
*/
|
|
157
|
+
function convertEmkToKarBatch(emkFiles, outputDirectory, options) {
|
|
158
|
+
const results = [];
|
|
159
|
+
// Create output directory
|
|
160
|
+
if (!fs.existsSync(outputDirectory)) {
|
|
161
|
+
fs.mkdirSync(outputDirectory, { recursive: true });
|
|
162
|
+
}
|
|
163
|
+
for (const emkFile of emkFiles) {
|
|
164
|
+
const baseFileName = path.basename(emkFile, '.emk');
|
|
165
|
+
const outputKar = path.join(outputDirectory, `${baseFileName}.kar`);
|
|
166
|
+
try {
|
|
167
|
+
const result = convertEmkToKar({
|
|
168
|
+
inputEmk: emkFile,
|
|
169
|
+
outputKar,
|
|
170
|
+
keepIntermediateFiles: options?.keepIntermediateFiles || false,
|
|
171
|
+
appendTitles: options?.appendTitles || false
|
|
172
|
+
});
|
|
173
|
+
results.push(result);
|
|
174
|
+
console.log(`✓ ${baseFileName}: ${result.metadata.title}`);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
console.error(`✗ ${baseFileName}: ${error.message}`);
|
|
178
|
+
results.push({
|
|
179
|
+
success: false,
|
|
180
|
+
outputKar,
|
|
181
|
+
metadata: {
|
|
182
|
+
title: 'Error',
|
|
183
|
+
artist: 'Error'
|
|
184
|
+
},
|
|
185
|
+
warnings: [error.message]
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Validate Thai text readability from lyric buffer
|
|
193
|
+
*/
|
|
194
|
+
function validateThaiLyricReadability(lyricBuffer) {
|
|
195
|
+
// Try TIS-620 decoding
|
|
196
|
+
const text = iconv.decode(lyricBuffer, 'tis-620');
|
|
197
|
+
const lines = text.split(/\r?\n/).filter(l => l.trim().length > 0);
|
|
198
|
+
// Check if text contains Thai characters
|
|
199
|
+
const thaiCharRegex = /[\u0E00-\u0E7F]/;
|
|
200
|
+
const hasThaiChars = thaiCharRegex.test(text);
|
|
201
|
+
// Check if text is readable (not too many control characters)
|
|
202
|
+
const controlCharCount = (text.match(/[\x00-\x08\x0B-\x0C\x0E-\x1F]/g) || []).length;
|
|
203
|
+
const isReadable = controlCharCount < (text.length * 0.1); // Less than 10% control chars
|
|
204
|
+
return {
|
|
205
|
+
readable: isReadable,
|
|
206
|
+
encoding: 'TIS-620',
|
|
207
|
+
preview: lines.slice(0, 5),
|
|
208
|
+
charCount: text.length
|
|
209
|
+
};
|
|
210
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* file-coder - A comprehensive library for encoding/decoding karaoke files
|
|
3
|
+
* Supports .emk (Extreme Karaoke), .kar (MIDI karaoke), and NCN format conversions
|
|
4
|
+
*/
|
|
5
|
+
export { convertNcnToKar, convertWithDefaults, parseLyricFile, buildKaraokeTrack, buildMetadataTracks, readFileTextTIS620, splitLinesKeepEndings, trimLineEndings, metaEvent, endOfTrack, ensureReadableFile, ensureOutputDoesNotExist, CursorReader, type ConversionOptions, type SongMetadata } from './ncntokar';
|
|
6
|
+
export { readKarFile, validateKarFile, extractLyricsFromKar, type KarFileInfo, type KarTrack } from './kar-reader';
|
|
7
|
+
export { convertEmkToKar, convertEmkToKarBatch, validateThaiLyricReadability, type EmkToKarOptions, type EmkToKarResult } from './emk-to-kar';
|
|
8
|
+
export { decodeEmk as decodeEmkServer, parseSongInfo as parseSongInfoServer, xorDecrypt as xorDecryptServer, looksLikeText as looksLikeTextServer, type DecodedEmkParts as DecodedEmkPartsServer } from './emk/server-decode';
|
|
9
|
+
export { decodeEmk as decodeEmkClient, parseSongInfo as parseSongInfoClient, xorDecrypt as xorDecryptClient, looksLikeText as looksLikeTextClient, type DecodedEmkParts as DecodedEmkPartsClient } from './emk/client-decoder';
|
|
10
|
+
export { convertNcnToKarBrowser, parseLyricBuffer, buildKaraokeTrackBrowser, buildMetadataTracksBrowser, fileToBuffer, downloadBuffer, type BrowserConversionOptions, type BrowserConversionResult, } from './ncntokar.browser';
|
|
11
|
+
export { convertEmkToKarBrowser, convertEmkFileToKar, convertEmkFilesBatch, validateThaiLyricReadabilityBrowser, type BrowserEmkToKarOptions, type BrowserEmkToKarResult, } from './emk-to-kar.browser';
|
|
12
|
+
export { readKarBuffer, validateKarBuffer, extractLyricsFromKarBuffer, readKarFile as readKarFileFromBrowser, type KarFileInfo as BrowserKarFileInfo, type KarTrack as BrowserKarTrack, } from './kar-reader.browser';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* file-coder - A comprehensive library for encoding/decoding karaoke files
|
|
4
|
+
* Supports .emk (Extreme Karaoke), .kar (MIDI karaoke), and NCN format conversions
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.readKarFileFromBrowser = exports.extractLyricsFromKarBuffer = exports.validateKarBuffer = exports.readKarBuffer = exports.validateThaiLyricReadabilityBrowser = exports.convertEmkFilesBatch = exports.convertEmkFileToKar = exports.convertEmkToKarBrowser = exports.downloadBuffer = exports.fileToBuffer = exports.buildMetadataTracksBrowser = exports.buildKaraokeTrackBrowser = exports.parseLyricBuffer = exports.convertNcnToKarBrowser = exports.looksLikeTextClient = exports.xorDecryptClient = exports.parseSongInfoClient = exports.decodeEmkClient = exports.looksLikeTextServer = exports.xorDecryptServer = exports.parseSongInfoServer = exports.decodeEmkServer = exports.validateThaiLyricReadability = exports.convertEmkToKarBatch = exports.convertEmkToKar = exports.extractLyricsFromKar = exports.validateKarFile = exports.readKarFile = exports.CursorReader = exports.ensureOutputDoesNotExist = exports.ensureReadableFile = exports.endOfTrack = exports.metaEvent = exports.trimLineEndings = exports.splitLinesKeepEndings = exports.readFileTextTIS620 = exports.buildMetadataTracks = exports.buildKaraokeTrack = exports.parseLyricFile = exports.convertWithDefaults = exports.convertNcnToKar = void 0;
|
|
8
|
+
// NCN to KAR converter exports
|
|
9
|
+
var ncntokar_1 = require("./ncntokar");
|
|
10
|
+
Object.defineProperty(exports, "convertNcnToKar", { enumerable: true, get: function () { return ncntokar_1.convertNcnToKar; } });
|
|
11
|
+
Object.defineProperty(exports, "convertWithDefaults", { enumerable: true, get: function () { return ncntokar_1.convertWithDefaults; } });
|
|
12
|
+
Object.defineProperty(exports, "parseLyricFile", { enumerable: true, get: function () { return ncntokar_1.parseLyricFile; } });
|
|
13
|
+
Object.defineProperty(exports, "buildKaraokeTrack", { enumerable: true, get: function () { return ncntokar_1.buildKaraokeTrack; } });
|
|
14
|
+
Object.defineProperty(exports, "buildMetadataTracks", { enumerable: true, get: function () { return ncntokar_1.buildMetadataTracks; } });
|
|
15
|
+
Object.defineProperty(exports, "readFileTextTIS620", { enumerable: true, get: function () { return ncntokar_1.readFileTextTIS620; } });
|
|
16
|
+
Object.defineProperty(exports, "splitLinesKeepEndings", { enumerable: true, get: function () { return ncntokar_1.splitLinesKeepEndings; } });
|
|
17
|
+
Object.defineProperty(exports, "trimLineEndings", { enumerable: true, get: function () { return ncntokar_1.trimLineEndings; } });
|
|
18
|
+
Object.defineProperty(exports, "metaEvent", { enumerable: true, get: function () { return ncntokar_1.metaEvent; } });
|
|
19
|
+
Object.defineProperty(exports, "endOfTrack", { enumerable: true, get: function () { return ncntokar_1.endOfTrack; } });
|
|
20
|
+
Object.defineProperty(exports, "ensureReadableFile", { enumerable: true, get: function () { return ncntokar_1.ensureReadableFile; } });
|
|
21
|
+
Object.defineProperty(exports, "ensureOutputDoesNotExist", { enumerable: true, get: function () { return ncntokar_1.ensureOutputDoesNotExist; } });
|
|
22
|
+
Object.defineProperty(exports, "CursorReader", { enumerable: true, get: function () { return ncntokar_1.CursorReader; } });
|
|
23
|
+
// KAR file reader exports
|
|
24
|
+
var kar_reader_1 = require("./kar-reader");
|
|
25
|
+
Object.defineProperty(exports, "readKarFile", { enumerable: true, get: function () { return kar_reader_1.readKarFile; } });
|
|
26
|
+
Object.defineProperty(exports, "validateKarFile", { enumerable: true, get: function () { return kar_reader_1.validateKarFile; } });
|
|
27
|
+
Object.defineProperty(exports, "extractLyricsFromKar", { enumerable: true, get: function () { return kar_reader_1.extractLyricsFromKar; } });
|
|
28
|
+
// EMK to KAR workflow exports (complete pipeline)
|
|
29
|
+
var emk_to_kar_1 = require("./emk-to-kar");
|
|
30
|
+
Object.defineProperty(exports, "convertEmkToKar", { enumerable: true, get: function () { return emk_to_kar_1.convertEmkToKar; } });
|
|
31
|
+
Object.defineProperty(exports, "convertEmkToKarBatch", { enumerable: true, get: function () { return emk_to_kar_1.convertEmkToKarBatch; } });
|
|
32
|
+
Object.defineProperty(exports, "validateThaiLyricReadability", { enumerable: true, get: function () { return emk_to_kar_1.validateThaiLyricReadability; } });
|
|
33
|
+
// EMK server-side decoder exports
|
|
34
|
+
var server_decode_1 = require("./emk/server-decode");
|
|
35
|
+
Object.defineProperty(exports, "decodeEmkServer", { enumerable: true, get: function () { return server_decode_1.decodeEmk; } });
|
|
36
|
+
Object.defineProperty(exports, "parseSongInfoServer", { enumerable: true, get: function () { return server_decode_1.parseSongInfo; } });
|
|
37
|
+
Object.defineProperty(exports, "xorDecryptServer", { enumerable: true, get: function () { return server_decode_1.xorDecrypt; } });
|
|
38
|
+
Object.defineProperty(exports, "looksLikeTextServer", { enumerable: true, get: function () { return server_decode_1.looksLikeText; } });
|
|
39
|
+
// EMK client-side decoder exports
|
|
40
|
+
var client_decoder_1 = require("./emk/client-decoder");
|
|
41
|
+
Object.defineProperty(exports, "decodeEmkClient", { enumerable: true, get: function () { return client_decoder_1.decodeEmk; } });
|
|
42
|
+
Object.defineProperty(exports, "parseSongInfoClient", { enumerable: true, get: function () { return client_decoder_1.parseSongInfo; } });
|
|
43
|
+
Object.defineProperty(exports, "xorDecryptClient", { enumerable: true, get: function () { return client_decoder_1.xorDecrypt; } });
|
|
44
|
+
Object.defineProperty(exports, "looksLikeTextClient", { enumerable: true, get: function () { return client_decoder_1.looksLikeText; } });
|
|
45
|
+
// Browser-compatible NCN to KAR conversion
|
|
46
|
+
var ncntokar_browser_1 = require("./ncntokar.browser");
|
|
47
|
+
Object.defineProperty(exports, "convertNcnToKarBrowser", { enumerable: true, get: function () { return ncntokar_browser_1.convertNcnToKarBrowser; } });
|
|
48
|
+
Object.defineProperty(exports, "parseLyricBuffer", { enumerable: true, get: function () { return ncntokar_browser_1.parseLyricBuffer; } });
|
|
49
|
+
Object.defineProperty(exports, "buildKaraokeTrackBrowser", { enumerable: true, get: function () { return ncntokar_browser_1.buildKaraokeTrackBrowser; } });
|
|
50
|
+
Object.defineProperty(exports, "buildMetadataTracksBrowser", { enumerable: true, get: function () { return ncntokar_browser_1.buildMetadataTracksBrowser; } });
|
|
51
|
+
Object.defineProperty(exports, "fileToBuffer", { enumerable: true, get: function () { return ncntokar_browser_1.fileToBuffer; } });
|
|
52
|
+
Object.defineProperty(exports, "downloadBuffer", { enumerable: true, get: function () { return ncntokar_browser_1.downloadBuffer; } });
|
|
53
|
+
// Browser-compatible EMK to KAR workflow
|
|
54
|
+
var emk_to_kar_browser_1 = require("./emk-to-kar.browser");
|
|
55
|
+
Object.defineProperty(exports, "convertEmkToKarBrowser", { enumerable: true, get: function () { return emk_to_kar_browser_1.convertEmkToKarBrowser; } });
|
|
56
|
+
Object.defineProperty(exports, "convertEmkFileToKar", { enumerable: true, get: function () { return emk_to_kar_browser_1.convertEmkFileToKar; } });
|
|
57
|
+
Object.defineProperty(exports, "convertEmkFilesBatch", { enumerable: true, get: function () { return emk_to_kar_browser_1.convertEmkFilesBatch; } });
|
|
58
|
+
Object.defineProperty(exports, "validateThaiLyricReadabilityBrowser", { enumerable: true, get: function () { return emk_to_kar_browser_1.validateThaiLyricReadabilityBrowser; } });
|
|
59
|
+
// Browser-compatible KAR reader
|
|
60
|
+
var kar_reader_browser_1 = require("./kar-reader.browser");
|
|
61
|
+
Object.defineProperty(exports, "readKarBuffer", { enumerable: true, get: function () { return kar_reader_browser_1.readKarBuffer; } });
|
|
62
|
+
Object.defineProperty(exports, "validateKarBuffer", { enumerable: true, get: function () { return kar_reader_browser_1.validateKarBuffer; } });
|
|
63
|
+
Object.defineProperty(exports, "extractLyricsFromKarBuffer", { enumerable: true, get: function () { return kar_reader_browser_1.extractLyricsFromKarBuffer; } });
|
|
64
|
+
Object.defineProperty(exports, "readKarFileFromBrowser", { enumerable: true, get: function () { return kar_reader_browser_1.readKarFile; } });
|