@makefully/dutifully 1.10.1 → 1.11.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/CHANGELOG.md +9 -5
- package/LICENSE +21 -21
- package/README.md +216 -214
- package/cheer.js +36 -36
- package/package.json +63 -63
- package/say.js +15 -15
- package/src/classes/Cheer.js +254 -254
- package/src/classes/ElevenLabs.js +16 -16
- package/src/classes/FFMPEG.js +12 -12
- package/src/classes/LipSync.js +132 -132
- package/src/classes/Polly.js +9 -9
- package/src/classes/Rasterize.js +12 -12
- package/src/classes/TextToSpeech.js +204 -204
- package/src/classes/Transcript.js +72 -68
- package/src/classes/Transcription.js +162 -162
- package/src/formats/File.js +26 -26
- package/src/formats/JSONFile.js +39 -39
- package/src/formats/LRC.js +37 -37
- package/src/formats/MP3.js +59 -59
- package/src/formats/SAMI.js +58 -58
- package/src/formats/SRT.js +39 -39
- package/src/formats/VTT.js +37 -37
- package/src/helpers/getCaptions.js +43 -43
- package/src/helpers/getFileData.js +32 -32
- package/src/helpers/getJSON.js +32 -32
- package/src/index.js +58 -58
- package/src/say.js +141 -141
|
@@ -1,163 +1,163 @@
|
|
|
1
|
-
const
|
|
2
|
-
Cheer = require('./Cheer'),
|
|
3
|
-
Transcript = require('./Transcript'),
|
|
4
|
-
fs = require('fs').promises,
|
|
5
|
-
getCaptions = require('../helpers/getCaptions'),
|
|
6
|
-
getFileData = require('../helpers/getFileData'),
|
|
7
|
-
getJSON = require('../helpers/getJSON'),
|
|
8
|
-
SEPARATE_CHARACTER = '|',
|
|
9
|
-
JOIN_CHARACTER = '^',
|
|
10
|
-
filter = (fileType, file) => (file.indexOf('.') !== 0) && (file.slice(-(fileType.length + 1)) === `.${fileType}`),
|
|
11
|
-
mapReduction = (map, list) => list.reduce((obj, file) => {
|
|
12
|
-
const
|
|
13
|
-
id = file.substring(0, file.length - 4);
|
|
14
|
-
|
|
15
|
-
obj[id] = map[id];
|
|
16
|
-
return obj;
|
|
17
|
-
}, {}),
|
|
18
|
-
Transcription = class Transcription extends Cheer {
|
|
19
|
-
async prepare (data) {
|
|
20
|
-
this.differenceOnly = false;
|
|
21
|
-
this.updateList = null;
|
|
22
|
-
await this.replaceConfigPathWithJSON('script', 'files');
|
|
23
|
-
|
|
24
|
-
// create standardized transcripts and an accompanying hash for each.
|
|
25
|
-
const
|
|
26
|
-
{files, voice} = this.config,
|
|
27
|
-
standardizedFiles = {},
|
|
28
|
-
transcripts = {};
|
|
29
|
-
|
|
30
|
-
Object.keys(files).forEach((key) => {
|
|
31
|
-
const
|
|
32
|
-
transcript = new Transcript(files[key], {voice});
|
|
33
|
-
|
|
34
|
-
standardizedFiles[key] = transcript.toJSON();
|
|
35
|
-
transcripts[key] = transcript;
|
|
36
|
-
});
|
|
37
|
-
this.config.files = standardizedFiles;
|
|
38
|
-
this.transcripts = transcripts;
|
|
39
|
-
|
|
40
|
-
return super.prepare(data);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async checkDifference () {
|
|
44
|
-
const
|
|
45
|
-
{config} = this,
|
|
46
|
-
{files, format = 'json', output, src} = config,
|
|
47
|
-
fileType = format.toLowerCase(),
|
|
48
|
-
{captions: alreadyCaptioned, file: captionsFile, files: fileMap} = await getCaptions(output, fileType),
|
|
49
|
-
check = (id, caption) => {
|
|
50
|
-
const
|
|
51
|
-
file = fileMap?.[id],
|
|
52
|
-
original = alreadyCaptioned[id],
|
|
53
|
-
hash = file?.getHash();
|
|
54
|
-
|
|
55
|
-
delete alreadyCaptioned[id];
|
|
56
|
-
|
|
57
|
-
if (hash) {
|
|
58
|
-
const
|
|
59
|
-
same = hash === this.transcripts[id].getHash();
|
|
60
|
-
|
|
61
|
-
if (!same) {
|
|
62
|
-
console.log(`Updating "${id}".`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return same;
|
|
66
|
-
} else {
|
|
67
|
-
const
|
|
68
|
-
cap = (Array.isArray(caption) ? caption : [caption]).map((cap) => cap?.caption ?? cap ?? null).filter((cap) => cap !== null).join(' ').replaceAll(JOIN_CHARACTER, ' ').replaceAll(SEPARATE_CHARACTER, ' ');
|
|
69
|
-
|
|
70
|
-
if (cap && cap !== original) {
|
|
71
|
-
console.log(`Updating "${id}".`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// if a caption is not supplied, we'll assume the existence of an original caption means we don't need to redo.
|
|
75
|
-
return cap === '' || cap === original;
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
mergeCaptions = async (newCaptions = false) => { // Must run _after_ all checks and will remove what's left.
|
|
79
|
-
const
|
|
80
|
-
olds = Object.keys(alreadyCaptioned);
|
|
81
|
-
|
|
82
|
-
for (let i = 0; i < olds.length; i++) {
|
|
83
|
-
const
|
|
84
|
-
caption = olds[i];
|
|
85
|
-
|
|
86
|
-
if (captionsFile) {
|
|
87
|
-
captionsFile.removeKey(caption);
|
|
88
|
-
} else {
|
|
89
|
-
await fs.rm(`${output}${caption}.${fileType}`);
|
|
90
|
-
}
|
|
91
|
-
console.log(`Removed "${caption}"`);
|
|
92
|
-
}
|
|
93
|
-
if (captionsFile) {
|
|
94
|
-
if (newCaptions) {
|
|
95
|
-
captionsFile.mergeData(await getJSON(`${output}captions.json`));
|
|
96
|
-
}
|
|
97
|
-
await captionsFile.save();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return olds.length;
|
|
101
|
-
},
|
|
102
|
-
list = (await fs.readdir(src)).filter(filter.bind(null, 'mp3')).filter((file) => {
|
|
103
|
-
const
|
|
104
|
-
id = file.substring(0, file.length - 4),
|
|
105
|
-
caption = files?.[id];
|
|
106
|
-
|
|
107
|
-
return !check(id, caption);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
this.differenceOnly = true;
|
|
111
|
-
|
|
112
|
-
if (list.length === 0) {
|
|
113
|
-
// We'll still run the merge in case any have been removed.
|
|
114
|
-
if (await mergeCaptions()) {
|
|
115
|
-
throw Error('Old captions removed, but no new captions required generation.');
|
|
116
|
-
} else {
|
|
117
|
-
throw Error('Captions already up to date.');
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
this.updateList = list;
|
|
122
|
-
config.files = mapReduction(files, list);
|
|
123
|
-
this.mergeCaptions = mergeCaptions;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
beforeSend (archive) {
|
|
127
|
-
const
|
|
128
|
-
{src} = this.config;
|
|
129
|
-
|
|
130
|
-
if (this.differenceOnly) {
|
|
131
|
-
this.updateList.forEach((file) => archive.file(`${src}${file}`, {
|
|
132
|
-
name: file
|
|
133
|
-
}));
|
|
134
|
-
} else {
|
|
135
|
-
archive.directory(src, false);
|
|
136
|
-
}
|
|
137
|
-
super.beforeSend(archive);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async afterExport (...args) {
|
|
141
|
-
const
|
|
142
|
-
{config, transcripts, updateList} = this,
|
|
143
|
-
{format = 'json', output} = config;
|
|
144
|
-
|
|
145
|
-
if (this.mergeCaptions) {
|
|
146
|
-
await this.mergeCaptions(true);
|
|
147
|
-
}
|
|
148
|
-
if (format !== 'json') {
|
|
149
|
-
for (let i = 0; i < updateList.length; i++) {
|
|
150
|
-
const
|
|
151
|
-
filename = updateList[i],
|
|
152
|
-
id = filename.substring(0, filename.length - 4),
|
|
153
|
-
file = await getFileData(`${output}${id}.${format}`, format);
|
|
154
|
-
|
|
155
|
-
file.addHash(transcripts[id].getHash());
|
|
156
|
-
await file.save();
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
super.afterExport(...args)
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
|
|
1
|
+
const
|
|
2
|
+
Cheer = require('./Cheer'),
|
|
3
|
+
Transcript = require('./Transcript'),
|
|
4
|
+
fs = require('fs').promises,
|
|
5
|
+
getCaptions = require('../helpers/getCaptions'),
|
|
6
|
+
getFileData = require('../helpers/getFileData'),
|
|
7
|
+
getJSON = require('../helpers/getJSON'),
|
|
8
|
+
SEPARATE_CHARACTER = '|',
|
|
9
|
+
JOIN_CHARACTER = '^',
|
|
10
|
+
filter = (fileType, file) => (file.indexOf('.') !== 0) && (file.slice(-(fileType.length + 1)) === `.${fileType}`),
|
|
11
|
+
mapReduction = (map, list) => list.reduce((obj, file) => {
|
|
12
|
+
const
|
|
13
|
+
id = file.substring(0, file.length - 4);
|
|
14
|
+
|
|
15
|
+
obj[id] = map[id];
|
|
16
|
+
return obj;
|
|
17
|
+
}, {}),
|
|
18
|
+
Transcription = class Transcription extends Cheer {
|
|
19
|
+
async prepare (data) {
|
|
20
|
+
this.differenceOnly = false;
|
|
21
|
+
this.updateList = null;
|
|
22
|
+
await this.replaceConfigPathWithJSON('script', 'files');
|
|
23
|
+
|
|
24
|
+
// create standardized transcripts and an accompanying hash for each.
|
|
25
|
+
const
|
|
26
|
+
{files, voice} = this.config,
|
|
27
|
+
standardizedFiles = {},
|
|
28
|
+
transcripts = {};
|
|
29
|
+
|
|
30
|
+
Object.keys(files).forEach((key) => {
|
|
31
|
+
const
|
|
32
|
+
transcript = new Transcript(files[key], {voice});
|
|
33
|
+
|
|
34
|
+
standardizedFiles[key] = transcript.toJSON();
|
|
35
|
+
transcripts[key] = transcript;
|
|
36
|
+
});
|
|
37
|
+
this.config.files = standardizedFiles;
|
|
38
|
+
this.transcripts = transcripts;
|
|
39
|
+
|
|
40
|
+
return super.prepare(data);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async checkDifference () {
|
|
44
|
+
const
|
|
45
|
+
{config} = this,
|
|
46
|
+
{files, format = 'json', output, src} = config,
|
|
47
|
+
fileType = format.toLowerCase(),
|
|
48
|
+
{captions: alreadyCaptioned, file: captionsFile, files: fileMap} = await getCaptions(output, fileType),
|
|
49
|
+
check = (id, caption) => {
|
|
50
|
+
const
|
|
51
|
+
file = fileMap?.[id],
|
|
52
|
+
original = alreadyCaptioned[id],
|
|
53
|
+
hash = file?.getHash();
|
|
54
|
+
|
|
55
|
+
delete alreadyCaptioned[id];
|
|
56
|
+
|
|
57
|
+
if (hash) {
|
|
58
|
+
const
|
|
59
|
+
same = hash === this.transcripts[id].getHash();
|
|
60
|
+
|
|
61
|
+
if (!same) {
|
|
62
|
+
console.log(`Updating "${id}".`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return same;
|
|
66
|
+
} else {
|
|
67
|
+
const
|
|
68
|
+
cap = (Array.isArray(caption) ? caption : [caption]).map((cap) => cap?.caption ?? cap ?? null).filter((cap) => cap !== null).join(' ').replaceAll(JOIN_CHARACTER, ' ').replaceAll(SEPARATE_CHARACTER, ' ');
|
|
69
|
+
|
|
70
|
+
if (cap && cap !== original) {
|
|
71
|
+
console.log(`Updating "${id}".`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// if a caption is not supplied, we'll assume the existence of an original caption means we don't need to redo.
|
|
75
|
+
return cap === '' || cap === original;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
mergeCaptions = async (newCaptions = false) => { // Must run _after_ all checks and will remove what's left.
|
|
79
|
+
const
|
|
80
|
+
olds = Object.keys(alreadyCaptioned);
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < olds.length; i++) {
|
|
83
|
+
const
|
|
84
|
+
caption = olds[i];
|
|
85
|
+
|
|
86
|
+
if (captionsFile) {
|
|
87
|
+
captionsFile.removeKey(caption);
|
|
88
|
+
} else {
|
|
89
|
+
await fs.rm(`${output}${caption}.${fileType}`);
|
|
90
|
+
}
|
|
91
|
+
console.log(`Removed "${caption}"`);
|
|
92
|
+
}
|
|
93
|
+
if (captionsFile) {
|
|
94
|
+
if (newCaptions) {
|
|
95
|
+
captionsFile.mergeData(await getJSON(`${output}captions.json`));
|
|
96
|
+
}
|
|
97
|
+
await captionsFile.save();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return olds.length;
|
|
101
|
+
},
|
|
102
|
+
list = (await fs.readdir(src)).filter(filter.bind(null, 'mp3')).filter((file) => {
|
|
103
|
+
const
|
|
104
|
+
id = file.substring(0, file.length - 4),
|
|
105
|
+
caption = files?.[id];
|
|
106
|
+
|
|
107
|
+
return !check(id, caption);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this.differenceOnly = true;
|
|
111
|
+
|
|
112
|
+
if (list.length === 0) {
|
|
113
|
+
// We'll still run the merge in case any have been removed.
|
|
114
|
+
if (await mergeCaptions()) {
|
|
115
|
+
throw Error('Old captions removed, but no new captions required generation.');
|
|
116
|
+
} else {
|
|
117
|
+
throw Error('Captions already up to date.');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.updateList = list;
|
|
122
|
+
config.files = mapReduction(files, list);
|
|
123
|
+
this.mergeCaptions = mergeCaptions;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
beforeSend (archive) {
|
|
127
|
+
const
|
|
128
|
+
{src} = this.config;
|
|
129
|
+
|
|
130
|
+
if (this.differenceOnly) {
|
|
131
|
+
this.updateList.forEach((file) => archive.file(`${src}${file}`, {
|
|
132
|
+
name: file
|
|
133
|
+
}));
|
|
134
|
+
} else {
|
|
135
|
+
archive.directory(src, false);
|
|
136
|
+
}
|
|
137
|
+
super.beforeSend(archive);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async afterExport (...args) {
|
|
141
|
+
const
|
|
142
|
+
{config, transcripts, updateList} = this,
|
|
143
|
+
{format = 'json', output} = config;
|
|
144
|
+
|
|
145
|
+
if (this.mergeCaptions) {
|
|
146
|
+
await this.mergeCaptions(true);
|
|
147
|
+
}
|
|
148
|
+
if (format !== 'json') {
|
|
149
|
+
for (let i = 0; i < updateList.length; i++) {
|
|
150
|
+
const
|
|
151
|
+
filename = updateList[i],
|
|
152
|
+
id = filename.substring(0, filename.length - 4),
|
|
153
|
+
file = await getFileData(`${output}${id}.${format}`, format);
|
|
154
|
+
|
|
155
|
+
file.addHash(transcripts[id].getHash());
|
|
156
|
+
await file.save();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
super.afterExport(...args)
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
163
|
module.exports = Transcription;
|
package/src/formats/File.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
const
|
|
2
|
-
fs = require('fs').promises,
|
|
3
|
-
File = class {
|
|
4
|
-
constructor ({fileType}) {
|
|
5
|
-
this.data = null;
|
|
6
|
-
this.fileType = fileType;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
async load (path) {
|
|
10
|
-
this.path = path;
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
getHash () {
|
|
15
|
-
return null; // does not support hashes.
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
save () { // return a promise
|
|
19
|
-
return fs.writeFile(`${this.path}`, this.data ?? '');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
toString () {
|
|
23
|
-
return '';
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
1
|
+
const
|
|
2
|
+
fs = require('fs').promises,
|
|
3
|
+
File = class {
|
|
4
|
+
constructor ({fileType}) {
|
|
5
|
+
this.data = null;
|
|
6
|
+
this.fileType = fileType;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async load (path) {
|
|
10
|
+
this.path = path;
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getHash () {
|
|
15
|
+
return null; // does not support hashes.
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
save () { // return a promise
|
|
19
|
+
return fs.writeFile(`${this.path}`, this.data ?? '');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toString () {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
27
|
module.exports = File;
|
package/src/formats/JSONFile.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
const
|
|
2
|
-
crypto = require('crypto'),
|
|
3
|
-
fs = require('fs').promises,
|
|
4
|
-
File = require("./File"),
|
|
5
|
-
getJSON = require('../helpers/getJSON'),
|
|
6
|
-
sortKeys = (obj) => Object.keys(obj).sort().reduce((s, key) => {
|
|
7
|
-
s[key] = obj[key];
|
|
8
|
-
return s;
|
|
9
|
-
}, {});
|
|
10
|
-
|
|
11
|
-
module.exports = class JSONFile extends File {
|
|
12
|
-
async load (path) {
|
|
13
|
-
await super.load(path);
|
|
14
|
-
this.data = await getJSON(path);
|
|
15
|
-
return this.data;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
getHash () {
|
|
19
|
-
return this.data ? crypto.createHash('sha256').update(JSON.stringify(this.data)).digest('hex') : null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
toString () {
|
|
23
|
-
return Object.keys(this.data).map((key) => this.data[key].map(({content}) => content).join(' ')).join('\n');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
removeKey (key) {
|
|
27
|
-
delete this.data[key];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
mergeData (data) {
|
|
31
|
-
this.data = sortKeys({
|
|
32
|
-
...this.data,
|
|
33
|
-
...data
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
save () { // return a promise
|
|
38
|
-
return fs.writeFile(`${this.path}`, JSON.stringify(this.data, null, 4));
|
|
39
|
-
}
|
|
1
|
+
const
|
|
2
|
+
crypto = require('crypto'),
|
|
3
|
+
fs = require('fs').promises,
|
|
4
|
+
File = require("./File"),
|
|
5
|
+
getJSON = require('../helpers/getJSON'),
|
|
6
|
+
sortKeys = (obj) => Object.keys(obj).sort().reduce((s, key) => {
|
|
7
|
+
s[key] = obj[key];
|
|
8
|
+
return s;
|
|
9
|
+
}, {});
|
|
10
|
+
|
|
11
|
+
module.exports = class JSONFile extends File {
|
|
12
|
+
async load (path) {
|
|
13
|
+
await super.load(path);
|
|
14
|
+
this.data = await getJSON(path);
|
|
15
|
+
return this.data;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getHash () {
|
|
19
|
+
return this.data ? crypto.createHash('sha256').update(JSON.stringify(this.data)).digest('hex') : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toString () {
|
|
23
|
+
return Object.keys(this.data).map((key) => this.data[key].map(({content}) => content).join(' ')).join('\n');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
removeKey (key) {
|
|
27
|
+
delete this.data[key];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
mergeData (data) {
|
|
31
|
+
this.data = sortKeys({
|
|
32
|
+
...this.data,
|
|
33
|
+
...data
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
save () { // return a promise
|
|
38
|
+
return fs.writeFile(`${this.path}`, JSON.stringify(this.data, null, 4));
|
|
39
|
+
}
|
|
40
40
|
}
|
package/src/formats/LRC.js
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
const
|
|
2
|
-
fs = require('fs').promises,
|
|
3
|
-
os = require('os'),
|
|
4
|
-
File = require("./File"),
|
|
5
|
-
getSeparater = (str) => str.indexOf(os.EOL) >= 0 ? os.EOL : '\n';
|
|
6
|
-
|
|
7
|
-
module.exports = class LRC extends File {
|
|
8
|
-
async load (path) {
|
|
9
|
-
await super.load(path);
|
|
10
|
-
this.data = await fs.readFile(path, 'utf8');
|
|
11
|
-
return this.data;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
addHash (hash) {
|
|
15
|
-
this.data = this.data.replace(/^\[hash:[a-f0-9]+\]\n?/im, '');
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
sep = getSeparater(this.data),
|
|
19
|
-
insertAfter = this.data.match(/^\[generator:[^\]]*\]/im);
|
|
20
|
-
|
|
21
|
-
if (insertAfter) {
|
|
22
|
-
this.data = this.data.replace(insertAfter[0], `${insertAfter[0]}${sep}[hash:${hash}]`);
|
|
23
|
-
} else {
|
|
24
|
-
this.data = `[hash:${hash}]${sep}${this.data}`;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
getHash () {
|
|
29
|
-
const
|
|
30
|
-
match = this.data.match(/^\[hash:([a-f0-9]+)\]/im);
|
|
31
|
-
|
|
32
|
-
return match ? match[1] : null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
toString () {
|
|
36
|
-
return this.data.split(getSeparater(this.data)).filter((line) => line.length > 0 && line.indexOf('[') === 0 && line.indexOf(']') === 9).map((text) => text.substring(10)).join(' ');
|
|
37
|
-
}
|
|
1
|
+
const
|
|
2
|
+
fs = require('fs').promises,
|
|
3
|
+
os = require('os'),
|
|
4
|
+
File = require("./File"),
|
|
5
|
+
getSeparater = (str) => str.indexOf(os.EOL) >= 0 ? os.EOL : '\n';
|
|
6
|
+
|
|
7
|
+
module.exports = class LRC extends File {
|
|
8
|
+
async load (path) {
|
|
9
|
+
await super.load(path);
|
|
10
|
+
this.data = await fs.readFile(path, 'utf8');
|
|
11
|
+
return this.data;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
addHash (hash) {
|
|
15
|
+
this.data = this.data.replace(/^\[hash:[a-f0-9]+\]\n?/im, '');
|
|
16
|
+
|
|
17
|
+
const
|
|
18
|
+
sep = getSeparater(this.data),
|
|
19
|
+
insertAfter = this.data.match(/^\[generator:[^\]]*\]/im);
|
|
20
|
+
|
|
21
|
+
if (insertAfter) {
|
|
22
|
+
this.data = this.data.replace(insertAfter[0], `${insertAfter[0]}${sep}[hash:${hash}]`);
|
|
23
|
+
} else {
|
|
24
|
+
this.data = `[hash:${hash}]${sep}${this.data}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getHash () {
|
|
29
|
+
const
|
|
30
|
+
match = this.data.match(/^\[hash:([a-f0-9]+)\]/im);
|
|
31
|
+
|
|
32
|
+
return match ? match[1] : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
toString () {
|
|
36
|
+
return this.data.split(getSeparater(this.data)).filter((line) => line.length > 0 && line.indexOf('[') === 0 && line.indexOf(']') === 9).map((text) => text.substring(10)).join(' ');
|
|
37
|
+
}
|
|
38
38
|
}
|
package/src/formats/MP3.js
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
const
|
|
2
|
-
id3 = require('node-id3').Promise,
|
|
3
|
-
File = require("./File");
|
|
4
|
-
|
|
5
|
-
module.exports = class MP3 extends File {
|
|
6
|
-
async load (path) {
|
|
7
|
-
await super.load(path);
|
|
8
|
-
this.data = await id3.read(path);
|
|
9
|
-
return this.data;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
addHash (hash, {hashKey = 'CaptionHash'} = {}) {
|
|
13
|
-
const
|
|
14
|
-
existing = (this.data.userDefinedText ?? []).filter(({description}) => description !== hashKey);
|
|
15
|
-
|
|
16
|
-
this.data.userDefinedText = [...existing, {description: hashKey, value: hash}];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
getHash ({hashKey = 'CaptionHash'} = {}) {
|
|
20
|
-
const
|
|
21
|
-
{userDefinedText} = this.data;
|
|
22
|
-
|
|
23
|
-
if (userDefinedText) {
|
|
24
|
-
for (let i = 0; i < userDefinedText.length; i++) {
|
|
25
|
-
if (userDefinedText[i].description === hashKey) {
|
|
26
|
-
return userDefinedText[i].value;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
save () { // return a promise
|
|
35
|
-
return id3.write(this.data, this.path);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
toString () {
|
|
39
|
-
const
|
|
40
|
-
{synchronisedLyrics, unsynchronisedLyrics} = this.data;
|
|
41
|
-
|
|
42
|
-
if (unsynchronisedLyrics?.text) {
|
|
43
|
-
return unsynchronisedLyrics.text;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (synchronisedLyrics) {
|
|
47
|
-
const
|
|
48
|
-
index = synchronisedLyrics.map(({shortText}) => shortText).indexOf('captions');
|
|
49
|
-
|
|
50
|
-
if (index >= 0) {
|
|
51
|
-
const
|
|
52
|
-
{synchronisedText} = synchronisedLyrics[index];
|
|
53
|
-
|
|
54
|
-
return synchronisedText.map(({text}) => text).join(' ');
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return '';
|
|
59
|
-
}
|
|
1
|
+
const
|
|
2
|
+
id3 = require('node-id3').Promise,
|
|
3
|
+
File = require("./File");
|
|
4
|
+
|
|
5
|
+
module.exports = class MP3 extends File {
|
|
6
|
+
async load (path) {
|
|
7
|
+
await super.load(path);
|
|
8
|
+
this.data = await id3.read(path);
|
|
9
|
+
return this.data;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
addHash (hash, {hashKey = 'CaptionHash'} = {}) {
|
|
13
|
+
const
|
|
14
|
+
existing = (this.data.userDefinedText ?? []).filter(({description}) => description !== hashKey);
|
|
15
|
+
|
|
16
|
+
this.data.userDefinedText = [...existing, {description: hashKey, value: hash}];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getHash ({hashKey = 'CaptionHash'} = {}) {
|
|
20
|
+
const
|
|
21
|
+
{userDefinedText} = this.data;
|
|
22
|
+
|
|
23
|
+
if (userDefinedText) {
|
|
24
|
+
for (let i = 0; i < userDefinedText.length; i++) {
|
|
25
|
+
if (userDefinedText[i].description === hashKey) {
|
|
26
|
+
return userDefinedText[i].value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
save () { // return a promise
|
|
35
|
+
return id3.write(this.data, this.path);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
toString () {
|
|
39
|
+
const
|
|
40
|
+
{synchronisedLyrics, unsynchronisedLyrics} = this.data;
|
|
41
|
+
|
|
42
|
+
if (unsynchronisedLyrics?.text) {
|
|
43
|
+
return unsynchronisedLyrics.text;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (synchronisedLyrics) {
|
|
47
|
+
const
|
|
48
|
+
index = synchronisedLyrics.map(({shortText}) => shortText).indexOf('captions');
|
|
49
|
+
|
|
50
|
+
if (index >= 0) {
|
|
51
|
+
const
|
|
52
|
+
{synchronisedText} = synchronisedLyrics[index];
|
|
53
|
+
|
|
54
|
+
return synchronisedText.map(({text}) => text).join(' ');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
60
|
}
|