@karaplay/file-coder 1.2.0 → 1.3.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/README.md CHANGED
@@ -216,3 +216,39 @@ MIT
216
216
 
217
217
  Contributions are welcome! Please feel free to submit a Pull Request.
218
218
 
219
+
220
+ ## Changelog
221
+
222
+ ### v1.3.0 (Latest)
223
+ **🔧 Critical Fix: Beginning Lyrics Preservation**
224
+
225
+ - **Fixed**: Beginning lyrics were being cut off during EMK to KAR and NCN to KAR conversion
226
+ - **Improved**: Smart detection and skipping of instrumental intro markers (e.g., "....ดนตรี....")
227
+ - **Added**: 6 comprehensive tests to verify beginning lyrics preservation
228
+ - **Verified**: All 119 tests passing, ensuring complete lyric integrity from EMK decode to KAR output
229
+
230
+ **What was fixed:**
231
+ Previously, the conversion process was double-skipping the first 4 lines of lyrics, causing the actual beginning of songs to be missing from KAR files. This version correctly preserves all lyrics from the beginning while intelligently filtering out non-lyrical markers.
232
+
233
+ **Comparison test:**
234
+ ```typescript
235
+ // EMK decoded lyrics vs KAR output: 100% match
236
+ // First lyric: "ท่องเที่ยวมาแล้วแทบทั่วเมืองไทย" ✅
237
+ // Last lyric: "กับยุพิน ที่เมืองพระรถ..." ✅
238
+ ```
239
+
240
+ ### v1.2.0
241
+ - Added Thai encoding tests (6 new tests)
242
+ - Verified TIS-620 encoding preservation
243
+ - Updated README with encoding clarification
244
+
245
+ ### v1.1.1
246
+ - Documentation update for Thai encoding in KAR files
247
+ - No code changes
248
+
249
+ ### v1.0.0
250
+ - Initial release
251
+ - Full browser and server support
252
+ - EMK decoding and NCN to KAR conversion
253
+ - Thai language support (TIS-620)
254
+
@@ -0,0 +1,161 @@
1
+ # Release v1.2.0 - Thai Encoding Fix 🎉
2
+
3
+ ## 🐛 Bug Fix Release
4
+
5
+ **Package**: `@karaplay/file-coder`
6
+ **Version**: 1.2.0
7
+ **Published**: December 17, 2025
8
+
9
+ ## ✅ What Was Fixed
10
+
11
+ ### Thai Lyrics Now Fully Readable!
12
+
13
+ Fixed a critical bug where Thai lyrics in KAR files generated from EMK files were not readable when extracted programmatically.
14
+
15
+ #### The Problem
16
+ - EMK to KAR conversion was writing Thai text correctly (TIS-620 encoding)
17
+ - BUT when reading KAR files back, Thai characters appeared garbled
18
+ - The `extractLyricsFromKar()` function was not properly decoding Thai text
19
+
20
+ #### The Solution
21
+ - Fixed `readKarFile()` to properly decode TIS-620 bytes as Thai characters
22
+ - Fixed `extractLyricsFromKar()` to return properly decoded Thai text
23
+ - Applied same fix to browser versions (`readKarBuffer`, `extractLyricsFromKarBuffer`)
24
+
25
+ ## 🧪 New Tests
26
+
27
+ Added **6 comprehensive Thai encoding tests**:
28
+
29
+ 1. ✅ Preserve Thai characters in title and artist
30
+ 2. ✅ Preserve Thai characters when reading KAR file back
31
+ 3. ✅ Extract readable Thai lyrics from KAR file
32
+ 4. ✅ Handle Thai characters in karaoke track
33
+ 5. ✅ Preserve Thai encoding in KAR file bytes
34
+ 6. ✅ Match Thai content between EMK and KAR
35
+
36
+ **Total Tests**: 113 passing (107 original + 6 new)
37
+
38
+ ## 📊 Test Results
39
+
40
+ ```
41
+ Test Suites: 8 passed, 8 total
42
+ Tests: 113 passed, 113 total
43
+ ```
44
+
45
+ All tests pass including:
46
+ - Unit tests for all functions
47
+ - Integration tests with real EMK files
48
+ - Thai encoding verification tests
49
+ - Browser API tests
50
+
51
+ ## 🔧 Technical Details
52
+
53
+ ### Files Modified
54
+
55
+ 1. **`src/kar-reader.ts`**
56
+ - Fixed `readKarFile()` to decode TIS-620 text events
57
+ - Simplified `extractLyricsFromKar()` to use decoded text
58
+
59
+ 2. **`src/kar-reader.browser.ts`**
60
+ - Applied same fixes for browser environment
61
+
62
+ 3. **`src/emk-to-kar.ts`**
63
+ - Added comment clarifying TIS-620 encoding preservation
64
+
65
+ ### How It Works
66
+
67
+ ```typescript
68
+ // Before (garbled):
69
+ // midi-file decodes as Latin-1 → "àʹèËìàÁ×ͧ¾ÃÐö"
70
+
71
+ // After (correct):
72
+ // Re-encode as Latin-1 bytes → Decode as TIS-620 → "เสน่ห์เมืองพระรถ"
73
+ ```
74
+
75
+ The fix converts the garbled Latin-1 strings back to their original bytes, then properly decodes them as TIS-620 (Thai encoding).
76
+
77
+ ## 📦 Installation
78
+
79
+ ```bash
80
+ npm install @karaplay/file-coder@1.2.0
81
+ ```
82
+
83
+ Or update:
84
+
85
+ ```bash
86
+ npm update @karaplay/file-coder
87
+ ```
88
+
89
+ ## 🎯 Usage Example
90
+
91
+ ```typescript
92
+ import { convertEmkToKar, extractLyricsFromKar } from '@karaplay/file-coder';
93
+
94
+ // Convert EMK to KAR
95
+ const result = convertEmkToKar({
96
+ inputEmk: 'song.emk',
97
+ outputKar: 'song.kar'
98
+ });
99
+
100
+ console.log(result.metadata.title); // ✅ Thai text readable!
101
+ console.log(result.metadata.artist); // ✅ Thai text readable!
102
+
103
+ // Extract lyrics
104
+ const lyrics = extractLyricsFromKar('song.kar');
105
+ lyrics.forEach(lyric => {
106
+ console.log(lyric); // ✅ Thai lyrics readable!
107
+ });
108
+ ```
109
+
110
+ ## 🔍 Verification
111
+
112
+ Test with your own EMK files:
113
+
114
+ ```javascript
115
+ const { convertEmkToKar, extractLyricsFromKar } = require('@karaplay/file-coder');
116
+
117
+ // Convert
118
+ convertEmkToKar({
119
+ inputEmk: 'your-thai-song.emk',
120
+ outputKar: 'output.kar'
121
+ });
122
+
123
+ // Extract and verify
124
+ const lyrics = extractLyricsFromKar('output.kar');
125
+ const thaiRegex = /[\u0E00-\u0E7F]/;
126
+ const hasThaiChars = lyrics.some(l => thaiRegex.test(l));
127
+
128
+ console.log('Thai characters preserved:', hasThaiChars); // Should be true!
129
+ ```
130
+
131
+ ## 📝 Breaking Changes
132
+
133
+ **None** - This is a bug fix release. All existing code will continue to work, but Thai text will now be properly readable.
134
+
135
+ ## 🎊 Summary
136
+
137
+ | Metric | Value |
138
+ |--------|-------|
139
+ | Version | 1.2.0 |
140
+ | Tests | 113 passing |
141
+ | Bug Fixes | Thai encoding |
142
+ | New Tests | 6 |
143
+ | Breaking Changes | None |
144
+
145
+ ## 🔗 Links
146
+
147
+ - **npm**: https://www.npmjs.com/package/@karaplay/file-coder
148
+ - **Version**: 1.2.0
149
+
150
+ ## 🙏 Thank You
151
+
152
+ Thank you for reporting this issue! Thai lyrics are now fully supported and readable throughout the entire EMK→KAR conversion process.
153
+
154
+ ---
155
+
156
+ **Upgrade now to get proper Thai encoding support!** 🚀
157
+
158
+ ```bash
159
+ npm install @karaplay/file-coder@1.2.0
160
+ ```
161
+
@@ -170,11 +170,18 @@ function buildKaraokeTrackBrowser(metadata, cursorBuffer, ticksPerBeat) {
170
170
  const cursor = new BrowserCursorReader(cursorBuffer);
171
171
  const splitter = new grapheme_splitter_1.default();
172
172
  let previousAbsoluteTimestamp = 0;
173
+ // Note: metadata.fullLyric already has title/artist removed (starts from line 4 of original file)
174
+ // We should process ALL lines in fullLyric to avoid cutting off the beginning
173
175
  const lyricsWithEndings = splitLinesKeepEndings(metadata.fullLyric);
174
- for (let i = 4; i < lyricsWithEndings.length; i++) {
176
+ for (let i = 0; i < lyricsWithEndings.length; i++) {
175
177
  const trimmed = trimLineEndings(lyricsWithEndings[i]);
176
178
  if (!trimmed || trimmed.length === 0)
177
179
  continue;
180
+ // Skip instrumental intro markers (e.g., "....ดนตรี...." or similar patterns)
181
+ // These are common in EMK files but don't have timing data
182
+ if (trimmed.match(/^\.{2,}[^.]+\.{2,}$/)) {
183
+ continue;
184
+ }
178
185
  const lineForTiming = "/" + trimmed;
179
186
  const graphemes = splitter.splitGraphemes(lineForTiming);
180
187
  for (const g of graphemes) {
package/dist/ncntokar.js CHANGED
@@ -209,11 +209,18 @@ function buildKaraokeTrack(metadata, cursorBuffer, ticksPerBeat) {
209
209
  const splitter = new grapheme_splitter_1.default();
210
210
  let previousAbsoluteTimestamp = 0;
211
211
  // Process each lyric line
212
+ // Note: metadata.fullLyric already has title/artist removed (starts from line 4 of original file)
213
+ // We should process ALL lines in fullLyric to avoid cutting off the beginning
212
214
  const lyricsWithEndings = splitLinesKeepEndings(metadata.fullLyric);
213
- for (let i = 4; i < lyricsWithEndings.length; i++) {
215
+ for (let i = 0; i < lyricsWithEndings.length; i++) {
214
216
  const trimmed = trimLineEndings(lyricsWithEndings[i]);
215
217
  if (!trimmed || trimmed.length === 0)
216
218
  continue;
219
+ // Skip instrumental intro markers (e.g., "....ดนตรี...." or similar patterns)
220
+ // These are common in EMK files but don't have timing data
221
+ if (trimmed.match(/^\.{2,}[^.]+\.{2,}$/)) {
222
+ continue;
223
+ }
217
224
  const lineForTiming = "/" + trimmed;
218
225
  const graphemes = splitter.splitGraphemes(lineForTiming);
219
226
  for (const g of graphemes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karaplay/file-coder",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "A comprehensive library for encoding/decoding karaoke files (.emk, .kar, MIDI) with Next.js support. Convert EMK to KAR, read/write karaoke files, full browser and server support.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/temp/Z2510001.cur DELETED
Binary file
package/temp/Z2510001.lyr DELETED
@@ -1,37 +0,0 @@
1
- �ʹ������ͧ���ö(Ab)
2
- ǧ�����ǧ
3
- Ab
4
-
5
- ....�����....
6
- ��ͧ�����������᷺�������ͧ��
7
- ������ǧ�������
8
- ���ʹ㨹֡��ҡ��蹪�
9
- ������� ��ǧ�����ʹԤ�
10
- ���͹㨡�ѧ���� �֡��ҡ���繤�����
11
- ��������Ҿ������ʺ��
12
- �������������˹��
13
- ������觾������ҡ�ҡ˹�
14
- ���ʹԤ� ࢵᴹ��鹪ź���
15
- �����ͧ���ö����
16
- ����ͧ���������㹵ӹҹ
17
- ���ʹԤ� �֧���¡������ͧ���ö
18
- ����ǧ�����ʴ ���ǵӺż�餹����Ǣҹ
19
- ������§ ��͹�ҧ ��Ң���
20
- ⤡���� ��Шѹ��� ˹�Ҿ�иҵ�
21
- �Ѵ��ǧ�Ǻ��� �Դ���ҭ
22
- ��ǹ��ѧ�Թ ���˹ͧ��Ҵ
23
- ��觢�ҧ �د�� ��Һح�դ�����
24
- ˹ͧ���§͹��� ������ؾԹ
25
- ��ǹ��е�� ���������Ҫ�������
26
- �����ҡ���������ӡԹ �Ѻ�ؾԹ������ͧ
27
- ���ö
28
- ....�����...
29
- ���˹ͧ��Ҵ ��觢�ҧ
30
- �د�� ��Һح�դ�����
31
- ˹ͧ���§͹��������ؾԹ
32
- ��ǹ��е�� ���������Ҫ�������
33
- �����ҡ���������ӡԹ
34
- �Ѻ�ؾԹ ������ͧ���ö...
35
- ..���ŧ..
36
-
37
-
package/temp/Z2510001.mid DELETED
Binary file
Binary file
@@ -1,79 +0,0 @@
1
- const { convertEmkToKar } = require('./dist/emk-to-kar');
2
- const { readKarFile, extractLyricsFromKar } = require('./dist/kar-reader');
3
- const fs = require('fs');
4
- const iconv = require('iconv-lite');
5
- const path = require('path');
6
-
7
- console.log('=== Testing EMK to KAR conversion with Thai lyrics ===\n');
8
-
9
- // Clean up old output
10
- const outputPath = path.join(__dirname, 'test-emk-output.kar');
11
- if (fs.existsSync(outputPath)) {
12
- fs.unlinkSync(outputPath);
13
- }
14
-
15
- // Convert EMK to KAR
16
- const result = convertEmkToKar({
17
- inputEmk: path.join(__dirname, 'songs/emk/Z2510001.emk'),
18
- outputKar: outputPath,
19
- keepIntermediateFiles: true,
20
- intermediateDir: __dirname
21
- });
22
-
23
- console.log('Conversion result:', result.success);
24
- console.log('Title:', result.metadata.title);
25
- console.log('Artist:', result.metadata.artist);
26
-
27
- // Read the intermediate lyric file
28
- const lyricPath = path.join(__dirname, 'Z2510001.lyr');
29
- if (fs.existsSync(lyricPath)) {
30
- const lyricBuffer = fs.readFileSync(lyricPath);
31
- const lyricText = iconv.decode(lyricBuffer, 'tis-620');
32
- console.log('\nIntermediate lyric file (first 200 chars):');
33
- console.log(lyricText.substring(0, 200));
34
-
35
- const thaiRegex = /[\u0E00-\u0E7F]/;
36
- console.log('Has Thai in intermediate lyric:', thaiRegex.test(lyricText));
37
- }
38
-
39
- // Read back the KAR file
40
- console.log('\n=== Reading back the KAR file ===');
41
- const karInfo = readKarFile(outputPath);
42
- console.log('KAR Title:', karInfo.metadata.title);
43
- console.log('KAR Artist:', karInfo.metadata.artist);
44
-
45
- // Extract lyrics
46
- const lyrics = extractLyricsFromKar(outputPath);
47
- console.log('\nExtracted lyrics (first 10):');
48
- lyrics.slice(0, 10).forEach((lyric, i) => {
49
- console.log(`${i + 1}. "${lyric}"`);
50
- });
51
-
52
- // Check for Thai characters
53
- const thaiRegex = /[\u0E00-\u0E7F]/;
54
- const hasThaiInLyrics = lyrics.some(l => thaiRegex.test(l));
55
- console.log('\n❌ ERROR: Has Thai characters in extracted lyrics:', hasThaiInLyrics);
56
- console.log('Expected: true');
57
- console.log('Actual:', hasThaiInLyrics);
58
-
59
- // Check the actual bytes in the KAR file
60
- console.log('\n=== Checking KAR file bytes ===');
61
- const karBuffer = fs.readFileSync(outputPath);
62
- let foundThai = false;
63
- for (let i = 0; i < karBuffer.length - 20; i++) {
64
- if (karBuffer[i] === 0x40 && karBuffer[i+1] === 0x54) { // @T
65
- const slice = karBuffer.slice(i, Math.min(i + 30, karBuffer.length));
66
- const text = iconv.decode(slice, 'tis-620');
67
- if (thaiRegex.test(text)) {
68
- console.log('✓ Found Thai in KAR bytes:', text.substring(0, 25));
69
- foundThai = true;
70
- break;
71
- }
72
- }
73
- }
74
-
75
- if (!foundThai) {
76
- console.log('❌ ERROR: No Thai characters found in KAR file bytes!');
77
- }
78
-
79
- process.exit(hasThaiInLyrics ? 0 : 1);
@@ -1,22 +0,0 @@
1
- const { extractLyricsFromKar } = require('./dist/kar-reader');
2
-
3
- const lyrics = extractLyricsFromKar('test-emk-output.kar');
4
-
5
- console.log('Total lyrics:', lyrics.length);
6
- console.log('\nFirst 20 lyrics:');
7
- lyrics.slice(0, 20).forEach((lyric, i) => {
8
- const bytes = Buffer.from(lyric, 'utf8');
9
- console.log(`${i + 1}. "${lyric}" [${bytes.length} bytes] [${Array.from(bytes.slice(0, 10)).map(b => '0x'+b.toString(16)).join(', ')}]`);
10
- });
11
-
12
- // Check for Thai
13
- const thaiRegex = /[\u0E00-\u0E7F]/;
14
- const hasThaiInLyrics = lyrics.some(l => thaiRegex.test(l));
15
- console.log('\nHas Thai characters:', hasThaiInLyrics);
16
-
17
- // Show some that have Thai
18
- const thaiLyrics = lyrics.filter(l => thaiRegex.test(l));
19
- console.log('\nLyrics with Thai (first 10):');
20
- thaiLyrics.slice(0, 10).forEach((lyric, i) => {
21
- console.log(`${i + 1}. "${lyric}"`);
22
- });
package/test-readkar.js DELETED
@@ -1,24 +0,0 @@
1
- const { readKarFile } = require('./dist/kar-reader');
2
-
3
- const info = readKarFile('test-emk-output.kar');
4
-
5
- console.log('=== KAR File Info ===');
6
- console.log('Title:', info.metadata.title);
7
- console.log('Artist:', info.metadata.artist);
8
- console.log('Has Karaoke Track:', info.metadata.hasKaraokeTrack);
9
- console.log('\nTracks:');
10
-
11
- info.tracks.forEach((track, i) => {
12
- console.log(`\nTrack ${i}: ${track.name || 'unnamed'}`);
13
- console.log(` Events: ${track.events}`);
14
- console.log(` Has Text: ${track.hasText}`);
15
- console.log(` Text Events: ${track.textEvents.length}`);
16
-
17
- if (track.name === 'Words' && track.textEvents.length > 0) {
18
- console.log(' First 15 text events:');
19
- track.textEvents.slice(0, 15).forEach((text, j) => {
20
- const bytes = Buffer.from(text, 'utf8');
21
- console.log(` ${j}. "${text}" [${bytes.length} bytes]`);
22
- });
23
- }
24
- });