@mulingai-npm/redis 3.0.1 → 3.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/dist/loggers/mulingstream-chunk-logger.d.ts +3 -1
- package/dist/loggers/mulingstream-chunk-logger.js +12 -7
- package/dist/managers/mulingstream-chunk-manager.d.ts +28 -48
- package/dist/managers/mulingstream-chunk-manager.js +301 -409
- package/dist/redis-client.d.ts +20 -4
- package/dist/redis-client.js +81 -34
- package/package.json +1 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { MulingstreamChunkManager, MulingstreamChunkData } from '../managers/mulingstream-chunk-manager';
|
|
2
2
|
export interface ChunkLogOptions {
|
|
3
|
+
chunkId?: boolean;
|
|
3
4
|
chunkNumber?: boolean;
|
|
4
5
|
roomId?: boolean;
|
|
5
6
|
language?: boolean;
|
|
7
|
+
processLength?: boolean;
|
|
6
8
|
finalTranscription?: boolean;
|
|
7
9
|
sttStatus?: boolean;
|
|
10
|
+
stt?: boolean;
|
|
8
11
|
translation?: boolean;
|
|
9
12
|
tts?: boolean;
|
|
10
|
-
retryMs?: number;
|
|
11
13
|
label?: string;
|
|
12
14
|
}
|
|
13
15
|
export declare class MulingstreamChunkLogger extends MulingstreamChunkManager {
|
|
@@ -3,17 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MulingstreamChunkLogger = void 0;
|
|
4
4
|
const mulingstream_chunk_manager_1 = require("../managers/mulingstream-chunk-manager");
|
|
5
5
|
const defaults = {
|
|
6
|
+
chunkId: false,
|
|
6
7
|
chunkNumber: true,
|
|
7
|
-
roomId:
|
|
8
|
-
|
|
8
|
+
roomId: false,
|
|
9
|
+
processLength: false,
|
|
10
|
+
language: false,
|
|
9
11
|
finalTranscription: true,
|
|
10
12
|
sttStatus: true,
|
|
13
|
+
stt: false,
|
|
11
14
|
translation: false,
|
|
12
|
-
tts:
|
|
13
|
-
retryMs: 100,
|
|
15
|
+
tts: true,
|
|
14
16
|
label: ''
|
|
15
17
|
};
|
|
16
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
17
18
|
class MulingstreamChunkLogger extends mulingstream_chunk_manager_1.MulingstreamChunkManager {
|
|
18
19
|
/* ------------ one chunk ------------------------------------ */
|
|
19
20
|
/* ------------ one chunk ------------------------------------ */
|
|
@@ -22,7 +23,6 @@ class MulingstreamChunkLogger extends mulingstream_chunk_manager_1.MulingstreamC
|
|
|
22
23
|
const prefix = cfg.label ? `[${cfg.label}] ` : '';
|
|
23
24
|
let chunk = await this.getMulingstreamChunkById(roomId, chunkNumber);
|
|
24
25
|
if (!chunk) {
|
|
25
|
-
await sleep(cfg.retryMs);
|
|
26
26
|
chunk = await this.getMulingstreamChunkById(roomId, chunkNumber);
|
|
27
27
|
}
|
|
28
28
|
if (!chunk) {
|
|
@@ -39,7 +39,6 @@ class MulingstreamChunkLogger extends mulingstream_chunk_manager_1.MulingstreamC
|
|
|
39
39
|
const prefix = cfg.label ? `[${cfg.label}] ` : '';
|
|
40
40
|
let chunks = await this.getRoomById(roomId);
|
|
41
41
|
if (!(chunks === null || chunks === void 0 ? void 0 : chunks.length)) {
|
|
42
|
-
await sleep(cfg.retryMs);
|
|
43
42
|
chunks = await this.getRoomById(roomId);
|
|
44
43
|
}
|
|
45
44
|
if (!(chunks === null || chunks === void 0 ? void 0 : chunks.length)) {
|
|
@@ -56,16 +55,22 @@ class MulingstreamChunkLogger extends mulingstream_chunk_manager_1.MulingstreamC
|
|
|
56
55
|
mapChunk(chunk, cfg = {}) {
|
|
57
56
|
const o = { ...defaults, ...cfg };
|
|
58
57
|
const out = {};
|
|
58
|
+
if (o.chunkId)
|
|
59
|
+
out.chunkId = chunk.chunkId;
|
|
59
60
|
if (o.roomId)
|
|
60
61
|
out.roomId = chunk.roomId;
|
|
61
62
|
if (o.chunkNumber)
|
|
62
63
|
out.chunkNumber = chunk.chunkNumber;
|
|
64
|
+
if (o.processLength)
|
|
65
|
+
out.processLength = Date.now() - chunk.createdAt + 'ms';
|
|
63
66
|
if (o.language)
|
|
64
67
|
out.language = chunk.language;
|
|
65
68
|
if (o.finalTranscription)
|
|
66
69
|
out.finalTranscription = chunk.finalTranscription;
|
|
67
70
|
if (o.sttStatus)
|
|
68
71
|
out.sttStatus = chunk.sttStatus;
|
|
72
|
+
if (o.stt)
|
|
73
|
+
out.stt = chunk.stt;
|
|
69
74
|
if (o.translation)
|
|
70
75
|
out.translation = chunk.translation;
|
|
71
76
|
if (o.tts)
|
|
@@ -2,6 +2,7 @@ import { RedisClient } from '../redis-client';
|
|
|
2
2
|
export type StepStatus = 'INIT' | 'DISCARDED' | 'READY' | 'USED';
|
|
3
3
|
export type SttProvider = 'azure' | 'whisper' | 'google' | 'aws';
|
|
4
4
|
export type MulingstreamChunkData = {
|
|
5
|
+
chunkId: string;
|
|
5
6
|
roomId: string;
|
|
6
7
|
chunkNumber: number;
|
|
7
8
|
language: string;
|
|
@@ -44,25 +45,17 @@ export type MulingstreamChunkData = {
|
|
|
44
45
|
export declare class MulingstreamChunkManager {
|
|
45
46
|
private redisClient;
|
|
46
47
|
constructor(redisClient: RedisClient);
|
|
48
|
+
private roomZsetKey;
|
|
49
|
+
private chunkHashKey;
|
|
50
|
+
private generateChunkId;
|
|
51
|
+
private serialize;
|
|
52
|
+
private deserialize;
|
|
53
|
+
private hashToChunk;
|
|
47
54
|
private getTimeout;
|
|
48
|
-
/**
|
|
49
|
-
* Initializes a room in Redis as an empty JSON array.
|
|
50
|
-
* If the key [roomId] already exists, we do NOT overwrite it.
|
|
51
|
-
* Returns true if room was created, false if room already existed.
|
|
52
|
-
*/
|
|
53
55
|
initRoom(roomId: string): Promise<boolean>;
|
|
54
|
-
/**
|
|
55
|
-
* Returns all rooms. This is naive if you store many other keys in Redis,
|
|
56
|
-
* because we search keys for pattern "[*]".
|
|
57
|
-
* Adjust as needed if you have a separate naming prefix.
|
|
58
|
-
*/
|
|
59
56
|
getRooms(): Promise<string[]>;
|
|
60
|
-
/**
|
|
61
|
-
* Returns the entire array of chunks for the given roomId,
|
|
62
|
-
* or null if the room doesn't exist in Redis.
|
|
63
|
-
*/
|
|
64
57
|
getMulingstreamChunksByRoom(roomId: string): Promise<MulingstreamChunkData[] | null>;
|
|
65
|
-
getRoomById(roomId: string): Promise<MulingstreamChunkData[]
|
|
58
|
+
getRoomById(roomId: string): Promise<MulingstreamChunkData[]>;
|
|
66
59
|
addMulingstreamChunk(params: {
|
|
67
60
|
roomId: string;
|
|
68
61
|
chunkNumber: number;
|
|
@@ -77,53 +70,40 @@ export declare class MulingstreamChunkManager {
|
|
|
77
70
|
targetLanguages: string[];
|
|
78
71
|
shortCodeTargetLanguages: string[];
|
|
79
72
|
}): Promise<void>;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
getMulingstreamChunkById(roomId: string, chunkNumber: number): Promise<MulingstreamChunkData | null>;
|
|
85
|
-
/**
|
|
86
|
-
* Update STT fields for a given chunk.
|
|
87
|
-
* If transcription or sttStatus is null, skip that field.
|
|
88
|
-
*/
|
|
89
|
-
updateStt(roomId: string, chunkNumber: number, sttProvider: SttProvider, options: {
|
|
73
|
+
private getChunkId;
|
|
74
|
+
getMulingstreamChunkById(roomId: string, n: number): Promise<MulingstreamChunkData>;
|
|
75
|
+
private withChunk;
|
|
76
|
+
updateStt(roomId: string, n: number, provider: SttProvider, opt: {
|
|
90
77
|
transcription?: string;
|
|
91
78
|
sttStatus?: StepStatus;
|
|
92
79
|
}): Promise<boolean>;
|
|
93
|
-
updateSttObject(roomId: string,
|
|
80
|
+
updateSttObject(roomId: string, n: number, newStt: Record<string, {
|
|
94
81
|
transcription: string;
|
|
95
82
|
status: StepStatus;
|
|
96
83
|
}>): Promise<boolean>;
|
|
97
|
-
discardStt(roomId: string,
|
|
98
|
-
updateFinalTranscription(roomId: string,
|
|
84
|
+
discardStt(roomId: string, n: number): Promise<boolean>;
|
|
85
|
+
updateFinalTranscription(roomId: string, n: number, opt: {
|
|
99
86
|
transcription?: string;
|
|
100
87
|
sttStatus?: StepStatus;
|
|
101
|
-
}): Promise<MulingstreamChunkData
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
*/
|
|
106
|
-
discardPostStt(roomId: string, chunkNumber: number): Promise<boolean>;
|
|
107
|
-
/**
|
|
108
|
-
* Discards a specific language in both translation and tts for a chunk.
|
|
109
|
-
*/
|
|
110
|
-
discardLanguage(roomId: string, chunkNumber: number, language: string): Promise<boolean>;
|
|
111
|
-
discardLanguages(roomId: string, chunkNumber: number, options: {
|
|
88
|
+
}): Promise<MulingstreamChunkData>;
|
|
89
|
+
discardPostStt(roomId: string, n: number): Promise<boolean>;
|
|
90
|
+
discardLanguage(roomId: string, n: number, lang: string): Promise<boolean>;
|
|
91
|
+
discardLanguages(roomId: string, n: number, opt: {
|
|
112
92
|
translation?: string[];
|
|
113
93
|
tts?: string[];
|
|
114
|
-
}): Promise<MulingstreamChunkData
|
|
115
|
-
updateTranslation(roomId: string,
|
|
94
|
+
}): Promise<MulingstreamChunkData>;
|
|
95
|
+
updateTranslation(roomId: string, n: number, lang: string, opt: {
|
|
116
96
|
translation?: string;
|
|
117
97
|
status?: StepStatus;
|
|
118
|
-
}): Promise<MulingstreamChunkData
|
|
119
|
-
updateTranslationInBulk(roomId: string,
|
|
120
|
-
updateTts(roomId: string,
|
|
98
|
+
}): Promise<MulingstreamChunkData>;
|
|
99
|
+
updateTranslationInBulk(roomId: string, n: number, dict: Record<string, string>, status?: StepStatus): Promise<MulingstreamChunkData>;
|
|
100
|
+
updateTts(roomId: string, n: number, lang: string, opt: {
|
|
121
101
|
ttsAudioPath?: string;
|
|
122
102
|
status?: StepStatus;
|
|
123
103
|
isEmitted?: boolean;
|
|
124
104
|
totalCheck?: number;
|
|
125
|
-
}): Promise<MulingstreamChunkData
|
|
126
|
-
increaseTotalCheck(roomId: string,
|
|
127
|
-
areTranslationsProcessed(roomId: string,
|
|
128
|
-
getAllReadyTts(roomId: string,
|
|
105
|
+
}): Promise<MulingstreamChunkData>;
|
|
106
|
+
increaseTotalCheck(roomId: string, n: number, lang: string): Promise<MulingstreamChunkData>;
|
|
107
|
+
areTranslationsProcessed(roomId: string, n: number): Promise<boolean>;
|
|
108
|
+
getAllReadyTts(roomId: string, lang: string): Promise<MulingstreamChunkData[]>;
|
|
129
109
|
}
|
|
@@ -1,452 +1,346 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MulingstreamChunkManager = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
/* -------------------------------------------------------------------------- */
|
|
6
|
+
/* Constants */
|
|
7
|
+
/* -------------------------------------------------------------------------- */
|
|
8
|
+
const EXPIRATION = 12 * 60 * 60; // 12 h
|
|
9
|
+
const ROOM_ARRAY_LENGTH = 50; // keep last 50 chunks
|
|
10
|
+
/* -------------------------------------------------------------------------- */
|
|
11
|
+
/* Main class */
|
|
12
|
+
/* -------------------------------------------------------------------------- */
|
|
6
13
|
class MulingstreamChunkManager {
|
|
7
14
|
constructor(redisClient) {
|
|
8
15
|
this.redisClient = redisClient;
|
|
9
16
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
/* ----------------------------- key helpers ----------------------------- */
|
|
18
|
+
roomZsetKey(roomId) {
|
|
19
|
+
return `room:${roomId}:chunks`;
|
|
20
|
+
}
|
|
21
|
+
chunkHashKey(chunkId) {
|
|
22
|
+
return `chunk:${chunkId}`;
|
|
23
|
+
}
|
|
24
|
+
generateChunkId(roomId, chunkNumber) {
|
|
25
|
+
return `[${roomId}]-[${chunkNumber}]-[${(0, uuid_1.v4)()}]`;
|
|
26
|
+
}
|
|
27
|
+
/* --------------------------- (de)serialization -------------------------- */
|
|
28
|
+
serialize(value) {
|
|
29
|
+
return JSON.stringify(value);
|
|
30
|
+
}
|
|
31
|
+
deserialize(v) {
|
|
32
|
+
return v ? JSON.parse(v) : undefined;
|
|
33
|
+
}
|
|
34
|
+
hashToChunk(h) {
|
|
35
|
+
return {
|
|
36
|
+
chunkId: h.chunkId,
|
|
37
|
+
roomId: h.roomId,
|
|
38
|
+
chunkNumber: parseInt(h.chunkNumber, 10),
|
|
39
|
+
language: h.language,
|
|
40
|
+
sttProviders: this.deserialize(h.sttProviders),
|
|
41
|
+
targetLanguages: this.deserialize(h.targetLanguages),
|
|
42
|
+
shortCodeTargetLanguages: this.deserialize(h.shortCodeTargetLanguages),
|
|
43
|
+
finalTranscription: h.finalTranscription,
|
|
44
|
+
sttStatus: h.sttStatus,
|
|
45
|
+
createdAt: parseInt(h.createdAt, 10),
|
|
46
|
+
audioChunk: this.deserialize(h.audioChunk),
|
|
47
|
+
stt: this.deserialize(h.stt),
|
|
48
|
+
translation: this.deserialize(h.translation),
|
|
49
|
+
tts: this.deserialize(h.tts)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/* ------------------------------ timeouts ------------------------------- */
|
|
53
|
+
getTimeout(start, end) {
|
|
54
|
+
// const len = end - start;
|
|
55
|
+
// if (len < 1_000) return 1_500;
|
|
56
|
+
// if (len < 3_000) return 3_000;
|
|
57
|
+
// if (len < 5_000) return 7_000;
|
|
58
|
+
// if (len < 8_000) return 10_000;
|
|
59
|
+
// if (len < 12_000) return 15_000;
|
|
60
|
+
// if (len < 20_000) return 20_000;
|
|
61
|
+
return 25000;
|
|
33
62
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*/
|
|
63
|
+
/* ---------------------------------------------------------------------- */
|
|
64
|
+
/* Public API (same) */
|
|
65
|
+
/* ---------------------------------------------------------------------- */
|
|
66
|
+
/* Room helpers ---------------------------------------------------------- */
|
|
39
67
|
async initRoom(roomId) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (isRoomExisting !== null) {
|
|
68
|
+
const exists = await this.redisClient.exists(this.roomZsetKey(roomId));
|
|
69
|
+
if (exists)
|
|
43
70
|
return false;
|
|
44
|
-
|
|
45
|
-
// Create an empty array at the root
|
|
46
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', []);
|
|
47
|
-
await this.redisClient.expire(`[${roomId}]`, EXPIRATION);
|
|
71
|
+
await this.redisClient.expire(this.roomZsetKey(roomId), EXPIRATION);
|
|
48
72
|
return true;
|
|
49
73
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Returns all rooms. This is naive if you store many other keys in Redis,
|
|
52
|
-
* because we search keys for pattern "[*]".
|
|
53
|
-
* Adjust as needed if you have a separate naming prefix.
|
|
54
|
-
*/
|
|
55
74
|
async getRooms() {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// This returns the raw keys, e.g. ['[myRoom]', '[anotherRoom]']
|
|
59
|
-
// We might want to strip off the brackets:
|
|
60
|
-
return rooms.map((k) => k.replace(/^\[|\]$/g, ''));
|
|
75
|
+
const keys = await this.redisClient.keys('room:*:chunks');
|
|
76
|
+
return keys.map((k) => k.replace(/^room:(.*):chunks$/, '$1'));
|
|
61
77
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Returns the entire array of chunks for the given roomId,
|
|
64
|
-
* or null if the room doesn't exist in Redis.
|
|
65
|
-
*/
|
|
66
78
|
async getMulingstreamChunksByRoom(roomId) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (chunks === null) {
|
|
79
|
+
const ids = await this.redisClient.zrange(this.roomZsetKey(roomId), 0, -1);
|
|
80
|
+
if (!ids.length)
|
|
70
81
|
return null;
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
const pipe = this.redisClient.pipeline();
|
|
83
|
+
ids.forEach((cid) => pipe.hgetall(this.chunkHashKey(cid)));
|
|
84
|
+
const res = await pipe.exec();
|
|
85
|
+
return res.map(([, d]) => this.hashToChunk(d));
|
|
73
86
|
}
|
|
74
|
-
|
|
87
|
+
getRoomById(roomId) {
|
|
75
88
|
return this.getMulingstreamChunksByRoom(roomId);
|
|
76
89
|
}
|
|
90
|
+
/* ------------------------- chunk insertion ---------------------------- */
|
|
77
91
|
async addMulingstreamChunk(params) {
|
|
78
|
-
var _a;
|
|
79
92
|
const { roomId, chunkNumber, language, start, end, duration, isFirst, isLast, theme, sttProviders, targetLanguages, shortCodeTargetLanguages } = params;
|
|
80
|
-
/* ----------
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
/* ---------- reset room on first chunk ---------- */
|
|
94
|
+
if (chunkNumber === 1) {
|
|
95
|
+
const old = await this.redisClient.zrange(this.roomZsetKey(roomId), 0, -1);
|
|
96
|
+
if (old.length) {
|
|
97
|
+
const p = this.redisClient.pipeline();
|
|
98
|
+
old.forEach((cid) => p.unlink(this.chunkHashKey(cid)));
|
|
99
|
+
p.del(this.roomZsetKey(roomId));
|
|
100
|
+
await p.exec();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// remove older duplicate with same chunkNumber
|
|
105
|
+
const dup = await this.redisClient.zrangebyscore(this.roomZsetKey(roomId), chunkNumber, chunkNumber);
|
|
106
|
+
if (dup.length) {
|
|
107
|
+
const p = this.redisClient.pipeline();
|
|
108
|
+
dup.forEach((cid) => p.unlink(this.chunkHashKey(cid)));
|
|
109
|
+
p.zrem(this.roomZsetKey(roomId), ...dup);
|
|
110
|
+
await p.exec();
|
|
111
|
+
}
|
|
93
112
|
}
|
|
113
|
+
/* ---------- build new chunk ---------- */
|
|
114
|
+
const chunkId = this.generateChunkId(roomId, chunkNumber);
|
|
115
|
+
const audioChunk = { start, end, duration, isFirst, isLast, theme, processingStart: Date.now() };
|
|
116
|
+
const stt = {};
|
|
117
|
+
sttProviders.forEach((p) => (stt[p] = { transcription: '', status: 'INIT' }));
|
|
94
118
|
const translation = {};
|
|
95
119
|
const tts = {};
|
|
96
|
-
|
|
97
|
-
translation[
|
|
98
|
-
tts[
|
|
99
|
-
}
|
|
100
|
-
const
|
|
120
|
+
shortCodeTargetLanguages.forEach((l) => {
|
|
121
|
+
translation[l] = { translation: '', status: 'INIT' };
|
|
122
|
+
tts[l] = { ttsAudioPath: '', status: 'INIT', isEmitted: false, totalCheck: 0 };
|
|
123
|
+
});
|
|
124
|
+
const hash = {
|
|
125
|
+
chunkId,
|
|
101
126
|
roomId,
|
|
102
|
-
chunkNumber,
|
|
127
|
+
chunkNumber: String(chunkNumber),
|
|
103
128
|
language,
|
|
104
|
-
sttProviders,
|
|
105
|
-
targetLanguages,
|
|
106
|
-
shortCodeTargetLanguages,
|
|
129
|
+
sttProviders: this.serialize(sttProviders),
|
|
130
|
+
targetLanguages: this.serialize(targetLanguages),
|
|
131
|
+
shortCodeTargetLanguages: this.serialize(shortCodeTargetLanguages),
|
|
107
132
|
finalTranscription: '',
|
|
108
133
|
sttStatus: 'INIT',
|
|
109
|
-
createdAt: Date.now(),
|
|
110
|
-
audioChunk,
|
|
111
|
-
stt,
|
|
112
|
-
translation,
|
|
113
|
-
tts
|
|
134
|
+
createdAt: String(Date.now()),
|
|
135
|
+
audioChunk: this.serialize(audioChunk),
|
|
136
|
+
stt: this.serialize(stt),
|
|
137
|
+
translation: this.serialize(translation),
|
|
138
|
+
tts: this.serialize(tts)
|
|
114
139
|
};
|
|
115
|
-
/* ----------
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
140
|
+
/* ---------- write + trim (pipeline) ---------- */
|
|
141
|
+
const pipe = this.redisClient.pipeline();
|
|
142
|
+
pipe.hset(this.chunkHashKey(chunkId), hash);
|
|
143
|
+
pipe.expire(this.chunkHashKey(chunkId), EXPIRATION);
|
|
144
|
+
pipe.zadd(this.roomZsetKey(roomId), chunkNumber, chunkId);
|
|
145
|
+
pipe.expire(this.roomZsetKey(roomId), EXPIRATION);
|
|
146
|
+
pipe.zrange(this.roomZsetKey(roomId), 0, -ROOM_ARRAY_LENGTH - 1, 'WITHSCORES');
|
|
147
|
+
const execResults = await pipe.exec();
|
|
148
|
+
const oldPairs = execResults[4][1]; // [id1, score1, id2, score2, ...]
|
|
149
|
+
if (Array.isArray(oldPairs) && oldPairs.length) {
|
|
150
|
+
const oldIds = [];
|
|
151
|
+
for (let i = 0; i < oldPairs.length; i += 2)
|
|
152
|
+
oldIds.push(oldPairs[i]);
|
|
153
|
+
const trim = this.redisClient.pipeline();
|
|
154
|
+
trim.zrem(this.roomZsetKey(roomId), ...oldIds);
|
|
155
|
+
oldIds.forEach((cid) => trim.unlink(this.chunkHashKey(cid)));
|
|
156
|
+
await trim.exec();
|
|
120
157
|
}
|
|
121
|
-
/* ---------- insert / replace ---------- */
|
|
122
|
-
const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
|
|
123
|
-
if (idx !== -1) {
|
|
124
|
-
chunks[idx] = newChunk;
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
chunks.push(newChunk);
|
|
128
|
-
if (chunks.length > ROOM_ARRAY_LENGTH)
|
|
129
|
-
chunks.shift();
|
|
130
|
-
}
|
|
131
|
-
/* ---------- persist ---------- */
|
|
132
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
133
|
-
await this.redisClient.expire(`[${roomId}]`, EXPIRATION);
|
|
134
158
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
if (!
|
|
159
|
+
/* --------------------------- helper lookup ---------------------------- */
|
|
160
|
+
async getChunkId(roomId, n) {
|
|
161
|
+
const ids = await this.redisClient.zrangebyscore(this.roomZsetKey(roomId), n, n);
|
|
162
|
+
return ids.length ? ids[0] : null;
|
|
163
|
+
}
|
|
164
|
+
async getMulingstreamChunkById(roomId, n) {
|
|
165
|
+
const cid = await this.getChunkId(roomId, n);
|
|
166
|
+
if (!cid)
|
|
143
167
|
return null;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return chunk || null;
|
|
168
|
+
const raw = await this.redisClient.hgetall(this.chunkHashKey(cid));
|
|
169
|
+
return raw.chunkId ? this.hashToChunk(raw) : null;
|
|
147
170
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
chunk.
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// Write back
|
|
172
|
-
chunks[chunkIndex] = chunk;
|
|
173
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
174
|
-
return true;
|
|
171
|
+
/* ------------------------------ Updaters ------------------------------ */
|
|
172
|
+
/* helper to fetch-mutate-save one chunk */
|
|
173
|
+
async withChunk(roomId, n, fn) {
|
|
174
|
+
const cid = await this.getChunkId(roomId, n);
|
|
175
|
+
if (!cid)
|
|
176
|
+
return null;
|
|
177
|
+
const key = this.chunkHashKey(cid);
|
|
178
|
+
const raw = await this.redisClient.hgetall(key);
|
|
179
|
+
if (!raw.chunkId)
|
|
180
|
+
return null;
|
|
181
|
+
const chunk = this.hashToChunk(raw);
|
|
182
|
+
const out = await fn(chunk);
|
|
183
|
+
const p = this.redisClient.pipeline();
|
|
184
|
+
p.hset(key, {
|
|
185
|
+
finalTranscription: chunk.finalTranscription,
|
|
186
|
+
sttStatus: chunk.sttStatus,
|
|
187
|
+
stt: this.serialize(chunk.stt),
|
|
188
|
+
translation: this.serialize(chunk.translation),
|
|
189
|
+
tts: this.serialize(chunk.tts)
|
|
190
|
+
});
|
|
191
|
+
p.expire(key, EXPIRATION);
|
|
192
|
+
await p.exec();
|
|
193
|
+
return out;
|
|
175
194
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return
|
|
186
|
-
}
|
|
187
|
-
// Replace the stt object
|
|
188
|
-
chunks[idx].stt = newStt;
|
|
189
|
-
// Persist back to Redis
|
|
190
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
191
|
-
return true;
|
|
195
|
+
/* ---- specific update methods (same signatures as before) ---- */
|
|
196
|
+
async updateStt(roomId, n, provider, opt) {
|
|
197
|
+
return ((await this.withChunk(roomId, n, (c) => {
|
|
198
|
+
if (!c.stt[provider])
|
|
199
|
+
return false;
|
|
200
|
+
if (opt.transcription !== undefined)
|
|
201
|
+
c.stt[provider].transcription = opt.transcription;
|
|
202
|
+
if (opt.sttStatus !== undefined)
|
|
203
|
+
c.stt[provider].status = opt.sttStatus;
|
|
204
|
+
return true;
|
|
205
|
+
})) === true);
|
|
192
206
|
}
|
|
193
|
-
async
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
// Locate the desired chunk
|
|
200
|
-
const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
|
|
201
|
-
if (idx === -1) {
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
const chunk = chunks[idx];
|
|
205
|
-
// Reset every STT provider entry
|
|
206
|
-
for (const provider of Object.keys(chunk.stt)) {
|
|
207
|
-
chunk.stt[provider].transcription = '';
|
|
208
|
-
chunk.stt[provider].status = 'DISCARDED';
|
|
209
|
-
}
|
|
210
|
-
// Reset aggregate STT fields
|
|
211
|
-
chunk.finalTranscription = '';
|
|
212
|
-
chunk.sttStatus = 'DISCARDED';
|
|
213
|
-
// Persist back to Redis
|
|
214
|
-
chunks[idx] = chunk;
|
|
215
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
216
|
-
return true;
|
|
207
|
+
async updateSttObject(roomId, n, newStt) {
|
|
208
|
+
return ((await this.withChunk(roomId, n, (c) => {
|
|
209
|
+
c.stt = newStt;
|
|
210
|
+
return true;
|
|
211
|
+
})) === true);
|
|
217
212
|
}
|
|
218
|
-
async
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return
|
|
227
|
-
|
|
228
|
-
if (options.transcription !== undefined) {
|
|
229
|
-
chunks[idx].finalTranscription = options.transcription;
|
|
230
|
-
}
|
|
231
|
-
if (options.sttStatus !== undefined) {
|
|
232
|
-
chunks[idx].sttStatus = options.sttStatus;
|
|
233
|
-
}
|
|
234
|
-
// 4) persist and return
|
|
235
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
236
|
-
return chunks[idx];
|
|
213
|
+
async discardStt(roomId, n) {
|
|
214
|
+
return ((await this.withChunk(roomId, n, (c) => {
|
|
215
|
+
Object.keys(c.stt).forEach((p) => {
|
|
216
|
+
c.stt[p].transcription = '';
|
|
217
|
+
c.stt[p].status = 'DISCARDED';
|
|
218
|
+
});
|
|
219
|
+
c.finalTranscription = '';
|
|
220
|
+
c.sttStatus = 'DISCARDED';
|
|
221
|
+
return true;
|
|
222
|
+
})) === true);
|
|
237
223
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// Find chunk
|
|
247
|
-
const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
|
|
248
|
-
if (chunkIndex === -1)
|
|
249
|
-
return false;
|
|
250
|
-
// Discard translation & TTS statuses
|
|
251
|
-
const chunk = chunks[chunkIndex];
|
|
252
|
-
for (const lang of Object.keys(chunk.translation)) {
|
|
253
|
-
chunk.translation[lang].status = 'DISCARDED';
|
|
254
|
-
}
|
|
255
|
-
for (const lang of Object.keys(chunk.tts)) {
|
|
256
|
-
chunk.tts[lang].status = 'DISCARDED';
|
|
257
|
-
}
|
|
258
|
-
chunks[chunkIndex] = chunk;
|
|
259
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
260
|
-
return true;
|
|
224
|
+
async updateFinalTranscription(roomId, n, opt) {
|
|
225
|
+
return this.withChunk(roomId, n, (c) => {
|
|
226
|
+
if (opt.transcription !== undefined)
|
|
227
|
+
c.finalTranscription = opt.transcription;
|
|
228
|
+
if (opt.sttStatus !== undefined)
|
|
229
|
+
c.sttStatus = opt.sttStatus;
|
|
230
|
+
return c;
|
|
231
|
+
});
|
|
261
232
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return false;
|
|
269
|
-
const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
|
|
270
|
-
if (chunkIndex === -1)
|
|
271
|
-
return false;
|
|
272
|
-
const chunk = chunks[chunkIndex];
|
|
273
|
-
if (chunk.translation[language]) {
|
|
274
|
-
chunk.translation[language].status = 'DISCARDED';
|
|
275
|
-
}
|
|
276
|
-
if (chunk.tts[language]) {
|
|
277
|
-
chunk.tts[language].status = 'DISCARDED';
|
|
278
|
-
}
|
|
279
|
-
chunks[chunkIndex] = chunk;
|
|
280
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
281
|
-
return true;
|
|
233
|
+
async discardPostStt(roomId, n) {
|
|
234
|
+
return ((await this.withChunk(roomId, n, (c) => {
|
|
235
|
+
Object.values(c.translation).forEach((t) => (t.status = 'DISCARDED'));
|
|
236
|
+
Object.values(c.tts).forEach((t) => (t.status = 'DISCARDED'));
|
|
237
|
+
return true;
|
|
238
|
+
})) === true);
|
|
282
239
|
}
|
|
283
|
-
async
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
return null;
|
|
292
|
-
const chunk = chunks[idx];
|
|
293
|
-
// 3) discard translation languages still in INIT
|
|
294
|
-
if (options.translation) {
|
|
295
|
-
for (const lang of options.translation) {
|
|
296
|
-
const entry = chunk.translation[lang];
|
|
297
|
-
if (entry && entry.status === 'INIT') {
|
|
298
|
-
entry.status = 'DISCARDED';
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
// 4) discard TTS languages still in INIT
|
|
303
|
-
if (options.tts) {
|
|
304
|
-
for (const lang of options.tts) {
|
|
305
|
-
const entry = chunk.tts[lang];
|
|
306
|
-
if (entry && entry.status === 'INIT') {
|
|
307
|
-
entry.status = 'DISCARDED';
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
// 5) persist and return
|
|
312
|
-
chunks[idx] = chunk;
|
|
313
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
314
|
-
return chunk;
|
|
240
|
+
async discardLanguage(roomId, n, lang) {
|
|
241
|
+
return ((await this.withChunk(roomId, n, (c) => {
|
|
242
|
+
if (c.translation[lang])
|
|
243
|
+
c.translation[lang].status = 'DISCARDED';
|
|
244
|
+
if (c.tts[lang])
|
|
245
|
+
c.tts[lang].status = 'DISCARDED';
|
|
246
|
+
return true;
|
|
247
|
+
})) === true);
|
|
315
248
|
}
|
|
316
|
-
async
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
chunk.translation[language].translation = options.translation;
|
|
332
|
-
}
|
|
333
|
-
if (options.status !== undefined) {
|
|
334
|
-
chunk.translation[language].status = options.status;
|
|
335
|
-
}
|
|
336
|
-
// 5) persist and return
|
|
337
|
-
chunks[idx] = chunk;
|
|
338
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
339
|
-
return chunk;
|
|
249
|
+
async discardLanguages(roomId, n, opt) {
|
|
250
|
+
return this.withChunk(roomId, n, (c) => {
|
|
251
|
+
var _a, _b;
|
|
252
|
+
(_a = opt.translation) === null || _a === void 0 ? void 0 : _a.forEach((l) => {
|
|
253
|
+
const e = c.translation[l];
|
|
254
|
+
if (e && e.status === 'INIT')
|
|
255
|
+
e.status = 'DISCARDED';
|
|
256
|
+
});
|
|
257
|
+
(_b = opt.tts) === null || _b === void 0 ? void 0 : _b.forEach((l) => {
|
|
258
|
+
const e = c.tts[l];
|
|
259
|
+
if (e && e.status === 'INIT')
|
|
260
|
+
e.status = 'DISCARDED';
|
|
261
|
+
});
|
|
262
|
+
return c;
|
|
263
|
+
});
|
|
340
264
|
}
|
|
341
|
-
async
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return
|
|
351
|
-
}
|
|
352
|
-
const chunk = chunks[chunkIdx];
|
|
353
|
-
// 3) apply updates only for the languages provided
|
|
354
|
-
for (const [lang, translationText] of Object.entries(translations)) {
|
|
355
|
-
if (!chunk.translation[lang]) {
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
chunk.translation[lang].translation = translationText;
|
|
359
|
-
chunk.translation[lang].status = status;
|
|
360
|
-
}
|
|
361
|
-
// 4) write the full room array back to Redis
|
|
362
|
-
chunks[chunkIdx] = chunk;
|
|
363
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
364
|
-
return chunk;
|
|
265
|
+
async updateTranslation(roomId, n, lang, opt) {
|
|
266
|
+
return this.withChunk(roomId, n, (c) => {
|
|
267
|
+
const e = c.translation[lang];
|
|
268
|
+
if (!e)
|
|
269
|
+
return null;
|
|
270
|
+
if (opt.translation !== undefined)
|
|
271
|
+
e.translation = opt.translation;
|
|
272
|
+
if (opt.status !== undefined)
|
|
273
|
+
e.status = opt.status;
|
|
274
|
+
return c;
|
|
275
|
+
});
|
|
365
276
|
}
|
|
366
|
-
async
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
return
|
|
375
|
-
|
|
376
|
-
// 3) ensure the language exists in the TTS map
|
|
377
|
-
if (!chunk.tts[language])
|
|
378
|
-
return null;
|
|
379
|
-
// 4) apply only the provided keys
|
|
380
|
-
if (options.ttsAudioPath !== undefined) {
|
|
381
|
-
chunk.tts[language].ttsAudioPath = options.ttsAudioPath;
|
|
382
|
-
}
|
|
383
|
-
if (options.status !== undefined) {
|
|
384
|
-
chunk.tts[language].status = options.status;
|
|
385
|
-
}
|
|
386
|
-
if (options.isEmitted !== undefined) {
|
|
387
|
-
chunk.tts[language].isEmitted = options.isEmitted;
|
|
388
|
-
}
|
|
389
|
-
if (options.totalCheck !== undefined) {
|
|
390
|
-
chunk.tts[language].totalCheck = options.totalCheck;
|
|
391
|
-
}
|
|
392
|
-
// 5) persist and return the updated chunk
|
|
393
|
-
chunks[idx] = chunk;
|
|
394
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
395
|
-
return chunk;
|
|
277
|
+
async updateTranslationInBulk(roomId, n, dict, status = 'READY') {
|
|
278
|
+
return this.withChunk(roomId, n, (c) => {
|
|
279
|
+
Object.entries(dict).forEach(([l, txt]) => {
|
|
280
|
+
if (!c.translation[l])
|
|
281
|
+
return;
|
|
282
|
+
c.translation[l].translation = txt;
|
|
283
|
+
c.translation[l].status = status;
|
|
284
|
+
});
|
|
285
|
+
return c;
|
|
286
|
+
});
|
|
396
287
|
}
|
|
397
|
-
async
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
413
|
-
return chunk;
|
|
288
|
+
async updateTts(roomId, n, lang, opt) {
|
|
289
|
+
return this.withChunk(roomId, n, (c) => {
|
|
290
|
+
const e = c.tts[lang];
|
|
291
|
+
if (!e)
|
|
292
|
+
return null;
|
|
293
|
+
if (opt.ttsAudioPath !== undefined)
|
|
294
|
+
e.ttsAudioPath = opt.ttsAudioPath;
|
|
295
|
+
if (opt.status !== undefined)
|
|
296
|
+
e.status = opt.status;
|
|
297
|
+
if (opt.isEmitted !== undefined)
|
|
298
|
+
e.isEmitted = opt.isEmitted;
|
|
299
|
+
if (opt.totalCheck !== undefined)
|
|
300
|
+
e.totalCheck = opt.totalCheck;
|
|
301
|
+
return c;
|
|
302
|
+
});
|
|
414
303
|
}
|
|
415
|
-
async
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
304
|
+
async increaseTotalCheck(roomId, n, lang) {
|
|
305
|
+
return this.withChunk(roomId, n, (c) => {
|
|
306
|
+
if (!c.tts[lang])
|
|
307
|
+
return null;
|
|
308
|
+
c.tts[lang].totalCheck += 1;
|
|
309
|
+
return c;
|
|
310
|
+
});
|
|
421
311
|
}
|
|
422
|
-
async
|
|
423
|
-
const
|
|
424
|
-
|
|
312
|
+
async areTranslationsProcessed(roomId, n) {
|
|
313
|
+
const c = await this.getMulingstreamChunkById(roomId, n);
|
|
314
|
+
return !!c && Object.values(c.translation).every((t) => t.status !== 'INIT');
|
|
315
|
+
}
|
|
316
|
+
/* -------------------------- READY-TTS sequence ------------------------- */
|
|
317
|
+
async getAllReadyTts(roomId, lang) {
|
|
318
|
+
var _a;
|
|
319
|
+
const chunks = (_a = (await this.getMulingstreamChunksByRoom(roomId))) !== null && _a !== void 0 ? _a : [];
|
|
320
|
+
if (!chunks.length)
|
|
425
321
|
return [];
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
let
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
lastLangIdx = i;
|
|
322
|
+
chunks.sort((a, b) => a.chunkNumber - b.chunkNumber);
|
|
323
|
+
/* isolate window where lang exists */
|
|
324
|
+
let lastIdx = -1;
|
|
325
|
+
for (let i = chunks.length - 1; i >= 0; i--)
|
|
326
|
+
if (chunks[i].tts[lang]) {
|
|
327
|
+
lastIdx = i;
|
|
433
328
|
break;
|
|
434
329
|
}
|
|
435
|
-
|
|
436
|
-
if (lastLangIdx === -1)
|
|
330
|
+
if (lastIdx === -1)
|
|
437
331
|
return [];
|
|
438
|
-
let
|
|
439
|
-
for (let i =
|
|
440
|
-
if (!chunks[i].tts[
|
|
332
|
+
let firstIdx = lastIdx;
|
|
333
|
+
for (let i = lastIdx - 1; i >= 0; i--) {
|
|
334
|
+
if (!chunks[i].tts[lang])
|
|
441
335
|
break;
|
|
442
|
-
|
|
336
|
+
firstIdx = i;
|
|
443
337
|
}
|
|
444
|
-
const window = chunks.slice(
|
|
445
|
-
/*
|
|
338
|
+
const window = chunks.slice(firstIdx, lastIdx + 1);
|
|
339
|
+
/* find last USED/DISCARDED */
|
|
446
340
|
let lastDone = -1;
|
|
447
341
|
for (let i = window.length - 1; i >= 0; i--) {
|
|
448
|
-
const
|
|
449
|
-
if (
|
|
342
|
+
const s = window[i].tts[lang].status;
|
|
343
|
+
if (s === 'USED' || s === 'DISCARDED') {
|
|
450
344
|
lastDone = i;
|
|
451
345
|
break;
|
|
452
346
|
}
|
|
@@ -455,30 +349,28 @@ class MulingstreamChunkManager {
|
|
|
455
349
|
let blocked = false;
|
|
456
350
|
const now = Date.now();
|
|
457
351
|
for (let i = lastDone + 1; i < window.length; i++) {
|
|
458
|
-
const entry = window[i].tts[
|
|
459
|
-
const
|
|
352
|
+
const entry = window[i].tts[lang];
|
|
353
|
+
const audio = window[i].audioChunk;
|
|
460
354
|
if (entry.status === 'INIT') {
|
|
461
|
-
|
|
462
|
-
console.log('AUDIO LENGTH: ', audioChunk.end - audioChunk.start, ' TIMEOUT BASED ON AUDIO LENGTH: ', timeout, ' SINCE CREATED UNTIL NOW: ', now - window[i].createdAt);
|
|
463
|
-
if (now - window[i].createdAt > timeout) {
|
|
355
|
+
if (now - window[i].createdAt > this.getTimeout(audio.start, audio.end))
|
|
464
356
|
entry.status = 'DISCARDED';
|
|
465
|
-
|
|
466
|
-
else {
|
|
357
|
+
else
|
|
467
358
|
blocked = true;
|
|
468
|
-
}
|
|
469
359
|
continue;
|
|
470
360
|
}
|
|
471
361
|
if (entry.status === 'READY') {
|
|
472
362
|
if (blocked)
|
|
473
|
-
break;
|
|
363
|
+
break;
|
|
474
364
|
ready.push(window[i]);
|
|
475
365
|
continue;
|
|
476
366
|
}
|
|
477
|
-
break; //
|
|
367
|
+
break; // got USED/DISCARDED unexpectedly
|
|
478
368
|
}
|
|
479
|
-
|
|
480
|
-
if (ready.length
|
|
481
|
-
|
|
369
|
+
/* persist DISCARDED changes (if any) */
|
|
370
|
+
if (!ready.length || blocked) {
|
|
371
|
+
const p = this.redisClient.pipeline();
|
|
372
|
+
window.forEach((c) => p.hset(this.chunkHashKey(c.chunkId), { tts: this.serialize(c.tts) }));
|
|
373
|
+
await p.exec();
|
|
482
374
|
}
|
|
483
375
|
return ready;
|
|
484
376
|
}
|
package/dist/redis-client.d.ts
CHANGED
|
@@ -1,28 +1,44 @@
|
|
|
1
|
+
import IORedis from 'ioredis';
|
|
1
2
|
export interface RedisConfig {
|
|
2
3
|
host: string;
|
|
3
4
|
port: number;
|
|
4
5
|
password?: string;
|
|
5
6
|
db: number;
|
|
6
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Thin wrapper around ioredis that exposes exactly the helpers used by the two
|
|
10
|
+
* managers. All helpers are 1‑to‑1 pass‑through so there is no behavioural
|
|
11
|
+
* change – this merely centralises typing and keeps the rest of the codebase
|
|
12
|
+
* clean.
|
|
13
|
+
*/
|
|
7
14
|
export declare class RedisClient {
|
|
8
15
|
private client;
|
|
9
16
|
constructor(config: RedisConfig);
|
|
10
17
|
set(key: string, value: string): Promise<void>;
|
|
11
18
|
get(key: string): Promise<string | null>;
|
|
12
19
|
del(key: string): Promise<number>;
|
|
20
|
+
exists(key: string): Promise<number>;
|
|
21
|
+
expire(key: string, seconds: number): Promise<number>;
|
|
13
22
|
sadd(key: string, ...values: string[]): Promise<number>;
|
|
14
23
|
srem(key: string, ...values: string[]): Promise<number>;
|
|
15
24
|
smembers(key: string): Promise<string[]>;
|
|
16
|
-
keys(pattern: string): Promise<string[]>;
|
|
17
25
|
hset(key: string, data: Record<string, string>): Promise<number>;
|
|
18
26
|
hgetall(key: string): Promise<Record<string, string>>;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
zadd(key: string, score: number, member: string): Promise<number>;
|
|
28
|
+
zrange(key: string, start: number, stop: number): Promise<string[]>;
|
|
29
|
+
zremrangebyrank(key: string, start: number, stop: number): Promise<number>;
|
|
30
|
+
zrangebyscore(key: string, min: number | string, max: number | string): Promise<string[]>;
|
|
31
|
+
zrem(key: string, ...members: string[]): Promise<number>;
|
|
32
|
+
scan(cursor: number, pattern: string, count?: number): Promise<[string, string[]]>;
|
|
33
|
+
keys(pattern: string): Promise<string[]>;
|
|
34
|
+
pipeline(): ReturnType<IORedis['pipeline']>;
|
|
35
|
+
unlink(...keys: string[]): Promise<number>;
|
|
22
36
|
jsonSet<T>(key: string, path: string, value: T): Promise<string>;
|
|
23
37
|
jsonGet<T>(key: string, path: string): Promise<T | null>;
|
|
24
38
|
jsonDel(key: string, path: string): Promise<number>;
|
|
25
39
|
jsonArrAppend<T>(key: string, path: string, ...values: T[]): Promise<number>;
|
|
26
40
|
jsonArrLen(key: string, path: string): Promise<number | null>;
|
|
27
41
|
jsonArrPop(key: string, path: string, index?: number): Promise<any>;
|
|
42
|
+
flushAll(): Promise<string>;
|
|
43
|
+
flushDb(): Promise<string>;
|
|
28
44
|
}
|
package/dist/redis-client.js
CHANGED
|
@@ -5,6 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.RedisClient = void 0;
|
|
7
7
|
const ioredis_1 = __importDefault(require("ioredis"));
|
|
8
|
+
/**
|
|
9
|
+
* Thin wrapper around ioredis that exposes exactly the helpers used by the two
|
|
10
|
+
* managers. All helpers are 1‑to‑1 pass‑through so there is no behavioural
|
|
11
|
+
* change – this merely centralises typing and keeps the rest of the codebase
|
|
12
|
+
* clean.
|
|
13
|
+
*/
|
|
8
14
|
class RedisClient {
|
|
9
15
|
constructor(config) {
|
|
10
16
|
this.client = new ioredis_1.default({
|
|
@@ -17,7 +23,9 @@ class RedisClient {
|
|
|
17
23
|
console.error('Redis error:', err);
|
|
18
24
|
});
|
|
19
25
|
}
|
|
20
|
-
|
|
26
|
+
/* ----------------------------------------------------------------------- */
|
|
27
|
+
/* Primitive string helpers */
|
|
28
|
+
/* ----------------------------------------------------------------------- */
|
|
21
29
|
async set(key, value) {
|
|
22
30
|
await this.client.set(key, value);
|
|
23
31
|
}
|
|
@@ -27,70 +35,109 @@ class RedisClient {
|
|
|
27
35
|
async del(key) {
|
|
28
36
|
return this.client.del(key);
|
|
29
37
|
}
|
|
30
|
-
|
|
38
|
+
async exists(key) {
|
|
39
|
+
return this.client.exists(key);
|
|
40
|
+
}
|
|
41
|
+
async expire(key, seconds) {
|
|
42
|
+
return this.client.expire(key, seconds);
|
|
43
|
+
}
|
|
44
|
+
/* ----------------------------------------------------------------------- */
|
|
45
|
+
/* Set helpers */
|
|
46
|
+
/* ----------------------------------------------------------------------- */
|
|
31
47
|
async sadd(key, ...values) {
|
|
32
48
|
return this.client.sadd(key, values);
|
|
33
49
|
}
|
|
34
50
|
async srem(key, ...values) {
|
|
35
51
|
return this.client.srem(key, values);
|
|
36
52
|
}
|
|
37
|
-
// Get all set members
|
|
38
53
|
async smembers(key) {
|
|
39
54
|
return this.client.smembers(key);
|
|
40
55
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return this.client.keys(pattern);
|
|
45
|
-
}
|
|
46
|
-
// add these methods for hash, sets, keys, etc.
|
|
56
|
+
/* ----------------------------------------------------------------------- */
|
|
57
|
+
/* Hash helpers */
|
|
58
|
+
/* ----------------------------------------------------------------------- */
|
|
47
59
|
async hset(key, data) {
|
|
48
60
|
return this.client.hset(key, data);
|
|
49
61
|
}
|
|
50
62
|
async hgetall(key) {
|
|
51
63
|
return this.client.hgetall(key);
|
|
52
64
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
/* ----------------------------------------------------------------------- */
|
|
66
|
+
/* Sorted‑set helpers */
|
|
67
|
+
/* ----------------------------------------------------------------------- */
|
|
68
|
+
async zadd(key, score, member) {
|
|
69
|
+
return this.client.zadd(key, score, member);
|
|
57
70
|
}
|
|
58
|
-
async
|
|
59
|
-
|
|
60
|
-
return this.client.flushdb();
|
|
71
|
+
async zrange(key, start, stop) {
|
|
72
|
+
return this.client.zrange(key, start, stop);
|
|
61
73
|
}
|
|
62
|
-
async
|
|
63
|
-
return this.client.
|
|
74
|
+
async zremrangebyrank(key, start, stop) {
|
|
75
|
+
return this.client.zremrangebyrank(key, start, stop);
|
|
64
76
|
}
|
|
77
|
+
async zrangebyscore(key, min, max) {
|
|
78
|
+
return this.client.zrangebyscore(key, min, max);
|
|
79
|
+
}
|
|
80
|
+
async zrem(key, ...members) {
|
|
81
|
+
return this.client.zrem(key, members);
|
|
82
|
+
}
|
|
83
|
+
/* ----------------------------------------------------------------------- */
|
|
84
|
+
/* Scan (pattern search) */
|
|
85
|
+
/* ----------------------------------------------------------------------- */
|
|
86
|
+
async scan(cursor, pattern, count = 1000) {
|
|
87
|
+
return this.client.scan(cursor, 'MATCH', pattern, 'COUNT', count);
|
|
88
|
+
}
|
|
89
|
+
async keys(pattern) {
|
|
90
|
+
return this.client.keys(pattern);
|
|
91
|
+
}
|
|
92
|
+
/* ----------------------------------------------------------------------- */
|
|
93
|
+
/* Pipeline */
|
|
94
|
+
/* ----------------------------------------------------------------------- */
|
|
95
|
+
pipeline() {
|
|
96
|
+
return this.client.pipeline();
|
|
97
|
+
}
|
|
98
|
+
/* ----------------------------------------------------------------------- */
|
|
99
|
+
/* Misc */
|
|
100
|
+
/* ----------------------------------------------------------------------- */
|
|
101
|
+
async unlink(...keys) {
|
|
102
|
+
// unlink exists on Redis ≥4 and is supported by ioredis but not typed in
|
|
103
|
+
// older versions – we cast to any.
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
105
|
+
// @ts-ignore – runtime command exists even if typings lag behind.
|
|
106
|
+
return this.client.unlink(...keys);
|
|
107
|
+
}
|
|
108
|
+
/* JSON commands (RedisJSON module) -------------------------------------- */
|
|
65
109
|
async jsonSet(key, path, value) {
|
|
66
110
|
const result = await this.client.call('JSON.SET', key, path, JSON.stringify(value));
|
|
67
111
|
return result;
|
|
68
112
|
}
|
|
69
113
|
async jsonGet(key, path) {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
return JSON.parse(result);
|
|
114
|
+
const raw = (await this.client.call('JSON.GET', key, path));
|
|
115
|
+
return raw ? JSON.parse(raw) : null;
|
|
75
116
|
}
|
|
76
117
|
async jsonDel(key, path) {
|
|
77
|
-
|
|
78
|
-
return Number(result);
|
|
118
|
+
return Number(await this.client.call('JSON.DEL', key, path));
|
|
79
119
|
}
|
|
80
120
|
async jsonArrAppend(key, path, ...values) {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
return Number(result);
|
|
121
|
+
const stringified = values.map((v) => JSON.stringify(v));
|
|
122
|
+
return Number(await this.client.call('JSON.ARRAPPEND', key, path, ...stringified));
|
|
84
123
|
}
|
|
85
124
|
async jsonArrLen(key, path) {
|
|
86
|
-
const
|
|
87
|
-
return
|
|
125
|
+
const len = await this.client.call('JSON.ARRLEN', key, path);
|
|
126
|
+
return len === null ? null : Number(len);
|
|
88
127
|
}
|
|
89
128
|
async jsonArrPop(key, path, index) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
129
|
+
return index !== undefined ? this.client.call('JSON.ARRPOP', key, path, index) : this.client.call('JSON.ARRPOP', key, path);
|
|
130
|
+
}
|
|
131
|
+
/* ----------------------------------------------------------------------- */
|
|
132
|
+
/* Dangerous! – flush helpers */
|
|
133
|
+
/* ----------------------------------------------------------------------- */
|
|
134
|
+
async flushAll() {
|
|
135
|
+
console.warn('Flushing all Redis data!');
|
|
136
|
+
return this.client.flushall();
|
|
137
|
+
}
|
|
138
|
+
async flushDb() {
|
|
139
|
+
console.warn('Flushing current Redis DB!');
|
|
140
|
+
return this.client.flushdb();
|
|
94
141
|
}
|
|
95
142
|
}
|
|
96
143
|
exports.RedisClient = RedisClient;
|