@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.
@@ -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;
@@ -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;
@@ -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
  }
@@ -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
  }
@@ -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
  }