cohere-transcribe 0.1.2 → 0.1.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +9 -7
- data/ext/cohere_transcribe_native/.gitignore +6 -0
- data/ext/cohere_transcribe_native/CMakeLists.txt +8 -0
- data/ext/cohere_transcribe_native/audio_abi.cpp +20 -0
- data/ext/cohere_transcribe_native/audio_exports.macos +1 -0
- data/ext/cohere_transcribe_native/audio_exports.map +1 -0
- data/ext/cohere_transcribe_native/test/abi_smoke.rb +26 -0
- data/lib/cohere/transcribe/alignment/aligner.rb +40 -7
- data/lib/cohere/transcribe/asr/native.rb +43 -97
- data/lib/cohere/transcribe/audio/decoder.rb +184 -75
- data/lib/cohere/transcribe/audio/ffmpeg_native.rb +84 -32
- data/lib/cohere/transcribe/audio/segmentation.rb +1 -0
- data/lib/cohere/transcribe/cli.rb +30 -22
- data/lib/cohere/transcribe/configuration.rb +13 -0
- data/lib/cohere/transcribe/constants.rb +1 -1
- data/lib/cohere/transcribe/doctor.rb +2 -1
- data/lib/cohere/transcribe/hub.rb +446 -58
- data/lib/cohere/transcribe/input.rb +18 -3
- data/lib/cohere/transcribe/internal/interruptible_native_call.rb +70 -0
- data/lib/cohere/transcribe/internal/session_ownership.rb +61 -0
- data/lib/cohere/transcribe/internal/utf8.rb +19 -0
- data/lib/cohere/transcribe/model_identity.rb +10 -1
- data/lib/cohere/transcribe/output/publication.rb +95 -86
- data/lib/cohere/transcribe/pytorch_checkpoint.rb +38 -22
- data/lib/cohere/transcribe/runtime/engine.rb +37 -16
- data/lib/cohere/transcribe/runtime/preparation.rb +212 -35
- data/lib/cohere/transcribe/runtime/resources.rb +28 -62
- data/lib/cohere/transcribe/state/checkpoint.rb +3 -2
- data/lib/cohere/transcribe/state/io.rb +217 -117
- data/lib/cohere/transcribe/state/locking.rb +383 -64
- data/lib/cohere/transcribe/state/manifest.rb +21 -22
- data/lib/cohere/transcribe/types.rb +25 -11
- data/lib/cohere/transcribe/version.rb +1 -1
- data/sig/cohere/transcribe.rbs +1 -0
- metadata +5 -1
|
@@ -12,10 +12,11 @@ module Cohere
|
|
|
12
12
|
STATE_SUFFIX = ".cohere-transcribe.manifest.json"
|
|
13
13
|
CHECKPOINT_SUFFIX = ".cohere-transcribe.asr.json"
|
|
14
14
|
MAX_STATE_BYTES = 64 * 1024 * 1024
|
|
15
|
-
DEFERRED_PUBLICATION_EXCEPTIONS = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
DEFERRED_PUBLICATION_EXCEPTIONS = { Object => :never }.freeze
|
|
16
|
+
|
|
17
|
+
class PublicationDirectoryChangedError < TranscriptionRuntimeError; end
|
|
18
|
+
class PublicationEntryError < TranscriptionRuntimeError; end
|
|
19
|
+
private_constant :PublicationDirectoryChangedError, :PublicationEntryError
|
|
19
20
|
|
|
20
21
|
DirectoryBinding = Data.define(
|
|
21
22
|
:access_path,
|
|
@@ -49,10 +50,10 @@ module Cohere
|
|
|
49
50
|
stat.dev == device && stat.ino == inode
|
|
50
51
|
return self if valid
|
|
51
52
|
|
|
52
|
-
raise
|
|
53
|
+
raise PublicationDirectoryChangedError,
|
|
53
54
|
"Publication parent changed after planning: #{access_path}"
|
|
54
55
|
rescue SystemCallError, ArgumentError => e
|
|
55
|
-
raise
|
|
56
|
+
raise PublicationDirectoryChangedError,
|
|
56
57
|
"Publication parent changed after planning: #{access_path} (#{e.message})"
|
|
57
58
|
end
|
|
58
59
|
end
|
|
@@ -88,7 +89,7 @@ module Cohere
|
|
|
88
89
|
handle.close_on_exec = true
|
|
89
90
|
stat = handle.stat
|
|
90
91
|
unless stat.directory? && stat.dev == binding.device && stat.ino == binding.inode
|
|
91
|
-
raise
|
|
92
|
+
raise PublicationDirectoryChangedError,
|
|
92
93
|
"Publication parent changed while it was being opened: #{binding.access_path}"
|
|
93
94
|
end
|
|
94
95
|
guards.each(&:verify!)
|
|
@@ -98,6 +99,7 @@ module Cohere
|
|
|
98
99
|
rescue Interrupt, SystemExit
|
|
99
100
|
raise
|
|
100
101
|
rescue SystemCallError, ArgumentError => e
|
|
102
|
+
guards&.each(&:verify!)
|
|
101
103
|
raise TranscriptionRuntimeError,
|
|
102
104
|
"Cannot open planned publication parent #{binding.access_path}: #{e.message}"
|
|
103
105
|
ensure
|
|
@@ -169,12 +171,12 @@ module Cohere
|
|
|
169
171
|
handle = open_entry(name, flags, 0, mode: writable ? "r+b" : "rb")
|
|
170
172
|
unless handle.stat.file?
|
|
171
173
|
handle.close
|
|
172
|
-
raise
|
|
174
|
+
raise PublicationEntryError,
|
|
173
175
|
"Publication entry is not a regular file: #{display_path(name)}"
|
|
174
176
|
end
|
|
175
177
|
handle
|
|
176
178
|
rescue Errno::ELOOP, Errno::EISDIR, Errno::ENXIO => e
|
|
177
|
-
raise
|
|
179
|
+
raise PublicationEntryError,
|
|
178
180
|
"Publication entry is not a regular file: #{display_path(name)}",
|
|
179
181
|
cause: e
|
|
180
182
|
end
|
|
@@ -198,7 +200,7 @@ module Cohere
|
|
|
198
200
|
end
|
|
199
201
|
current = handle.stat
|
|
200
202
|
current.dev == expected_stat.dev && current.ino == expected_stat.ino
|
|
201
|
-
rescue Errno::ENOENT
|
|
203
|
+
rescue Errno::ENOENT, PublicationEntryError
|
|
202
204
|
false
|
|
203
205
|
ensure
|
|
204
206
|
handle&.close
|
|
@@ -263,7 +265,7 @@ module Cohere
|
|
|
263
265
|
def verify!
|
|
264
266
|
stat = @handle.stat
|
|
265
267
|
unless stat.directory? && stat.dev == binding.device && stat.ino == binding.inode
|
|
266
|
-
raise
|
|
268
|
+
raise PublicationDirectoryChangedError,
|
|
267
269
|
"Retained publication parent changed identity: #{binding.access_path}"
|
|
268
270
|
end
|
|
269
271
|
@guards.each(&:verify!)
|
|
@@ -278,10 +280,12 @@ module Cohere
|
|
|
278
280
|
end
|
|
279
281
|
|
|
280
282
|
def close
|
|
281
|
-
|
|
283
|
+
Thread.handle_interrupt(Object => :never) do
|
|
284
|
+
return if @closed
|
|
282
285
|
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
@handle.close
|
|
287
|
+
@closed = true
|
|
288
|
+
end
|
|
285
289
|
nil
|
|
286
290
|
end
|
|
287
291
|
|
|
@@ -367,7 +371,47 @@ module Cohere
|
|
|
367
371
|
|
|
368
372
|
module_function
|
|
369
373
|
|
|
370
|
-
def
|
|
374
|
+
def finish_atomic_recovery!(subject:, completed:, primary_error:, recovery_errors:,
|
|
375
|
+
retained_backups:)
|
|
376
|
+
return if recovery_errors.empty?
|
|
377
|
+
# Raising from an ensure during Thread#kill replaces termination with a
|
|
378
|
+
# catchable exception, so cleanup remains best effort while aborting.
|
|
379
|
+
return if Thread.current.status == "aborting"
|
|
380
|
+
|
|
381
|
+
outcome = if completed
|
|
382
|
+
"completed but cleanup was incomplete"
|
|
383
|
+
elsif primary_error
|
|
384
|
+
"failed and recovery was incomplete"
|
|
385
|
+
else
|
|
386
|
+
"did not complete and recovery was incomplete"
|
|
387
|
+
end
|
|
388
|
+
detail = recovery_errors.join("; ")
|
|
389
|
+
retained = retained_backups.compact.map(&:to_s).uniq.sort
|
|
390
|
+
retained_detail = if retained.empty?
|
|
391
|
+
""
|
|
392
|
+
elsif retained.one?
|
|
393
|
+
"; preserved backup: #{retained.first}"
|
|
394
|
+
else
|
|
395
|
+
"; preserved backups: #{retained}"
|
|
396
|
+
end
|
|
397
|
+
raise TranscriptionRuntimeError,
|
|
398
|
+
"#{subject} #{outcome} (#{detail})#{retained_detail}",
|
|
399
|
+
cause: primary_error
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def close_atomic_resources(resources, recovery_errors, label:)
|
|
403
|
+
failures = []
|
|
404
|
+
Array(resources).compact.uniq.each do |resource|
|
|
405
|
+
resource.close unless resource.respond_to?(:closed?) && resource.closed?
|
|
406
|
+
rescue SystemCallError, IOError, TranscriptionRuntimeError => e
|
|
407
|
+
recovery_errors << "close #{label.call(resource)}: #{e.message}"
|
|
408
|
+
failures << e
|
|
409
|
+
end
|
|
410
|
+
failures
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def with_bound_parent(path, directory_binding: nil, guard_bindings: nil,
|
|
414
|
+
recovery_errors: nil)
|
|
371
415
|
path = Pathname(path).expand_path
|
|
372
416
|
directory_binding ||= DirectoryBinding.capture(path.dirname)
|
|
373
417
|
unless [directory_binding.access_path, directory_binding.canonical_path].include?(path.dirname)
|
|
@@ -380,9 +424,28 @@ module Cohere
|
|
|
380
424
|
Thread.handle_interrupt(DEFERRED_PUBLICATION_EXCEPTIONS) do
|
|
381
425
|
bound = BoundDirectory.open(directory_binding, guards: guards.uniq)
|
|
382
426
|
end
|
|
383
|
-
|
|
427
|
+
completed = false
|
|
428
|
+
primary_error = nil
|
|
429
|
+
begin
|
|
430
|
+
result = yield bound, path.basename.to_s
|
|
431
|
+
completed = true
|
|
432
|
+
result
|
|
433
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- preserve the active outcome through close
|
|
434
|
+
primary_error = e
|
|
435
|
+
raise
|
|
436
|
+
end
|
|
384
437
|
ensure
|
|
385
|
-
|
|
438
|
+
close_errors = recovery_errors || []
|
|
439
|
+
close_atomic_resources([bound], close_errors, label: ->(resource) { resource.binding.canonical_path })
|
|
440
|
+
unless recovery_errors
|
|
441
|
+
finish_atomic_recovery!(
|
|
442
|
+
subject: "Publication directory operation",
|
|
443
|
+
completed: completed,
|
|
444
|
+
primary_error: primary_error,
|
|
445
|
+
recovery_errors: close_errors,
|
|
446
|
+
retained_backups: []
|
|
447
|
+
)
|
|
448
|
+
end
|
|
386
449
|
end
|
|
387
450
|
|
|
388
451
|
def ensure_bound_directory(path, root_binding: nil, operation_hook: nil)
|
|
@@ -511,121 +574,158 @@ module Cohere
|
|
|
511
574
|
end
|
|
512
575
|
|
|
513
576
|
def write_state_atomic(path, payload, source_snapshot:, directory_binding: nil,
|
|
514
|
-
guard_bindings: nil, operation_hook: nil, rename: nil
|
|
577
|
+
guard_bindings: nil, operation_hook: nil, rename: nil,
|
|
578
|
+
commit_guard: nil)
|
|
515
579
|
path = Pathname(path).expand_path
|
|
516
580
|
directory_binding ||= ensure_bound_directory(path.dirname)
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
primary_failed = false
|
|
531
|
-
begin
|
|
532
|
-
mode = bound_state_mode(bound, basename)
|
|
533
|
-
Thread.handle_interrupt(DEFERRED_PUBLICATION_EXCEPTIONS) do
|
|
534
|
-
temporary_name, temporary = bound.create_temporary(basename, ".tmp")
|
|
535
|
-
end
|
|
536
|
-
temporary.chmod(mode)
|
|
537
|
-
operation_hook&.call(:before_stage_write, path)
|
|
538
|
-
temporary.write(JSON.pretty_generate(envelope(payload)))
|
|
539
|
-
temporary.write("\n")
|
|
540
|
-
temporary.flush
|
|
541
|
-
temporary.fsync
|
|
542
|
-
temporary.close
|
|
581
|
+
recovery_errors = []
|
|
582
|
+
retained_backups = []
|
|
583
|
+
completed = false
|
|
584
|
+
primary_error = nil
|
|
585
|
+
begin
|
|
586
|
+
with_bound_parent(
|
|
587
|
+
path,
|
|
588
|
+
directory_binding: directory_binding,
|
|
589
|
+
guard_bindings: guard_bindings,
|
|
590
|
+
recovery_errors: recovery_errors
|
|
591
|
+
) do |bound, basename|
|
|
592
|
+
operation_hook&.call(:directory_opened, path)
|
|
593
|
+
temporary_name = nil
|
|
543
594
|
temporary = nil
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
595
|
+
backup_name = nil
|
|
596
|
+
backup = nil
|
|
597
|
+
source = nil
|
|
598
|
+
committed = false
|
|
599
|
+
begin
|
|
600
|
+
mode = bound_state_mode(bound, basename)
|
|
601
|
+
Thread.handle_interrupt(DEFERRED_PUBLICATION_EXCEPTIONS) do
|
|
602
|
+
temporary_name, temporary = bound.create_temporary(basename, ".tmp")
|
|
552
603
|
end
|
|
553
|
-
|
|
554
|
-
|
|
604
|
+
temporary.chmod(mode)
|
|
605
|
+
operation_hook&.call(:before_stage_write, path)
|
|
606
|
+
temporary.write(JSON.pretty_generate(envelope(payload)))
|
|
607
|
+
temporary.write("\n")
|
|
608
|
+
temporary.flush
|
|
609
|
+
temporary.fsync
|
|
610
|
+
temporary.close
|
|
611
|
+
temporary = nil
|
|
612
|
+
bound.verify!
|
|
613
|
+
|
|
614
|
+
operation_hook&.call(:before_backup_open, path)
|
|
555
615
|
Thread.handle_interrupt(DEFERRED_PUBLICATION_EXCEPTIONS) do
|
|
556
|
-
|
|
616
|
+
source = begin
|
|
617
|
+
bound.open_regular(basename)
|
|
618
|
+
rescue Errno::ENOENT
|
|
619
|
+
nil
|
|
620
|
+
end
|
|
557
621
|
end
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
622
|
+
if source
|
|
623
|
+
Thread.handle_interrupt(DEFERRED_PUBLICATION_EXCEPTIONS) do
|
|
624
|
+
backup_name, backup = bound.create_temporary(basename, ".bak")
|
|
625
|
+
end
|
|
626
|
+
backup_copy_error = nil
|
|
627
|
+
begin
|
|
628
|
+
backup.chmod(source.stat.mode & 0o7777)
|
|
629
|
+
operation_hook&.call(:before_backup_copy, path)
|
|
630
|
+
IO.copy_stream(source, backup)
|
|
631
|
+
backup.flush
|
|
632
|
+
backup.fsync
|
|
633
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- retain the copy failure through close
|
|
634
|
+
backup_copy_error = e
|
|
635
|
+
raise
|
|
636
|
+
ensure
|
|
637
|
+
close_failures = close_atomic_resources(
|
|
638
|
+
[source, backup],
|
|
639
|
+
recovery_errors,
|
|
640
|
+
label: ->(handle) { handle.respond_to?(:path) ? handle.path : path }
|
|
641
|
+
)
|
|
642
|
+
raise close_failures.first if close_failures.any? && backup_copy_error.nil?
|
|
643
|
+
end
|
|
567
644
|
end
|
|
568
|
-
end
|
|
569
645
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
646
|
+
ensure_source_unchanged!(source_snapshot)
|
|
647
|
+
bound.verify!
|
|
648
|
+
operation_hook&.call(:before_rename, path)
|
|
649
|
+
Thread.handle_interrupt(DEFERRED_PUBLICATION_EXCEPTIONS) do
|
|
650
|
+
commit_guard&.call
|
|
651
|
+
if rename
|
|
652
|
+
committed = true
|
|
653
|
+
rename.call(bound, temporary_name, basename, :commit)
|
|
654
|
+
else
|
|
655
|
+
bound.rename(temporary_name, basename)
|
|
656
|
+
committed = true
|
|
657
|
+
end
|
|
658
|
+
operation_hook&.call(:after_rename, path)
|
|
659
|
+
bound.verify!
|
|
660
|
+
bound.fsync
|
|
661
|
+
commit_guard&.call
|
|
662
|
+
completed = true
|
|
580
663
|
end
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
664
|
+
path
|
|
665
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- rollback includes interrupts
|
|
666
|
+
primary_error = e
|
|
667
|
+
raise
|
|
668
|
+
ensure
|
|
669
|
+
Thread.handle_interrupt(DEFERRED_PUBLICATION_EXCEPTIONS) do
|
|
670
|
+
if !completed && committed
|
|
671
|
+
begin
|
|
672
|
+
bound.unlink(basename, missing_ok: true)
|
|
673
|
+
if backup_name
|
|
674
|
+
if rename
|
|
675
|
+
rename.call(bound, backup_name, basename, :rollback)
|
|
676
|
+
else
|
|
677
|
+
bound.rename(backup_name, basename)
|
|
678
|
+
end
|
|
679
|
+
backup_name = nil
|
|
597
680
|
end
|
|
681
|
+
rescue StandardError => e
|
|
682
|
+
retained_backups << bound.display_path(backup_name) if backup_name
|
|
683
|
+
recovery_errors << "#{path}: #{e.message}"
|
|
684
|
+
end
|
|
685
|
+
begin
|
|
686
|
+
bound.fsync
|
|
687
|
+
rescue SystemCallError, TranscriptionRuntimeError => e
|
|
688
|
+
recovery_errors << "directory sync: #{e.message}"
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
close_atomic_resources(
|
|
693
|
+
[source, backup, temporary],
|
|
694
|
+
recovery_errors,
|
|
695
|
+
label: ->(handle) { handle.respond_to?(:path) ? handle.path : path }
|
|
696
|
+
)
|
|
697
|
+
if temporary_name
|
|
698
|
+
begin
|
|
699
|
+
bound.unlink(temporary_name, missing_ok: true)
|
|
700
|
+
rescue SystemCallError, TranscriptionRuntimeError => e
|
|
701
|
+
temporary_path = bound.display_path(temporary_name)
|
|
702
|
+
recovery_errors << "cleanup #{temporary_path}: #{e.message}"
|
|
703
|
+
end
|
|
704
|
+
end
|
|
705
|
+
if backup_name && !retained_backups.include?(bound.display_path(backup_name))
|
|
706
|
+
begin
|
|
707
|
+
bound.unlink(backup_name, missing_ok: true)
|
|
708
|
+
backup_name = nil
|
|
709
|
+
rescue SystemCallError, TranscriptionRuntimeError => e
|
|
710
|
+
backup_path = bound.display_path(backup_name)
|
|
711
|
+
retained_backups << backup_path
|
|
712
|
+
recovery_errors << "cleanup #{backup_path}: #{e.message}"
|
|
598
713
|
end
|
|
599
|
-
backup_name = nil
|
|
600
714
|
end
|
|
601
|
-
bound.fsync
|
|
602
|
-
rescue SystemCallError, TranscriptionRuntimeError => rollback_error
|
|
603
|
-
retained = backup_name ? bound.display_path(backup_name) : nil
|
|
604
|
-
preserved_backup = !backup_name.nil?
|
|
605
|
-
raise TranscriptionRuntimeError,
|
|
606
|
-
"State commit failed and rollback was incomplete (#{rollback_error.message}); " \
|
|
607
|
-
"preserved backup: #{retained}",
|
|
608
|
-
cause: e
|
|
609
|
-
end
|
|
610
|
-
end
|
|
611
|
-
raise e
|
|
612
|
-
ensure
|
|
613
|
-
cleanup_failure = nil
|
|
614
|
-
cleanup_operations = [
|
|
615
|
-
-> { temporary&.close unless temporary&.closed? },
|
|
616
|
-
-> { backup&.close unless backup&.closed? },
|
|
617
|
-
-> { bound.unlink(temporary_name, missing_ok: true) if temporary_name },
|
|
618
|
-
lambda do
|
|
619
|
-
bound.unlink(backup_name, missing_ok: true) if backup_name && !preserved_backup
|
|
620
715
|
end
|
|
621
|
-
]
|
|
622
|
-
cleanup_operations.each do |operation|
|
|
623
|
-
operation.call
|
|
624
|
-
rescue SystemCallError, IOError => e
|
|
625
|
-
cleanup_failure ||= e
|
|
626
716
|
end
|
|
627
|
-
raise cleanup_failure if cleanup_failure && !primary_failed
|
|
628
717
|
end
|
|
718
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- retain failures outside the bound block
|
|
719
|
+
primary_error ||= e
|
|
720
|
+
raise
|
|
721
|
+
ensure
|
|
722
|
+
finish_atomic_recovery!(
|
|
723
|
+
subject: "State commit",
|
|
724
|
+
completed: completed,
|
|
725
|
+
primary_error: primary_error,
|
|
726
|
+
recovery_errors: recovery_errors,
|
|
727
|
+
retained_backups: retained_backups
|
|
728
|
+
)
|
|
629
729
|
end
|
|
630
730
|
end
|
|
631
731
|
|