arcane-os 0.76.0 → 0.78.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.
@@ -111,8 +111,10 @@ function serializeStorage(storage,execute){
111
111
  return operation;
112
112
  }
113
113
 
114
- async function readManifest(storage){
115
- const stored=await storage.db.get(storage.table,storage.fileName,true);
114
+ async function readManifest(storage,cacheOnly=false){
115
+ const stored=cacheOnly
116
+ ?await storage.db.get(storage.table,storage.fileName,true,{createTable:false})
117
+ :await storage.db.get(storage.table,storage.fileName,true);
116
118
  if(stored===null||stored===undefined)return {version:1,entries:[]};
117
119
  const manifest=copyJSON(stored);
118
120
  if(manifest.version!==1||!is.array(manifest.entries)){
@@ -349,9 +351,10 @@ function finishPreparation(job,error){
349
351
  job.finished.resolve();
350
352
  }
351
353
 
352
- function createJob(ownerState,input,storage,synthesize,previous){
353
- const job={
354
+ function createJobState(input,storage,synthesize=null,previous=null){
355
+ return {
354
356
  input,storage,synthesize,previous,
357
+ cacheOnly:false,
355
358
  controller:new AbortController(),
356
359
  interests:new Set(),
357
360
  state:'queued',
@@ -367,6 +370,10 @@ function createJob(ownerState,input,storage,synthesize,previous){
367
370
  return {...part,index,state:'queued',audioFile:null,contentType:null,error:null};
368
371
  })
369
372
  };
373
+ }
374
+
375
+ function createJob(ownerState,input,storage,synthesize,previous){
376
+ const job=createJobState(input,storage,synthesize,previous);
370
377
  const admission=ownerState.admission.then(function admitOrderedPreparation(){
371
378
  return admitPreparation(job);
372
379
  });
@@ -416,7 +423,9 @@ function createHandle(job,signal,onState){
416
423
  if(!interest.active)throw abortError();
417
424
  if(!job.storage)return job.slots[index].blob;
418
425
  const audio=job.record.segments[index];
419
- const file=await job.storage.db.readFile(job.storage.table,audio.audioFile);
426
+ const file=job.cacheOnly
427
+ ?await job.storage.db.readFile(job.storage.table,audio.audioFile,{createTable:false})
428
+ :await job.storage.db.readFile(job.storage.table,audio.audioFile);
420
429
  if(!interest.active)throw abortError();
421
430
  return file.type===audio.contentType?file:new Blob([file],{type:audio.contentType});
422
431
  },
@@ -482,4 +491,100 @@ function prepareSpeech({
482
491
  return createHandle(job,signal,onState);
483
492
  }
484
493
 
485
- export {prepareSpeech};
494
+ /** Read complete existing audio without synthesis, storage writes, or repair. */
495
+ async function readPreparedSpeech({
496
+ owner,
497
+ parts,
498
+ originalParts=parts,
499
+ selection=null,
500
+ segmentation=null,
501
+ storage=null,
502
+ identity=null,
503
+ signal=null,
504
+ onState=null
505
+ }={}){
506
+ if((!owner||!is.object(owner))&&!is.function(owner)){
507
+ throw new TypeError('Speech preparation requires its owning AI instance.');
508
+ }
509
+ if(!is.array(parts)||(onState!==null&&!is.function(onState))){
510
+ throw new TypeError('Cached speech requires ordered parts and an optional state callback.');
511
+ }
512
+ const input=copyJSON({parts,originalParts,selection,segmentation,identity});
513
+ const destination=normalizeStorage(storage);
514
+ if(signal?.aborted)throw abortError();
515
+ function completeCachedRead(job){
516
+ // The lookup signal owns only this read. Retained audio keeps its own
517
+ // explicit cancellation lifetime after a successful lookup returns.
518
+ const handle=createHandle(job,null,onState);
519
+ if(signal?.aborted){
520
+ handle.cancel();
521
+ throw abortError();
522
+ }
523
+ return handle;
524
+ }
525
+ if(!destination){
526
+ const jobs=owners.get(owner)?.jobs;
527
+ if(!jobs)return null;
528
+ for(const job of jobs){
529
+ if(job.storage||!job.done||job.failed||job.controller.signal.aborted
530
+ ||!sameInput(job.input,input))continue;
531
+ if(!job.slots.every(function hasRetainedAudio(slot){
532
+ return slot.settled&&slot.blob instanceof Blob;
533
+ }))return null;
534
+ return completeCachedRead(job);
535
+ }
536
+ return null;
537
+ }
538
+ const cancelled=deferred();
539
+ function cancelRead(){cancelled.reject(abortError());}
540
+ signal?.addEventListener('abort',cancelRead,{once:true});
541
+ try{
542
+ const reading=serializeStorage(destination,async function readCachedPreparation(){
543
+ if(signal?.aborted)throw abortError();
544
+ const manifest=await readManifest(destination,true);
545
+ if(signal?.aborted)throw abortError();
546
+ const record=manifest.entries.find(function findCachedPreparation(entry){
547
+ return sameInput(entry,input);
548
+ });
549
+ if(!record)return null;
550
+ if(!is.array(record.segments)||record.segments.length!==input.parts.length){
551
+ throw new TypeError('Stored speech preparation segments are unreadable.');
552
+ }
553
+ let complete=true;
554
+ for(const audio of record.segments){
555
+ if(!audio?.audioFile){
556
+ complete=false;
557
+ continue;
558
+ }
559
+ if(!is.string(audio.contentType)){
560
+ throw new TypeError('Stored speech audio content type is unreadable.');
561
+ }
562
+ if(signal?.aborted)throw abortError();
563
+ try{
564
+ const file=await destination.db.readFile(destination.table,audio.audioFile,{createTable:false});
565
+ if(!(file instanceof Blob)){
566
+ throw new TypeError('Stored speech audio must be a Blob.');
567
+ }
568
+ }catch(error){
569
+ if(error?.name!=='NotFoundError')throw error;
570
+ complete=false;
571
+ }
572
+ if(signal?.aborted)throw abortError();
573
+ }
574
+ return complete?record:null;
575
+ });
576
+ const record=await Promise.race([reading,cancelled.promise]);
577
+ if(signal?.aborted)throw abortError();
578
+ if(!record)return null;
579
+ const job=createJobState(input,destination);
580
+ job.cacheOnly=true;
581
+ job.record=record;
582
+ for(let index=0;index<job.slots.length;index+=1)settleSegment(job,index);
583
+ finishPreparation(job);
584
+ return completeCachedRead(job);
585
+ }finally{
586
+ signal?.removeEventListener('abort',cancelRead);
587
+ }
588
+ }
589
+
590
+ export {prepareSpeech,readPreparedSpeech};