From 351d87b14f853114282131ba03869574047c557d Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Fri, 26 May 2023 17:40:34 -0400 Subject: [PATCH 01/27] added sounddevice to optionally record narration --- openadapt/record.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/openadapt/record.py b/openadapt/record.py index 3550e68a9..6e6e9a5ed 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -27,6 +27,9 @@ from openadapt import config, crud, utils, window +import sounddevice +import soundfile +import whisper EVENT_TYPES = ("screen", "action", "window") LOG_LEVEL = "INFO" @@ -544,12 +547,15 @@ def read_mouse_events( def record( task_description: str, + enable_audio: bool = False ): """ - Record Screenshots/ActionEvents/WindowEvents. + Record Screenshots/ActionEvents/WindowEvents. Optionally record audio narration from the user + describing what tasks are being done. Args: task_description: a text description of the task that will be recorded + enable_audio: a flag to enable or disable audio recording (default: False) """ utils.configure_logging(logger, LOG_LEVEL) @@ -646,12 +652,37 @@ def record( # TODO: discard events until everything is ready + audio_frames = [] # to store audio frames + + if enable_audio: + def audio_callback(indata, frames, time, status): + # called whenever there is new audio frames + audio_frames.append(indata.copy()) + + # open InputStream and start recording while ActionEvents are recorded + audio_stream = sounddevice.InputStream(callback=audio_callback) + audio_stream.start() + try: while True: time.sleep(1) except KeyboardInterrupt: terminate_event.set() + if enable_audio: + # stop the recording and close the InputStream + audio_stream.stop() + audio_stream.close() + + if len(audio_frames) > 0: + # write to a WAV file + # TODO: change name + wav_filename = "audio.wav" + soundfile.write(wav_filename, audio_frames, samplerate=audio_stream.samplerate) + + # Convert audio to text using OpenAI's Whisper + + logger.info(f"joining...") keyboard_event_reader.join() mouse_event_reader.join() From f19a84ab72381bbcd6cea800fd68d6bc6a321430 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Fri, 26 May 2023 20:18:10 -0400 Subject: [PATCH 02/27] added sounddevice to optionally record narration and initial whisper conversion --- openadapt/record.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openadapt/record.py b/openadapt/record.py index 6e6e9a5ed..553eb3071 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -682,6 +682,8 @@ def audio_callback(indata, frames, time, status): # Convert audio to text using OpenAI's Whisper + model = whisper.load_model("base") + result = model.transcribe("audio.wav") logger.info(f"joining...") keyboard_event_reader.join() From e143767082575e592f2ba33b09062b69cfb3afc2 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Mon, 29 May 2023 11:02:51 -0400 Subject: [PATCH 03/27] updated requirements for audio narration --- requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/requirements.txt b/requirements.txt index 007af87d4..bfd391628 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,3 +26,8 @@ tiktoken==0.4.0 torch==2.0.0 tqdm==4.64.0 transformers==4.28.1 + +numpy~=1.24.3 +sounddevice~=0.4.6 +soundfile~=0.12.1 +git+https://github.com/openai/whisper.git From 6f07b93c9cb705341920435124572e50014d42a1 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Wed, 31 May 2023 17:40:49 -0400 Subject: [PATCH 04/27] small changes --- openadapt/record.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openadapt/record.py b/openadapt/record.py index 553eb3071..d9e0c2d5f 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -661,6 +661,7 @@ def audio_callback(indata, frames, time, status): # open InputStream and start recording while ActionEvents are recorded audio_stream = sounddevice.InputStream(callback=audio_callback) + logger.info("Audio recording started.") audio_stream.start() try: @@ -678,12 +679,12 @@ def audio_callback(indata, frames, time, status): # write to a WAV file # TODO: change name wav_filename = "audio.wav" - soundfile.write(wav_filename, audio_frames, samplerate=audio_stream.samplerate) + soundfile.write(wav_filename, audio_frames, samplerate=int(audio_stream.samplerate)) # Convert audio to text using OpenAI's Whisper model = whisper.load_model("base") - result = model.transcribe("audio.wav") + result_text = model.transcribe("audio.wav") logger.info(f"joining...") keyboard_event_reader.join() From d3ef09a592f0fd949928208b7240744c809fa877 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Wed, 31 May 2023 17:55:01 -0400 Subject: [PATCH 05/27] fixed issue with created audio file being really slow --- openadapt/record.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openadapt/record.py b/openadapt/record.py index d9e0c2d5f..01174df98 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -30,6 +30,7 @@ import sounddevice import soundfile import whisper +import numpy as np EVENT_TYPES = ("screen", "action", "window") LOG_LEVEL = "INFO" @@ -676,10 +677,11 @@ def audio_callback(indata, frames, time, status): audio_stream.close() if len(audio_frames) > 0: + concatenated_audio = np.concatenate(audio_frames, axis=0) # write to a WAV file # TODO: change name wav_filename = "audio.wav" - soundfile.write(wav_filename, audio_frames, samplerate=int(audio_stream.samplerate)) + soundfile.write(wav_filename, concatenated_audio, samplerate=int(audio_stream.samplerate)) # Convert audio to text using OpenAI's Whisper From 9e86193807263ae5dd93d2dc11471afa9171d5b2 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Wed, 31 May 2023 19:29:33 -0400 Subject: [PATCH 06/27] updated to save audio data and transcribed text in database --- openadapt/crud.py | 22 +++++++++++++++++++++- openadapt/models.py | 26 ++++++++++++++++++++++++++ openadapt/record.py | 21 ++++++++++++++++----- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/openadapt/crud.py b/openadapt/crud.py index 36422b565..3355f05f3 100644 --- a/openadapt/crud.py +++ b/openadapt/crud.py @@ -2,7 +2,7 @@ import sqlalchemy as sa from openadapt.db import Session -from openadapt.models import ActionEvent, Screenshot, Recording, WindowEvent +from openadapt.models import ActionEvent, Screenshot, Recording, WindowEvent, AudioFile,AudioInfo BATCH_SIZE = 1 @@ -69,6 +69,26 @@ def insert_window_event(recording_timestamp, event_timestamp, event_data): _insert(event_data, WindowEvent, window_events) +def insert_audio_file(data, filename): + audio_data = AudioFile(data=data.tobytes(), filename=filename) + db.add(audio_data) + db.commit() + return audio_data + + +def insert_audio_info(transcribed_text, recording_timestamp, sample_rate, audio_file): + """Create an AudioInfo entry in the database.""" + audio_info = AudioInfo( + transcribed_text=transcribed_text, + recording_timestamp=recording_timestamp, + sample_rate=sample_rate, + file=audio_file + ) + db.add(audio_info) + db.commit() + return audio_info + + def insert_recording(recording_data): db_obj = Recording(**recording_data) db.add(db_obj) diff --git a/openadapt/models.py b/openadapt/models.py index 06236c8a7..d3c1f9085 100644 --- a/openadapt/models.py +++ b/openadapt/models.py @@ -48,6 +48,8 @@ class Recording(db.Base): order_by="WindowEvent.timestamp", ) + audio_info = sa.orm.relationship("AudioInfo", back_populates="recording") + _processed_action_events = None @property @@ -295,3 +297,27 @@ class WindowEvent(db.Base): @classmethod def get_active_window_event(cls): return WindowEvent(**window.get_active_window_data()) + + +class AudioInfo(db.Base): + __tablename__ = "audio_info" + + id = sa.Column(sa.Integer, primary_key=True) + transcribed_text = sa.column(sa.String) + recording_timestamp = sa.Column(sa.ForeignKey("recording.timestamp")) + file_id = sa.Column(sa.ForeignKey("audio_file.id")) + sample_rate = sa.Column(sa.Integer) + + recording = sa.orm.relationship("Recording", back_populates="audio_info") + file = sa.orm.relationship("AudioFile", back_populates="audio_info") + + + +class AudioFile(db.Base): + __tablename__ = "audio_file" + + id = sa.Column(sa.Integer, primary_key=True) + filename = sa.Column(sa.String) + data = sa.Column(sa.LargeBinary) + + audio_info = sa.orm.relationship("AudioInfo", back_populates="file") diff --git a/openadapt/record.py b/openadapt/record.py index 01174df98..25db26279 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -678,15 +678,26 @@ def audio_callback(indata, frames, time, status): if len(audio_frames) > 0: concatenated_audio = np.concatenate(audio_frames, axis=0) - # write to a WAV file - # TODO: change name - wav_filename = "audio.wav" - soundfile.write(wav_filename, concatenated_audio, samplerate=int(audio_stream.samplerate)) # Convert audio to text using OpenAI's Whisper model = whisper.load_model("base") - result_text = model.transcribe("audio.wav") + result_info = model.transcribe(concatenated_audio, word_timestamps=True) + logger.info(f"The narrated text is: {result_info['text']}") + # word timestamps found at result_info['words'] + + # convert to bytes + audio_data_bytes = concatenated_audio.tobytes() + # Save audio frames to the database + # TODO: change name + audio_file = crud.insert_audio_file(audio_data_bytes, "audio.flac") + + # Create AudioInfo entry + audio_info = crud.insert_audio_info(result_info['text'], recording_timestamp, + int(audio_stream.samplerate), audio_file) + + # soundfile.write("audio.flac", concatenated_audio, + # samplerate=int(audio_stream.samplerate)) logger.info(f"joining...") keyboard_event_reader.join() From ce84a1bb820ac681c89899c00bfa8c429cb8d0de Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Wed, 31 May 2023 19:38:08 -0400 Subject: [PATCH 07/27] new alembic migration --- ...add_new_tables_for_audio_info_and_files.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py diff --git a/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py b/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py new file mode 100644 index 000000000..58a00cdbf --- /dev/null +++ b/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py @@ -0,0 +1,43 @@ +"""Add new tables for audio info and files + +Revision ID: 3d3239ae4849 +Revises: 104d4a614d95 +Create Date: 2023-05-31 19:36:46.269697 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3d3239ae4849' +down_revision = '104d4a614d95' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('audio_file', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('filename', sa.String(), nullable=True), + sa.Column('data', sa.LargeBinary(), nullable=True), + sa.PrimaryKeyConstraint('id', name=op.f('pk_audio_file')) + ) + op.create_table('audio_info', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('recording_timestamp', openadapt.models.ForceFloat(precision=10, scale=2, asdecimal=False), nullable=True), + sa.Column('file_id', sa.Integer(), nullable=True), + sa.Column('sample_rate', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['file_id'], ['audio_file.id'], name=op.f('fk_audio_info_file_id_audio_file')), + sa.ForeignKeyConstraint(['recording_timestamp'], ['recording.timestamp'], name=op.f('fk_audio_info_recording_timestamp_recording')), + sa.PrimaryKeyConstraint('id', name=op.f('pk_audio_info')) + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('audio_info') + op.drop_table('audio_file') + # ### end Alembic commands ### From 5c584b2fb48d522372b194e1b1faf671ebb8178c Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 1 Jun 2023 15:16:37 -0400 Subject: [PATCH 08/27] edited audio tables --- .../3d3239ae4849_add_new_tables_for_audio_info_and_files.py | 4 ++-- openadapt/crud.py | 4 ++-- openadapt/models.py | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py b/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py index 58a00cdbf..97b70e688 100644 --- a/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py +++ b/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py @@ -20,13 +20,13 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table('audio_file', sa.Column('id', sa.Integer(), nullable=False), - sa.Column('filename', sa.String(), nullable=True), sa.Column('data', sa.LargeBinary(), nullable=True), sa.PrimaryKeyConstraint('id', name=op.f('pk_audio_file')) ) op.create_table('audio_info', sa.Column('id', sa.Integer(), nullable=False), - sa.Column('recording_timestamp', openadapt.models.ForceFloat(precision=10, scale=2, asdecimal=False), nullable=True), + sa.Column('transcribed_text', sa.String, nullable=True), + sa.Column('recording_timestamp', sa.Integer(), nullable=True), sa.Column('file_id', sa.Integer(), nullable=True), sa.Column('sample_rate', sa.Integer(), nullable=True), sa.ForeignKeyConstraint(['file_id'], ['audio_file.id'], name=op.f('fk_audio_info_file_id_audio_file')), diff --git a/openadapt/crud.py b/openadapt/crud.py index c396f22c6..a6dc64cfa 100644 --- a/openadapt/crud.py +++ b/openadapt/crud.py @@ -103,8 +103,8 @@ def get_perf_stats(recording_timestamp): .all() ) -def insert_audio_file(data, filename): - audio_data = AudioFile(data=data.tobytes(), filename=filename) +def insert_audio_file(data): + audio_data = AudioFile(data=data) db.add(audio_data) db.commit() return audio_data diff --git a/openadapt/models.py b/openadapt/models.py index 84460cc4a..93e1d2451 100644 --- a/openadapt/models.py +++ b/openadapt/models.py @@ -303,7 +303,7 @@ class AudioInfo(db.Base): __tablename__ = "audio_info" id = sa.Column(sa.Integer, primary_key=True) - transcribed_text = sa.column(sa.String) + transcribed_text = sa.Column(sa.String) recording_timestamp = sa.Column(sa.ForeignKey("recording.timestamp")) file_id = sa.Column(sa.ForeignKey("audio_file.id")) sample_rate = sa.Column(sa.Integer) @@ -317,7 +317,6 @@ class AudioFile(db.Base): __tablename__ = "audio_file" id = sa.Column(sa.Integer, primary_key=True) - filename = sa.Column(sa.String) data = sa.Column(sa.LargeBinary) audio_info = sa.orm.relationship("AudioInfo", back_populates="file") From 802c8a22a93bd4ee027b3cf4a472e39f628d3b3e Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 1 Jun 2023 15:16:59 -0400 Subject: [PATCH 09/27] convert audio array to required format for whisper --- openadapt/record.py | 180 ++++++++++++++++++++++---------------------- 1 file changed, 89 insertions(+), 91 deletions(-) diff --git a/openadapt/record.py b/openadapt/record.py index 2d4cae615..ef55d09b6 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -38,7 +38,6 @@ } PLOT_PERFORMANCE = False - Event = namedtuple("Event", ("timestamp", "type", "data")) @@ -50,13 +49,13 @@ def process_event(event, write_q, write_fn, recording_timestamp, perf_q): def process_events( - event_q: queue.Queue, - screen_write_q: multiprocessing.Queue, - action_write_q: multiprocessing.Queue, - window_write_q: multiprocessing.Queue, - perf_q: multiprocessing.Queue, - recording_timestamp: float, - terminate_event: multiprocessing.Event, + event_q: queue.Queue, + screen_write_q: multiprocessing.Queue, + action_write_q: multiprocessing.Queue, + window_write_q: multiprocessing.Queue, + perf_q: multiprocessing.Queue, + recording_timestamp: float, + terminate_event: multiprocessing.Event, ): """ Process events from event queue and write them to respective write queues. @@ -131,9 +130,9 @@ def process_events( def write_action_event( - recording_timestamp: float, - event: Event, - perf_q: multiprocessing.Queue, + recording_timestamp: float, + event: Event, + perf_q: multiprocessing.Queue, ): """ Write an action event to the database and update the performance queue. @@ -150,9 +149,9 @@ def write_action_event( def write_screen_event( - recording_timestamp: float, - event: Event, - perf_q: multiprocessing.Queue, + recording_timestamp: float, + event: Event, + perf_q: multiprocessing.Queue, ): """ Write a screen event to the database and update the performance queue. @@ -172,9 +171,9 @@ def write_screen_event( def write_window_event( - recording_timestamp: float, - event: Event, - perf_q: multiprocessing.Queue, + recording_timestamp: float, + event: Event, + perf_q: multiprocessing.Queue, ): """ Write a window event to the database and update the performance queue. @@ -191,12 +190,12 @@ def write_window_event( def write_events( - event_type: str, - write_fn: Callable, - write_q: multiprocessing.Queue, - perf_q: multiprocessing.Queue, - recording_timestamp: float, - terminate_event: multiprocessing.Event, + event_type: str, + write_fn: Callable, + write_q: multiprocessing.Queue, + perf_q: multiprocessing.Queue, + recording_timestamp: float, + terminate_event: multiprocessing.Event, ): """ Write events of a specific type to the db using the provided write function. @@ -226,8 +225,8 @@ def write_events( def trigger_action_event( - event_q: queue.Queue, - action_event_args: Dict[str, Any], + event_q: queue.Queue, + action_event_args: Dict[str, Any], ) -> None: x = action_event_args.get("mouse_x") y = action_event_args.get("mouse_y") @@ -241,12 +240,11 @@ def trigger_action_event( def on_move( - event_q: queue.Queue, - x: int, - y: int, - injected: bool, + event_q: queue.Queue, + x: int, + y: int, + injected: bool, ) -> None: - logger.debug(f"{x=} {y=} {injected=}") if not injected: trigger_action_event( @@ -260,12 +258,12 @@ def on_move( def on_click( - event_q: queue.Queue, - x: int, - y: int, - button: mouse.Button, - pressed: bool, - injected: bool, + event_q: queue.Queue, + x: int, + y: int, + button: mouse.Button, + pressed: bool, + injected: bool, ) -> None: logger.debug(f"{x=} {y=} {button=} {pressed=} {injected=}") if not injected: @@ -282,12 +280,12 @@ def on_click( def on_scroll( - event_q: queue.Queue, - x: int, - y: int, - dx: int, - dy: int, - injected: bool, + event_q: queue.Queue, + x: int, + y: int, + dx: int, + dy: int, + injected: bool, ) -> None: logger.debug(f"{x=} {y=} {dx=} {dy=} {injected=}") if not injected: @@ -304,10 +302,10 @@ def on_scroll( def handle_key( - event_q: queue.Queue, - event_name: str, - key: keyboard.KeyCode, - canonical_key: keyboard.KeyCode, + event_q: queue.Queue, + event_name: str, + key: keyboard.KeyCode, + canonical_key: keyboard.KeyCode, ) -> None: attr_names = [ "name", @@ -335,9 +333,9 @@ def handle_key( def read_screen_events( - event_q: queue.Queue, - terminate_event: multiprocessing.Event, - recording_timestamp: float, + event_q: queue.Queue, + terminate_event: multiprocessing.Event, + recording_timestamp: float, ) -> None: """ Read screen events and add them to the event queue. @@ -361,9 +359,9 @@ def read_screen_events( def read_window_events( - event_q: queue.Queue, - terminate_event: multiprocessing.Event, - recording_timestamp: float, + event_q: queue.Queue, + terminate_event: multiprocessing.Event, + recording_timestamp: float, ) -> None: """ Read window events and add them to the event queue. @@ -383,8 +381,8 @@ def read_window_events( if not window_data: continue if ( - window_data["title"] != prev_window_data.get("title") or - window_data["window_id"] != prev_window_data.get("window_id") + window_data["title"] != prev_window_data.get("title") or + window_data["window_id"] != prev_window_data.get("window_id") ): # TODO: fix exception sometimes triggered by the next line on win32: # File "\Python39\lib\threading.py" line 917, in run @@ -406,10 +404,10 @@ def read_window_events( prev_window_data = window_data -def performance_stats_writer ( - perf_q: multiprocessing.Queue, - recording_timestamp: float, - terminate_event: multiprocessing.Event, +def performance_stats_writer( + perf_q: multiprocessing.Queue, + recording_timestamp: float, + terminate_event: multiprocessing.Event, ): """ Write performance stats to the db. @@ -438,7 +436,7 @@ def performance_stats_writer ( def create_recording( - task_description: str, + task_description: str, ) -> Dict[str, Any]: """ Create a new recording entry in the database. @@ -471,26 +469,22 @@ def create_recording( def read_keyboard_events( - event_q: queue.Queue, - terminate_event: multiprocessing.Event, - recording_timestamp: float, + event_q: queue.Queue, + terminate_event: multiprocessing.Event, + recording_timestamp: float, ) -> None: - - def on_press(event_q, key, injected): canonical_key = keyboard_listener.canonical(key) logger.debug(f"{key=} {injected=} {canonical_key=}") if not injected: handle_key(event_q, "press", key, canonical_key) - def on_release(event_q, key, injected): canonical_key = keyboard_listener.canonical(key) logger.debug(f"{key=} {injected=} {canonical_key=}") if not injected: handle_key(event_q, "release", key, canonical_key) - utils.set_start_time(recording_timestamp) keyboard_listener = keyboard.Listener( on_press=partial(on_press, event_q), @@ -502,9 +496,9 @@ def on_release(event_q, key, injected): def read_mouse_events( - event_q: queue.Queue, - terminate_event: multiprocessing.Event, - recording_timestamp: float, + event_q: queue.Queue, + terminate_event: multiprocessing.Event, + recording_timestamp: float, ) -> None: utils.set_start_time(recording_timestamp) mouse_listener = mouse.Listener( @@ -518,8 +512,8 @@ def read_mouse_events( def record( - task_description: str, - enable_audio: bool = False + task_description: str, + enable_audio: bool = False ): """ Record Screenshots/ActionEvents/WindowEvents. Optionally record audio narration from the user @@ -643,7 +637,7 @@ def audio_callback(indata, frames, time, status): audio_frames.append(indata.copy()) # open InputStream and start recording while ActionEvents are recorded - audio_stream = sounddevice.InputStream(callback=audio_callback) + audio_stream = sounddevice.InputStream(callback=audio_callback, samplerate=16000, channels=1) logger.info("Audio recording started.") audio_stream.start() @@ -658,28 +652,22 @@ def audio_callback(indata, frames, time, status): audio_stream.stop() audio_stream.close() - if len(audio_frames) > 0: - concatenated_audio = np.concatenate(audio_frames, axis=0) + # Concatenate into one Numpy array + concatenated_audio = np.concatenate(audio_frames, axis=0) + # convert concatenated_audio to format expected by whisper + converted_audio = concatenated_audio.flatten().astype(np.float32) - # Convert audio to text using OpenAI's Whisper + # convert to bytes to save to database + audio_data_bytes = converted_audio.tobytes() - model = whisper.load_model("base") - result_info = model.transcribe(concatenated_audio, word_timestamps=True) - logger.info(f"The narrated text is: {result_info['text']}") - # word timestamps found at result_info['words'] + # convert back to np array with np.frombuffer(audio_data_bytes, np.float32) - # convert to bytes - audio_data_bytes = concatenated_audio.tobytes() - # Save audio frames to the database - # TODO: change name - audio_file = crud.insert_audio_file(audio_data_bytes, "audio.flac") - - # Create AudioInfo entry - audio_info = crud.insert_audio_info(result_info['text'], recording_timestamp, - int(audio_stream.samplerate), audio_file) - - # soundfile.write("audio.flac", concatenated_audio, - # samplerate=int(audio_stream.samplerate)) + # Convert audio to text using OpenAI's Whisper + logger.info("Transcribing audio...") + model = whisper.load_model("base") + result_info = model.transcribe(converted_audio, word_timestamps=True, fp16=False) + logger.info(f"The narrated text is: {result_info['text']}") + # word timestamps found at result_info['words'] logger.info(f"joining...") keyboard_event_reader.join() @@ -696,7 +684,17 @@ def audio_callback(indata, frames, time, status): if PLOT_PERFORMANCE: utils.plot_performance(recording_timestamp) + if enable_audio: + # Save audio frames to the database + # TODO: change name + audio_file = crud.insert_audio_file(audio_data_bytes) + + # Create AudioInfo entry + audio_info = crud.insert_audio_info(result_info['text'], recording_timestamp, + int(audio_stream.samplerate), audio_file) + logger.info(f"saved {recording_timestamp=}") + if __name__ == "__main__": fire.Fire(record) From aca8cdc4238b38e53bff47230fd2183982d8889c Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 1 Jun 2023 17:14:49 -0400 Subject: [PATCH 10/27] visualize audio info --- openadapt/crud.py | 9 +++++++++ openadapt/models.py | 7 ++----- openadapt/visualize.py | 10 +++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/openadapt/crud.py b/openadapt/crud.py index a6dc64cfa..80cf29dca 100644 --- a/openadapt/crud.py +++ b/openadapt/crud.py @@ -181,3 +181,12 @@ def get_screenshots(recording, precompute_diffs=False): def get_window_events(recording): return _get(WindowEvent, recording.timestamp) + + +def get_audio_info(recording): + return ( + db + .query(AudioInfo) + .filter(AudioInfo.recording_timestamp == recording.timestamp) + .first() + ) diff --git a/openadapt/models.py b/openadapt/models.py index 93e1d2451..7451b3ccf 100644 --- a/openadapt/models.py +++ b/openadapt/models.py @@ -60,7 +60,6 @@ def processed_action_events(self): return self._processed_action_events - class ActionEvent(db.Base): __tablename__ = "action_event" @@ -88,8 +87,8 @@ class ActionEvent(db.Base): children = sa.orm.relationship("ActionEvent") # TODO: replacing the above line with the following two results in an error: # AttributeError: 'list' object has no attribute '_sa_instance_state' - #children = sa.orm.relationship("ActionEvent", remote_side=[id], back_populates="parent") - #parent = sa.orm.relationship("ActionEvent", remote_side=[parent_id], back_populates="children") + # children = sa.orm.relationship("ActionEvent", remote_side=[id], back_populates="parent") + # parent = sa.orm.relationship("ActionEvent", remote_side=[parent_id], back_populates="children") recording = sa.orm.relationship("Recording", back_populates="action_events") screenshot = sa.orm.relationship("Screenshot", back_populates="action_event") @@ -312,7 +311,6 @@ class AudioInfo(db.Base): file = sa.orm.relationship("AudioFile", back_populates="audio_info") - class AudioFile(db.Base): __tablename__ = "audio_file" @@ -331,4 +329,3 @@ class PerformanceStat(db.Base): start_time = sa.Column(sa.Integer) end_time = sa.Column(sa.Integer) window_id = sa.Column(sa.String) - diff --git a/openadapt/visualize.py b/openadapt/visualize.py index 726da763f..6d179e28a 100644 --- a/openadapt/visualize.py +++ b/openadapt/visualize.py @@ -11,6 +11,7 @@ from openadapt.crud import ( get_latest_recording, + get_audio_info ) from openadapt.events import ( get_events, @@ -138,6 +139,8 @@ def main(): recording = get_latest_recording() logger.debug(f"{recording=}") + audio_info = get_audio_info(recording) + meta = {} action_events = get_events(recording, process=PROCESS_EVENTS, meta=meta) event_dicts = rows2dicts(action_events) @@ -159,7 +162,12 @@ def main(): text=f"{dict2html(meta)}", width_policy="max", ), - ) + ), + row( + Div( + text=f"{dict2html(row2dict(audio_info))}", + ), + ), ] logger.info(f"{len(action_events)=}") for idx, action_event in enumerate(action_events): From 42b10070993dab5f62315f2e4cdde643beaac4f3 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 1 Jun 2023 18:28:59 -0400 Subject: [PATCH 11/27] FLAC compression before storing --- openadapt/record.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/openadapt/record.py b/openadapt/record.py index ef55d09b6..eced71e2b 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -28,6 +28,7 @@ import soundfile import whisper import numpy as np +import io EVENT_TYPES = ("screen", "action", "window") LOG_LEVEL = "INFO" @@ -657,11 +658,6 @@ def audio_callback(indata, frames, time, status): # convert concatenated_audio to format expected by whisper converted_audio = concatenated_audio.flatten().astype(np.float32) - # convert to bytes to save to database - audio_data_bytes = converted_audio.tobytes() - - # convert back to np array with np.frombuffer(audio_data_bytes, np.float32) - # Convert audio to text using OpenAI's Whisper logger.info("Transcribing audio...") model = whisper.load_model("base") @@ -669,6 +665,22 @@ def audio_callback(indata, frames, time, status): logger.info(f"The narrated text is: {result_info['text']}") # word timestamps found at result_info['words'] + # compress and convert to bytes to save to database + logger.info("Size of uncompressed audio data: {} bytes".format(converted_audio.nbytes)) + # Create an in-memory file-like object + file_obj = io.BytesIO() + # Use the in-memory file to write the audio data using lossless compression + soundfile.write(file_obj, converted_audio, int(audio_stream.samplerate), format='FLAC') + # Get the compressed audio data as bytes + compressed_audio_bytes = file_obj.getvalue() + + logger.info("Size of compressed audio data: {} bytes".format(len(compressed_audio_bytes))) + + file_obj.close() + + # To decompress the audio and restore it to its original form: + # restored_audio, restored_samplerate = sf.read(io.BytesIO(compressed_audio_bytes)) + logger.info(f"joining...") keyboard_event_reader.join() mouse_event_reader.join() @@ -687,7 +699,7 @@ def audio_callback(indata, frames, time, status): if enable_audio: # Save audio frames to the database # TODO: change name - audio_file = crud.insert_audio_file(audio_data_bytes) + audio_file = crud.insert_audio_file(compressed_audio_bytes) # Create AudioInfo entry audio_info = crud.insert_audio_info(result_info['text'], recording_timestamp, From 9f4c280dcb2da8bbfd1552cd43e886deca7b753d Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 1 Jun 2023 18:59:36 -0400 Subject: [PATCH 12/27] store word by word timestamps --- .../3d3239ae4849_add_new_tables_for_audio_info_and_files.py | 3 ++- openadapt/crud.py | 6 ++++-- openadapt/models.py | 1 + openadapt/record.py | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py b/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py index 97b70e688..327e7f84c 100644 --- a/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py +++ b/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py @@ -25,10 +25,11 @@ def upgrade() -> None: ) op.create_table('audio_info', sa.Column('id', sa.Integer(), nullable=False), - sa.Column('transcribed_text', sa.String, nullable=True), + sa.Column('transcribed_text', sa.String(), nullable=True), sa.Column('recording_timestamp', sa.Integer(), nullable=True), sa.Column('file_id', sa.Integer(), nullable=True), sa.Column('sample_rate', sa.Integer(), nullable=True), + sa.Column('words_with_timestamps', sa.Text(), nullable=True), sa.ForeignKeyConstraint(['file_id'], ['audio_file.id'], name=op.f('fk_audio_info_file_id_audio_file')), sa.ForeignKeyConstraint(['recording_timestamp'], ['recording.timestamp'], name=op.f('fk_audio_info_recording_timestamp_recording')), sa.PrimaryKeyConstraint('id', name=op.f('pk_audio_info')) diff --git a/openadapt/crud.py b/openadapt/crud.py index 80cf29dca..520b786dc 100644 --- a/openadapt/crud.py +++ b/openadapt/crud.py @@ -1,5 +1,6 @@ from loguru import logger import sqlalchemy as sa +import json from openadapt.db import Session @@ -110,13 +111,14 @@ def insert_audio_file(data): return audio_data -def insert_audio_info(transcribed_text, recording_timestamp, sample_rate, audio_file): +def insert_audio_info(transcribed_text, recording_timestamp, sample_rate, audio_file, word_list): """Create an AudioInfo entry in the database.""" audio_info = AudioInfo( transcribed_text=transcribed_text, recording_timestamp=recording_timestamp, sample_rate=sample_rate, - file=audio_file + file=audio_file, + words_with_timestamps=json.dumps(word_list) ) db.add(audio_info) db.commit() diff --git a/openadapt/models.py b/openadapt/models.py index 7451b3ccf..357988b35 100644 --- a/openadapt/models.py +++ b/openadapt/models.py @@ -306,6 +306,7 @@ class AudioInfo(db.Base): recording_timestamp = sa.Column(sa.ForeignKey("recording.timestamp")) file_id = sa.Column(sa.ForeignKey("audio_file.id")) sample_rate = sa.Column(sa.Integer) + words_with_timestamps = sa.Column(sa.Text) recording = sa.orm.relationship("Recording", back_populates="audio_info") file = sa.orm.relationship("AudioFile", back_populates="audio_info") diff --git a/openadapt/record.py b/openadapt/record.py index eced71e2b..bcfe1e698 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -663,7 +663,7 @@ def audio_callback(indata, frames, time, status): model = whisper.load_model("base") result_info = model.transcribe(converted_audio, word_timestamps=True, fp16=False) logger.info(f"The narrated text is: {result_info['text']}") - # word timestamps found at result_info['words'] + word_list = result_info['segments'][0]['words'] # compress and convert to bytes to save to database logger.info("Size of uncompressed audio data: {} bytes".format(converted_audio.nbytes)) @@ -703,7 +703,7 @@ def audio_callback(indata, frames, time, status): # Create AudioInfo entry audio_info = crud.insert_audio_info(result_info['text'], recording_timestamp, - int(audio_stream.samplerate), audio_file) + int(audio_stream.samplerate), audio_file, word_list) logger.info(f"saved {recording_timestamp=}") From 20d29e193a83d7eac7cb8b7ed5e1e3ecfccf5927 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Fri, 2 Jun 2023 18:16:57 -0400 Subject: [PATCH 13/27] style changes --- openadapt/crud.py | 43 ++++++++++++++++++++++++++----------------- openadapt/record.py | 35 ++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/openadapt/crud.py b/openadapt/crud.py index 520b786dc..ab525a108 100644 --- a/openadapt/crud.py +++ b/openadapt/crud.py @@ -14,7 +14,6 @@ AudioInfo ) - BATCH_SIZE = 1 db = Session() @@ -23,6 +22,7 @@ window_events = [] performance_stats = [] + def _insert(event_data, table, buffer=None): """Insert using Core API for improved performance (no rows are returned)""" @@ -78,6 +78,7 @@ def insert_window_event(recording_timestamp, event_timestamp, event_data): } _insert(event_data, WindowEvent, window_events) + def insert_perf_stat(recording_timestamp, event_type, start_time, end_time): """ Insert event performance stat into db @@ -91,6 +92,7 @@ def insert_perf_stat(recording_timestamp, event_type, start_time, end_time): } _insert(event_perf_stat, PerformanceStat, performance_stats) + def get_perf_stats(recording_timestamp): """ return performance stats for a given recording @@ -98,12 +100,13 @@ def get_perf_stats(recording_timestamp): return ( db - .query(PerformanceStat) - .filter(PerformanceStat.recording_timestamp == recording_timestamp) - .order_by(PerformanceStat.start_time) - .all() + .query(PerformanceStat) + .filter(PerformanceStat.recording_timestamp == recording_timestamp) + .order_by(PerformanceStat.start_time) + .all() ) + def insert_audio_file(data): audio_data = AudioFile(data=data) db.add(audio_data) @@ -111,7 +114,13 @@ def insert_audio_file(data): return audio_data -def insert_audio_info(transcribed_text, recording_timestamp, sample_rate, audio_file, word_list): +def insert_audio_info( + transcribed_text, + recording_timestamp, + sample_rate, + audio_file, + word_list +): """Create an AudioInfo entry in the database.""" audio_info = AudioInfo( transcribed_text=transcribed_text, @@ -136,29 +145,29 @@ def insert_recording(recording_data): def get_latest_recording(): return ( db - .query(Recording) - .order_by(sa.desc(Recording.timestamp)) - .limit(1) - .first() + .query(Recording) + .order_by(sa.desc(Recording.timestamp)) + .limit(1) + .first() ) def get_recording(timestamp): return ( db - .query(Recording) - .filter(Recording.timestamp == timestamp) - .first() + .query(Recording) + .filter(Recording.timestamp == timestamp) + .first() ) def _get(table, recording_timestamp): return ( db - .query(table) - .filter(table.recording_timestamp == recording_timestamp) - .order_by(table.timestamp) - .all() + .query(table) + .filter(table.recording_timestamp == recording_timestamp) + .order_by(table.timestamp) + .all() ) diff --git a/openadapt/record.py b/openadapt/record.py index bcfe1e698..d3bcff69c 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -638,7 +638,8 @@ def audio_callback(indata, frames, time, status): audio_frames.append(indata.copy()) # open InputStream and start recording while ActionEvents are recorded - audio_stream = sounddevice.InputStream(callback=audio_callback, samplerate=16000, channels=1) + audio_stream = sounddevice.InputStream( + callback=audio_callback, samplerate=16000, channels=1) logger.info("Audio recording started.") audio_stream.start() @@ -661,25 +662,36 @@ def audio_callback(indata, frames, time, status): # Convert audio to text using OpenAI's Whisper logger.info("Transcribing audio...") model = whisper.load_model("base") - result_info = model.transcribe(converted_audio, word_timestamps=True, fp16=False) + result_info = model.transcribe( + converted_audio, word_timestamps=True, fp16=False) logger.info(f"The narrated text is: {result_info['text']}") - word_list = result_info['segments'][0]['words'] + # empty word_list if the user didn't say anything + word_list = [] + if len(result_info['segments']) > 0: + # segments could be empty + if 'words' in result_info['segments'][0]: + # there won't be a 'words' list if the user didn't say anything + word_list = result_info['segments'][0]['words'] # compress and convert to bytes to save to database - logger.info("Size of uncompressed audio data: {} bytes".format(converted_audio.nbytes)) + logger.info("Size of uncompressed audio data: {} bytes" + .format(converted_audio.nbytes)) # Create an in-memory file-like object file_obj = io.BytesIO() - # Use the in-memory file to write the audio data using lossless compression - soundfile.write(file_obj, converted_audio, int(audio_stream.samplerate), format='FLAC') + # Write the audio data using lossless compression + soundfile.write(file_obj, converted_audio, + int(audio_stream.samplerate), format='FLAC') # Get the compressed audio data as bytes compressed_audio_bytes = file_obj.getvalue() - logger.info("Size of compressed audio data: {} bytes".format(len(compressed_audio_bytes))) + logger.info("Size of compressed audio data: {} bytes" + .format(len(compressed_audio_bytes))) file_obj.close() # To decompress the audio and restore it to its original form: - # restored_audio, restored_samplerate = sf.read(io.BytesIO(compressed_audio_bytes)) + # restored_audio, restored_samplerate = sf.read( + # io.BytesIO(compressed_audio_bytes)) logger.info(f"joining...") keyboard_event_reader.join() @@ -698,12 +710,13 @@ def audio_callback(indata, frames, time, status): if enable_audio: # Save audio frames to the database - # TODO: change name audio_file = crud.insert_audio_file(compressed_audio_bytes) # Create AudioInfo entry - audio_info = crud.insert_audio_info(result_info['text'], recording_timestamp, - int(audio_stream.samplerate), audio_file, word_list) + audio_info = crud.insert_audio_info(result_info['text'], + recording_timestamp, + int(audio_stream.samplerate), + audio_file, word_list) logger.info(f"saved {recording_timestamp=}") From 8d27b4fd295c91d449015b33b318a06fc7f828fa Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 15 Jun 2023 21:53:20 -0400 Subject: [PATCH 14/27] changed tiktoken version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9706006d6..9f08d4338 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,7 @@ scipy==1.9.3 setuptools-lint sphinx sqlalchemy==1.4.43 -tiktoken==0.4.0 +tiktoken==0.3.3 torch==2.0.0 tqdm==4.64.0 numpy~=1.24.3 From d631b2d67a53ffb46bb085f8cfbe66f6a9089aa6 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 15 Jun 2023 21:53:34 -0400 Subject: [PATCH 15/27] removed unused tiktoken code --- openadapt/strategies/mixins/openai.py | 48 --------------------------- 1 file changed, 48 deletions(-) diff --git a/openadapt/strategies/mixins/openai.py b/openadapt/strategies/mixins/openai.py index 2698cf61b..353be33f0 100644 --- a/openadapt/strategies/mixins/openai.py +++ b/openadapt/strategies/mixins/openai.py @@ -11,7 +11,6 @@ class MyReplayStrategy(OpenAIReplayStrategyMixin): from loguru import logger import openai -import tiktoken from openadapt.strategies.base import BaseReplayStrategy from openadapt import cache, config, models @@ -30,7 +29,6 @@ class MyReplayStrategy(OpenAIReplayStrategyMixin): MODEL_NAME = "gpt-4" openai.api_key = config.OPENAI_API_KEY -encoding = tiktoken.get_encoding("cl100k_base") class OpenAIReplayStrategyMixin(BaseReplayStrategy): @@ -160,49 +158,3 @@ def _get_completion( logger.debug(f"appending assistant_message=\n{pformat(assistant_message)}") messages.append(assistant_message) return messages - - -# XXX TODO not currently in use -# https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb -def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301"): - """Returns the number of tokens used by a list of messages.""" - try: - encoding = tiktoken.encoding_for_model(model) - except KeyError: - logger.info("Warning: model not found. Using cl100k_base encoding.") - encoding = tiktoken.get_encoding("cl100k_base") - if model == "gpt-3.5-turbo": - logger.info( - "Warning: gpt-3.5-turbo may change over time. Returning num tokens " - "assuming gpt-3.5-turbo-0301." - ) - return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301") - elif model == "gpt-4": - logger.info( - "Warning: gpt-4 may change over time. Returning num tokens " - "assuming gpt-4-0314." - ) - return num_tokens_from_messages(messages, model="gpt-4-0314") - elif model == "gpt-3.5-turbo-0301": - tokens_per_message = ( - 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n - ) - tokens_per_name = -1 # if there's a name, the role is omitted - elif model == "gpt-4-0314": - tokens_per_message = 3 - tokens_per_name = 1 - else: - raise NotImplementedError( - f"""num_tokens_from_messages() is not implemented for model " - "{model}. See " - "https://github.com/openai/openai-python/blob/main/chatml.md for " - information on how messages are converted to tokens.""") - num_tokens = 0 - for message in messages: - num_tokens += tokens_per_message - for key, value in message.items(): - num_tokens += len(encoding.encode(value)) - if key == "name": - num_tokens += tokens_per_name - num_tokens += 3 # every reply is primed with <|start|>assistant<|message|> - return num_tokens From e30538be1cff1f966c935f23f8a107d0b747db8e Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:34:12 -0400 Subject: [PATCH 16/27] alphabetic order, removed redundant dependencies --- requirements.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9f08d4338..53aecdf07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,18 +21,16 @@ pywin32==306; sys_platform == 'win32' pytesseract==0.3.7 -e git+https://github.com/abrichr/pynput.git#egg=pynput pytest==7.1.3 +python-dotenv==1.0.0 rapidocr-onnxruntime==1.2.3 scikit-learn==1.2.2 scipy==1.9.3 setuptools-lint +sounddevice~=0.4.6 +soundfile~=0.12.1 sphinx sqlalchemy==1.4.43 -tiktoken==0.3.3 torch==2.0.0 tqdm==4.64.0 -numpy~=1.24.3 -sounddevice~=0.4.6 -soundfile~=0.12.1 -git+https://github.com/openai/whisper.git transformers==4.29.2 -python-dotenv==1.0.0 +git+https://github.com/openai/whisper.git From 94690430274a606329aa6090b5a904dab60c3c49 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Sun, 18 Jun 2023 18:01:52 -0400 Subject: [PATCH 17/27] merged AudioInfo and AudioFile --- ...d_files.py => 3d3239ae4849_add_audio_info.py} | 11 ++--------- openadapt/crud.py | 13 ++----------- openadapt/models.py | 16 +++------------- openadapt/record.py | 8 +++----- openadapt/visualize.py | 6 ++++-- 5 files changed, 14 insertions(+), 40 deletions(-) rename alembic/versions/{3d3239ae4849_add_new_tables_for_audio_info_and_files.py => 3d3239ae4849_add_audio_info.py} (71%) diff --git a/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py b/alembic/versions/3d3239ae4849_add_audio_info.py similarity index 71% rename from alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py rename to alembic/versions/3d3239ae4849_add_audio_info.py index 327e7f84c..aedae40fb 100644 --- a/alembic/versions/3d3239ae4849_add_new_tables_for_audio_info_and_files.py +++ b/alembic/versions/3d3239ae4849_add_audio_info.py @@ -1,4 +1,4 @@ -"""Add new tables for audio info and files +"""Add audio info Revision ID: 3d3239ae4849 Revises: 104d4a614d95 @@ -18,19 +18,13 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.create_table('audio_file', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('data', sa.LargeBinary(), nullable=True), - sa.PrimaryKeyConstraint('id', name=op.f('pk_audio_file')) - ) op.create_table('audio_info', sa.Column('id', sa.Integer(), nullable=False), + sa.Column('flac_data', sa.LargeBinary(), nullable=True), sa.Column('transcribed_text', sa.String(), nullable=True), sa.Column('recording_timestamp', sa.Integer(), nullable=True), - sa.Column('file_id', sa.Integer(), nullable=True), sa.Column('sample_rate', sa.Integer(), nullable=True), sa.Column('words_with_timestamps', sa.Text(), nullable=True), - sa.ForeignKeyConstraint(['file_id'], ['audio_file.id'], name=op.f('fk_audio_info_file_id_audio_file')), sa.ForeignKeyConstraint(['recording_timestamp'], ['recording.timestamp'], name=op.f('fk_audio_info_recording_timestamp_recording')), sa.PrimaryKeyConstraint('id', name=op.f('pk_audio_info')) ) @@ -40,5 +34,4 @@ def upgrade() -> None: def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.drop_table('audio_info') - op.drop_table('audio_file') # ### end Alembic commands ### diff --git a/openadapt/crud.py b/openadapt/crud.py index ab525a108..d254544b2 100644 --- a/openadapt/crud.py +++ b/openadapt/crud.py @@ -10,7 +10,6 @@ Recording, WindowEvent, PerformanceStat, - AudioFile, AudioInfo ) @@ -107,31 +106,23 @@ def get_perf_stats(recording_timestamp): ) -def insert_audio_file(data): - audio_data = AudioFile(data=data) - db.add(audio_data) - db.commit() - return audio_data - - def insert_audio_info( + audio_data, transcribed_text, recording_timestamp, sample_rate, - audio_file, word_list ): """Create an AudioInfo entry in the database.""" audio_info = AudioInfo( + flac_data=audio_data, transcribed_text=transcribed_text, recording_timestamp=recording_timestamp, sample_rate=sample_rate, - file=audio_file, words_with_timestamps=json.dumps(word_list) ) db.add(audio_info) db.commit() - return audio_info def insert_recording(recording_data): diff --git a/openadapt/models.py b/openadapt/models.py index 14c195818..afccbbaa9 100644 --- a/openadapt/models.py +++ b/openadapt/models.py @@ -270,11 +270,11 @@ def take_screenshot(cls): sct_img = utils.take_screenshot() screenshot = Screenshot(sct_img=sct_img) return screenshot - + def crop_active_window(self, action_event): window_event = action_event.window_event width_ratio, height_ratio = utils.get_scale_ratios(action_event) - + x0 = window_event.left * width_ratio y0 = window_event.top * height_ratio x1 = x0 + window_event.width * width_ratio @@ -310,23 +310,13 @@ class AudioInfo(db.Base): __tablename__ = "audio_info" id = sa.Column(sa.Integer, primary_key=True) + flac_data = sa.Column(sa.LargeBinary) transcribed_text = sa.Column(sa.String) recording_timestamp = sa.Column(sa.ForeignKey("recording.timestamp")) - file_id = sa.Column(sa.ForeignKey("audio_file.id")) sample_rate = sa.Column(sa.Integer) words_with_timestamps = sa.Column(sa.Text) recording = sa.orm.relationship("Recording", back_populates="audio_info") - file = sa.orm.relationship("AudioFile", back_populates="audio_info") - - -class AudioFile(db.Base): - __tablename__ = "audio_file" - - id = sa.Column(sa.Integer, primary_key=True) - data = sa.Column(sa.LargeBinary) - - audio_info = sa.orm.relationship("AudioInfo", back_populates="file") class PerformanceStat(db.Base): diff --git a/openadapt/record.py b/openadapt/record.py index e011a549a..6a538b162 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -706,14 +706,12 @@ def audio_callback(indata, frames, time, status): utils.plot_performance(recording_timestamp) if enable_audio: - # Save audio frames to the database - audio_file = crud.insert_audio_file(compressed_audio_bytes) - # Create AudioInfo entry - audio_info = crud.insert_audio_info(result_info['text'], + audio_info = crud.insert_audio_info(compressed_audio_bytes, + result_info['text'], recording_timestamp, int(audio_stream.samplerate), - audio_file, word_list) + word_list) logger.info(f"saved {recording_timestamp=}") diff --git a/openadapt/visualize.py b/openadapt/visualize.py index 67a78719e..d893ea373 100644 --- a/openadapt/visualize.py +++ b/openadapt/visualize.py @@ -150,7 +150,9 @@ def main(): scrub.scrub_text(recording.task_description) logger.debug(f"{recording=}") - audio_info = get_audio_info(recording) + audio_info = row2dict(get_audio_info(recording)) + # don't display the FLAC data + del audio_info['flac_data'] meta = {} action_events = get_events(recording, process=PROCESS_EVENTS, meta=meta) @@ -183,7 +185,7 @@ def main(): ), row( Div( - text=f"{dict2html(row2dict(audio_info))}", + text=f"{dict2html(audio_info)}", ), ), ] From e9f2d36dd2ec57df9542bb07502f08e69432505c Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Mon, 19 Jun 2023 12:18:36 -0400 Subject: [PATCH 18/27] move audio recording into record_audio function --- openadapt/record.py | 254 +++++++++++++++++++++++--------------------- 1 file changed, 134 insertions(+), 120 deletions(-) diff --git a/openadapt/record.py b/openadapt/record.py index 6a538b162..12b4aee1d 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -50,13 +50,13 @@ def process_event(event, write_q, write_fn, recording_timestamp, perf_q): def process_events( - event_q: queue.Queue, - screen_write_q: multiprocessing.Queue, - action_write_q: multiprocessing.Queue, - window_write_q: multiprocessing.Queue, - perf_q: multiprocessing.Queue, - recording_timestamp: float, - terminate_event: multiprocessing.Event, + event_q: queue.Queue, + screen_write_q: multiprocessing.Queue, + action_write_q: multiprocessing.Queue, + window_write_q: multiprocessing.Queue, + perf_q: multiprocessing.Queue, + recording_timestamp: float, + terminate_event: multiprocessing.Event, ): """ Process events from event queue and write them to respective write queues. @@ -131,9 +131,9 @@ def process_events( def write_action_event( - recording_timestamp: float, - event: Event, - perf_q: multiprocessing.Queue, + recording_timestamp: float, + event: Event, + perf_q: multiprocessing.Queue, ): """ Write an action event to the database and update the performance queue. @@ -150,9 +150,9 @@ def write_action_event( def write_screen_event( - recording_timestamp: float, - event: Event, - perf_q: multiprocessing.Queue, + recording_timestamp: float, + event: Event, + perf_q: multiprocessing.Queue, ): """ Write a screen event to the database and update the performance queue. @@ -172,9 +172,9 @@ def write_screen_event( def write_window_event( - recording_timestamp: float, - event: Event, - perf_q: multiprocessing.Queue, + recording_timestamp: float, + event: Event, + perf_q: multiprocessing.Queue, ): """ Write a window event to the database and update the performance queue. @@ -191,12 +191,12 @@ def write_window_event( def write_events( - event_type: str, - write_fn: Callable, - write_q: multiprocessing.Queue, - perf_q: multiprocessing.Queue, - recording_timestamp: float, - terminate_event: multiprocessing.Event, + event_type: str, + write_fn: Callable, + write_q: multiprocessing.Queue, + perf_q: multiprocessing.Queue, + recording_timestamp: float, + terminate_event: multiprocessing.Event, ): """ Write events of a specific type to the db using the provided write function. @@ -226,8 +226,8 @@ def write_events( def trigger_action_event( - event_q: queue.Queue, - action_event_args: Dict[str, Any], + event_q: queue.Queue, + action_event_args: Dict[str, Any], ) -> None: x = action_event_args.get("mouse_x") y = action_event_args.get("mouse_y") @@ -241,10 +241,10 @@ def trigger_action_event( def on_move( - event_q: queue.Queue, - x: int, - y: int, - injected: bool, + event_q: queue.Queue, + x: int, + y: int, + injected: bool, ) -> None: logger.debug(f"{x=} {y=} {injected=}") if not injected: @@ -259,12 +259,12 @@ def on_move( def on_click( - event_q: queue.Queue, - x: int, - y: int, - button: mouse.Button, - pressed: bool, - injected: bool, + event_q: queue.Queue, + x: int, + y: int, + button: mouse.Button, + pressed: bool, + injected: bool, ) -> None: logger.debug(f"{x=} {y=} {button=} {pressed=} {injected=}") if not injected: @@ -281,12 +281,12 @@ def on_click( def on_scroll( - event_q: queue.Queue, - x: int, - y: int, - dx: int, - dy: int, - injected: bool, + event_q: queue.Queue, + x: int, + y: int, + dx: int, + dy: int, + injected: bool, ) -> None: logger.debug(f"{x=} {y=} {dx=} {dy=} {injected=}") if not injected: @@ -303,10 +303,10 @@ def on_scroll( def handle_key( - event_q: queue.Queue, - event_name: str, - key: keyboard.KeyCode, - canonical_key: keyboard.KeyCode, + event_q: queue.Queue, + event_name: str, + key: keyboard.KeyCode, + canonical_key: keyboard.KeyCode, ) -> None: attr_names = [ "name", @@ -326,9 +326,9 @@ def handle_key( def read_screen_events( - event_q: queue.Queue, - terminate_event: multiprocessing.Event, - recording_timestamp: float, + event_q: queue.Queue, + terminate_event: multiprocessing.Event, + recording_timestamp: float, ) -> None: """ Read screen events and add them to the event queue. @@ -377,7 +377,6 @@ def read_window_events( if window_data["title"] != prev_window_data.get("title") or window_data[ "window_id" ] != prev_window_data.get("window_id"): - # TODO: fix exception sometimes triggered by the next line on win32: # File "\Python39\lib\threading.py" line 917, in run # File "...\openadapt\record.py", line 277, in read window events @@ -435,7 +434,7 @@ def performance_stats_writer( def create_recording( - task_description: str, + task_description: str, ) -> Dict[str, Any]: """ Create a new recording entry in the database. @@ -495,9 +494,9 @@ def on_release(event_q, key, injected): def read_mouse_events( - event_q: queue.Queue, - terminate_event: multiprocessing.Event, - recording_timestamp: float, + event_q: queue.Queue, + terminate_event: multiprocessing.Event, + recording_timestamp: float, ) -> None: utils.set_start_time(recording_timestamp) mouse_listener = mouse.Listener( @@ -510,10 +509,81 @@ def read_mouse_events( mouse_listener.stop() -def record( - task_description: str, - enable_audio: bool = False -): +def record_audio( + terminate_event: multiprocessing.Event, + recording_timestamp: float, +) -> None: + utils.configure_logging(logger, LOG_LEVEL) + utils.set_start_time(recording_timestamp) + + audio_frames = [] # to store audio frames + + def audio_callback(indata, frames, time, status): + # called whenever there is new audio frames + audio_frames.append(indata.copy()) + + # open InputStream and start recording while ActionEvents are recorded + audio_stream = sounddevice.InputStream( + callback=audio_callback, samplerate=16000, channels=1 + ) + logger.info("Audio recording started.") + audio_stream.start() + terminate_event.wait() + audio_stream.stop() + audio_stream.close() + + # Concatenate into one Numpy array + concatenated_audio = np.concatenate(audio_frames, axis=0) + # convert concatenated_audio to format expected by whisper + converted_audio = concatenated_audio.flatten().astype(np.float32) + + # Convert audio to text using OpenAI's Whisper + logger.info("Transcribing audio...") + model = whisper.load_model("base") + result_info = model.transcribe(converted_audio, word_timestamps=True, fp16=False) + logger.info(f"The narrated text is: {result_info['text']}") + # empty word_list if the user didn't say anything + word_list = [] + # segments could be empty + if len(result_info["segments"]) > 0: + # there won't be a 'words' list if the user didn't say anything + if "words" in result_info["segments"][0]: + word_list = result_info["segments"][0]["words"] + + # compress and convert to bytes to save to database + logger.info( + "Size of uncompressed audio data: {} bytes".format(converted_audio.nbytes) + ) + # Create an in-memory file-like object + file_obj = io.BytesIO() + # Write the audio data using lossless compression + soundfile.write( + file_obj, converted_audio, int(audio_stream.samplerate), format="FLAC" + ) + # Get the compressed audio data as bytes + compressed_audio_bytes = file_obj.getvalue() + + logger.info( + "Size of compressed audio data: {} bytes".format(len(compressed_audio_bytes)) + ) + + file_obj.close() + + # To decompress the audio and restore it to its original form: + # restored_audio, restored_samplerate = sf.read( + # io.BytesIO(compressed_audio_bytes)) + + # Create AudioInfo entry + crud.insert_audio_info( + compressed_audio_bytes, + result_info["text"], + recording_timestamp, + int(audio_stream.samplerate), + word_list, + ) + + +def record(task_description: str, enable_audio: bool = False): """ Record Screenshots/ActionEvents/WindowEvents. Optionally record audio narration from the user describing what tasks are being done. @@ -625,20 +695,14 @@ def record( ) perf_stat_writer.start() - # TODO: discard events until everything is ready - - audio_frames = [] # to store audio frames - if enable_audio: - def audio_callback(indata, frames, time, status): - # called whenever there is new audio frames - audio_frames.append(indata.copy()) + audio_recorder = threading.Thread( + target=record_audio, + args=(terminate_event, recording_timestamp), + ) + audio_recorder.start() - # open InputStream and start recording while ActionEvents are recorded - audio_stream = sounddevice.InputStream( - callback=audio_callback, samplerate=16000, channels=1) - logger.info("Audio recording started.") - audio_stream.start() + # TODO: discard events until everything is ready try: while True: @@ -646,50 +710,6 @@ def audio_callback(indata, frames, time, status): except KeyboardInterrupt: terminate_event.set() - if enable_audio: - # stop the recording and close the InputStream - audio_stream.stop() - audio_stream.close() - - # Concatenate into one Numpy array - concatenated_audio = np.concatenate(audio_frames, axis=0) - # convert concatenated_audio to format expected by whisper - converted_audio = concatenated_audio.flatten().astype(np.float32) - - # Convert audio to text using OpenAI's Whisper - logger.info("Transcribing audio...") - model = whisper.load_model("base") - result_info = model.transcribe( - converted_audio, word_timestamps=True, fp16=False) - logger.info(f"The narrated text is: {result_info['text']}") - # empty word_list if the user didn't say anything - word_list = [] - if len(result_info['segments']) > 0: - # segments could be empty - if 'words' in result_info['segments'][0]: - # there won't be a 'words' list if the user didn't say anything - word_list = result_info['segments'][0]['words'] - - # compress and convert to bytes to save to database - logger.info("Size of uncompressed audio data: {} bytes" - .format(converted_audio.nbytes)) - # Create an in-memory file-like object - file_obj = io.BytesIO() - # Write the audio data using lossless compression - soundfile.write(file_obj, converted_audio, - int(audio_stream.samplerate), format='FLAC') - # Get the compressed audio data as bytes - compressed_audio_bytes = file_obj.getvalue() - - logger.info("Size of compressed audio data: {} bytes" - .format(len(compressed_audio_bytes))) - - file_obj.close() - - # To decompress the audio and restore it to its original form: - # restored_audio, restored_samplerate = sf.read( - # io.BytesIO(compressed_audio_bytes)) - logger.info(f"joining...") keyboard_event_reader.join() mouse_event_reader.join() @@ -699,20 +719,14 @@ def audio_callback(indata, frames, time, status): screen_event_writer.join() action_event_writer.join() window_event_writer.join() + if enable_audio: + audio_recorder.join() terminate_perf_event.set() if PLOT_PERFORMANCE: utils.plot_performance(recording_timestamp) - if enable_audio: - # Create AudioInfo entry - audio_info = crud.insert_audio_info(compressed_audio_bytes, - result_info['text'], - recording_timestamp, - int(audio_stream.samplerate), - word_list) - logger.info(f"saved {recording_timestamp=}") From 9293b0b74cf514180dd34673e55707dc1f86d4c9 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Mon, 19 Jun 2023 12:20:08 -0400 Subject: [PATCH 19/27] use thread-local scoped_session --- openadapt/crud.py | 7 ++++--- openadapt/db.py | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openadapt/crud.py b/openadapt/crud.py index d254544b2..62e438029 100644 --- a/openadapt/crud.py +++ b/openadapt/crud.py @@ -113,7 +113,8 @@ def insert_audio_info( sample_rate, word_list ): - """Create an AudioInfo entry in the database.""" + """Insert an AudioInfo entry into the database.""" + thread_local_db = Session() audio_info = AudioInfo( flac_data=audio_data, transcribed_text=transcribed_text, @@ -121,8 +122,8 @@ def insert_audio_info( sample_rate=sample_rate, words_with_timestamps=json.dumps(word_list) ) - db.add(audio_info) - db.commit() + thread_local_db.add(audio_info) + thread_local_db.commit() def insert_recording(recording_data): diff --git a/openadapt/db.py b/openadapt/db.py index 290b6b2e0..e6c2bd9d8 100644 --- a/openadapt/db.py +++ b/openadapt/db.py @@ -1,6 +1,6 @@ import sqlalchemy as sa from dictalchemy import DictableModel -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.schema import MetaData from sqlalchemy.ext.declarative import declarative_base @@ -50,4 +50,5 @@ def get_base(engine): engine = get_engine() Base = get_base(engine) -Session = sessionmaker(bind=engine) +session_factory = sessionmaker(bind=engine) +Session = scoped_session(session_factory) From 888d335399b76f583310c2afc9b8795ee501a0f8 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:18:12 -0400 Subject: [PATCH 20/27] remove redundant requirement --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d271ad420..a5eaf9d11 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,6 +26,7 @@ pytesseract==0.3.7 -e git+https://github.com/abrichr/pynput.git#egg=pynput pytest==7.1.3 python-dotenv==1.0.0 +python-Levenshtein==0.21.1 rapidocr-onnxruntime==1.2.3 scikit-learn==1.2.2 scipy==1.9.3 @@ -38,5 +39,3 @@ torch==2.0.0 tqdm==4.64.0 transformers==4.29.2 git+https://github.com/openai/whisper.git -python-dotenv==1.0.0 -python-Levenshtein==0.21.1 From d7c54f2ae50b620fff23cb7e5356b399475af2bc Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Wed, 30 Aug 2023 18:12:13 -0700 Subject: [PATCH 21/27] pull from main --- openadapt/{ => db}/db.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) rename openadapt/{ => db}/db.py (62%) diff --git a/openadapt/db.py b/openadapt/db/db.py similarity index 62% rename from openadapt/db.py rename to openadapt/db/db.py index e6c2bd9d8..c507d6e24 100644 --- a/openadapt/db.py +++ b/openadapt/db/db.py @@ -1,12 +1,15 @@ -import sqlalchemy as sa +"""Implements functionality for connecting to and interacting with the database. + +Module: db.py +""" + from dictalchemy import DictableModel -from sqlalchemy.orm import sessionmaker, scoped_session -from sqlalchemy.schema import MetaData from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.schema import MetaData +import sqlalchemy as sa from openadapt.config import DB_ECHO, DB_URL -from openadapt.utils import EMPTY, row2dict - NAMING_CONVENTION = { "ix": "ix_%(column_0_label)s", @@ -18,10 +21,15 @@ class BaseModel(DictableModel): + """The base model for database tables.""" __abstract__ = True - def __repr__(self): + def __repr__(self) -> str: + """Return a string representation of the model object.""" + # avoid circular import + from openadapt.utils import EMPTY, row2dict + params = ", ".join( f"{k}={v!r}" # !r converts value to string using repr (adds quotes) for k, v in row2dict(self, follow=False).items() @@ -30,7 +38,8 @@ def __repr__(self): return f"{self.__class__.__name__}({params})" -def get_engine(): +def get_engine() -> sa.engine: + """Create and return a database engine.""" engine = sa.create_engine( DB_URL, echo=DB_ECHO, @@ -38,7 +47,15 @@ def get_engine(): return engine -def get_base(engine): +def get_base(engine: sa.engine) -> sa.engine: + """Create and return the base model with the provided engine. + + Args: + engine (sa.engine): The database engine to bind to the base model. + + Returns: + sa.engine: The base model object. + """ metadata = MetaData(naming_convention=NAMING_CONVENTION) Base = declarative_base( cls=BaseModel, From 3eaa3a8dcf537eec71eebd8d68a8435339406088 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Wed, 30 Aug 2023 18:30:16 -0700 Subject: [PATCH 22/27] remove unused tiktoken function --- openadapt/strategies/mixins/openai.py | 49 --------------------------- 1 file changed, 49 deletions(-) diff --git a/openadapt/strategies/mixins/openai.py b/openadapt/strategies/mixins/openai.py index 35386b87a..3031e310d 100644 --- a/openadapt/strategies/mixins/openai.py +++ b/openadapt/strategies/mixins/openai.py @@ -11,7 +11,6 @@ class MyReplayStrategy(OpenAIReplayStrategyMixin): from loguru import logger import openai -import tiktoken from openadapt import cache, config, models from openadapt.strategies.base import BaseReplayStrategy @@ -29,7 +28,6 @@ class MyReplayStrategy(OpenAIReplayStrategyMixin): MODEL_NAME = "gpt-4" openai.api_key = config.OPENAI_API_KEY -encoding = tiktoken.get_encoding("cl100k_base") class OpenAIReplayStrategyMixin(BaseReplayStrategy): @@ -187,50 +185,3 @@ def _get_completion(prompt: str) -> str: logger.debug(f"appending assistant_message=\n{pformat(assistant_message)}") messages.append(assistant_message) return messages - - -# XXX TODO not currently in use -# https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb -def num_tokens_from_messages(messages: list, model: str = "gpt-3.5-turbo-0301") -> int: - """Returns the number of tokens used by a list of messages.""" - try: - encoding = tiktoken.encoding_for_model(model) - except KeyError: - logger.info("Warning: model not found. Using cl100k_base encoding.") - encoding = tiktoken.get_encoding("cl100k_base") - if model == "gpt-3.5-turbo": - logger.info( - "Warning: gpt-3.5-turbo may change over time. Returning num tokens " - "assuming gpt-3.5-turbo-0301." - ) - return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301") - elif model == "gpt-4": - logger.info( - "Warning: gpt-4 may change over time. Returning num tokens " - "assuming gpt-4-0314." - ) - return num_tokens_from_messages(messages, model="gpt-4-0314") - elif model == "gpt-3.5-turbo-0301": - tokens_per_message = ( - 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n - ) - tokens_per_name = -1 # if there's a name, the role is omitted - elif model == "gpt-4-0314": - tokens_per_message = 3 - tokens_per_name = 1 - else: - raise NotImplementedError( - f"""num_tokens_from_messages() is not implemented for model " - "{model}. See " - "https://github.com/openai/openai-python/blob/main/chatml.md for " - information on how messages are converted to tokens.""" - ) - num_tokens = 0 - for message in messages: - num_tokens += tokens_per_message - for key, value in message.items(): - num_tokens += len(encoding.encode(value)) - if key == "name": - num_tokens += tokens_per_name - num_tokens += 3 # every reply is primed with <|start|>assistant<|message|> - return num_tokens From 05834c49713ca591f4bbe6aeb16fdc6320df78bc Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Wed, 30 Aug 2023 22:09:00 -0700 Subject: [PATCH 23/27] add audio dependencies --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e05451d34..1291ba5eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,6 @@ rapidocr-onnxruntime = "1.2.3" scikit-learn = "1.2.2" scipy = "1.9.3" sqlalchemy = "1.4.43" -tiktoken = "0.4.0" torch = "^2.0.0" tqdm = "4.64.0" transformers = "4.29.2" @@ -86,6 +85,11 @@ click = "^8.1.6" spacy-transformers = "^1.2.5" boto3 = "^1.28.30" botocore = "^1.31.30" +llvmlite = "0.40.1rc1" +numba = "0.57.0" +openai-whisper = {git = "https://github.com/openai/whisper.git"} +sounddevice = "^0.4.6" +soundfile = "^0.12.1" [tool.pytest.ini_options] filterwarnings = [ From a6e45bdc8e49f7b24f48ebe2c77f3c87a28b028e Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:20:50 -0700 Subject: [PATCH 24/27] style changes --- openadapt/db/crud.py | 21 +++++++++++++++++++++ openadapt/models.py | 2 ++ openadapt/record.py | 19 ++++++++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/openadapt/db/crud.py b/openadapt/db/crud.py index 7b48d9a82..71ad34b74 100644 --- a/openadapt/db/crud.py +++ b/openadapt/db/crud.py @@ -4,6 +4,7 @@ """ from typing import Any +import json from loguru import logger import sqlalchemy as sa @@ -12,6 +13,7 @@ from openadapt.db.db import BaseModel, Session from openadapt.models import ( ActionEvent, + AudioInfo, MemoryStat, PerformanceStat, Recording, @@ -411,3 +413,22 @@ def new_session() -> None: if db: db.close() db = Session() + + +def insert_audio_info( + audio_data: bytes, + transcribed_text: str, + recording_timestamp: float, + sample_rate: int, + word_list: list, +) -> None: + """Create an AudioInfo entry in the database.""" + audio_info = AudioInfo( + flac_data=audio_data, + transcribed_text=transcribed_text, + recording_timestamp=recording_timestamp, + sample_rate=sample_rate, + words_with_timestamps=json.dumps(word_list), + ) + db.add(audio_info) + db.commit() diff --git a/openadapt/models.py b/openadapt/models.py index 07b9d30f7..b64f78d95 100644 --- a/openadapt/models.py +++ b/openadapt/models.py @@ -381,6 +381,8 @@ def get_active_window_event(cls: "WindowEvent") -> "WindowEvent": class AudioInfo(db.Base): + """Class representing the audio from a recording in the database.""" + __tablename__ = "audio_info" id = sa.Column(sa.Integer, primary_key=True) diff --git a/openadapt/record.py b/openadapt/record.py index 8787dd7d3..5165056d9 100644 --- a/openadapt/record.py +++ b/openadapt/record.py @@ -4,6 +4,10 @@ $ python openadapt/record.py "" +To record audio: + + $ python openadapt/record.py "" --enable_audio + """ from collections import namedtuple @@ -813,12 +817,25 @@ def record_audio( terminate_event: multiprocessing.Event, recording_timestamp: float, ) -> None: + """Record audio narration during the recording and store data in database. + + Args: + terminate_event: The event to signal termination of event reading. + recording_timestamp: The timestamp of the recording. + """ utils.configure_logging(logger, LOG_LEVEL) utils.set_start_time(recording_timestamp) audio_frames = [] # to store audio frames - def audio_callback(indata, frames, time, status): + def audio_callback( + indata: np.ndarray, frames: int, time: Any, status: sounddevice.CallbackFlags + ) -> None: + """Callback function used when new audio frames are recorded. + + Note: time is of type cffi.FFI.CData, but since we don't use this argument + and we also don't use the cffi library, the Any type annotation is used. + """ # called whenever there is new audio frames audio_frames.append(indata.copy()) From f23df5130242e2d40b7fbce3d96decde7f4a7071 Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:28:53 -0700 Subject: [PATCH 25/27] new alembic file --- .../versions/3d3239ae4849_add_audio_info.py | 37 --------------- .../versions/c176288cb508_add_audio_info.py | 47 +++++++++++++++++++ 2 files changed, 47 insertions(+), 37 deletions(-) delete mode 100644 alembic/versions/3d3239ae4849_add_audio_info.py create mode 100644 alembic/versions/c176288cb508_add_audio_info.py diff --git a/alembic/versions/3d3239ae4849_add_audio_info.py b/alembic/versions/3d3239ae4849_add_audio_info.py deleted file mode 100644 index aedae40fb..000000000 --- a/alembic/versions/3d3239ae4849_add_audio_info.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Add audio info - -Revision ID: 3d3239ae4849 -Revises: 104d4a614d95 -Create Date: 2023-05-31 19:36:46.269697 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '3d3239ae4849' -down_revision = '104d4a614d95' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('audio_info', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('flac_data', sa.LargeBinary(), nullable=True), - sa.Column('transcribed_text', sa.String(), nullable=True), - sa.Column('recording_timestamp', sa.Integer(), nullable=True), - sa.Column('sample_rate', sa.Integer(), nullable=True), - sa.Column('words_with_timestamps', sa.Text(), nullable=True), - sa.ForeignKeyConstraint(['recording_timestamp'], ['recording.timestamp'], name=op.f('fk_audio_info_recording_timestamp_recording')), - sa.PrimaryKeyConstraint('id', name=op.f('pk_audio_info')) - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('audio_info') - # ### end Alembic commands ### diff --git a/alembic/versions/c176288cb508_add_audio_info.py b/alembic/versions/c176288cb508_add_audio_info.py new file mode 100644 index 000000000..db7de14ae --- /dev/null +++ b/alembic/versions/c176288cb508_add_audio_info.py @@ -0,0 +1,47 @@ +"""Add audio info. + +Revision ID: c176288cb508 +Revises: 8713b142f5de +Create Date: 2023-08-31 00:25:04.889325 + +""" +import sqlalchemy as sa + +from alembic import op +from openadapt.models import ForceFloat + +# revision identifiers, used by Alembic. +revision = "c176288cb508" +down_revision = "8713b142f5de" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "audio_info", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("flac_data", sa.LargeBinary(), nullable=True), + sa.Column("transcribed_text", sa.String(), nullable=True), + sa.Column( + "recording_timestamp", + ForceFloat(precision=10, scale=2, asdecimal=False), + nullable=True, + ), + sa.Column("sample_rate", sa.Integer(), nullable=True), + sa.Column("words_with_timestamps", sa.Text(), nullable=True), + sa.ForeignKeyConstraint( + ["recording_timestamp"], + ["recording.timestamp"], + name=op.f("fk_audio_info_recording_timestamp_recording"), + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_audio_info")), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("audio_info") + # ### end Alembic commands ### From f6cdbc08c617ba9936ce1234c94a8b4e2bbf93dd Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:30:30 -0700 Subject: [PATCH 26/27] delete old requirements.txt --- requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e69de29bb..000000000 From 873cf6d4325b398da52f663f93c998827f1ed34d Mon Sep 17 00:00:00 2001 From: angelala3252 <88949118+angelala3252@users.noreply.github.com> Date: Thu, 31 Aug 2023 04:47:45 -0700 Subject: [PATCH 27/27] added audio dependencies --- poetry.lock | 365 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 270 insertions(+), 95 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0e29491ea..53c662c5e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -175,24 +175,24 @@ files = [ [[package]] name = "anyio" -version = "3.7.1" +version = "4.0.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, + {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"}, + {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"}, ] [package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" [package.extras] -doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (<0.22)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.22)"] [[package]] name = "appnope" @@ -430,17 +430,17 @@ typing-extensions = ">=3.10.0" [[package]] name = "boto3" -version = "1.28.30" +version = "1.28.38" description = "The AWS SDK for Python" optional = false python-versions = ">= 3.7" files = [ - {file = "boto3-1.28.30-py3-none-any.whl", hash = "sha256:e095ede98d3680e65966ab71f273b7d86938f5d853773ef96f4cb646277c2a4b"}, - {file = "boto3-1.28.30.tar.gz", hash = "sha256:2b509a959966a572f15db5768a18066ce1f53022ac53fca9421c620219fa3998"}, + {file = "boto3-1.28.38-py3-none-any.whl", hash = "sha256:dae0bc5548b39d8dfcf4167d9a238ab899a0491c11c5e77934db71b3ecf34752"}, + {file = "boto3-1.28.38.tar.gz", hash = "sha256:cdb466e51ebe4c99640269d88d5450328271437d58e6ce089690d0485bef6174"}, ] [package.dependencies] -botocore = ">=1.31.30,<1.32.0" +botocore = ">=1.31.38,<1.32.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.6.0,<0.7.0" @@ -449,13 +449,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.31.30" +version = "1.31.38" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">= 3.7" files = [ - {file = "botocore-1.31.30-py3-none-any.whl", hash = "sha256:269f20dcadd8dfd0c26d0e6fbceb84814ff6638ff3aafcc5324b9fb9949a7051"}, - {file = "botocore-1.31.30.tar.gz", hash = "sha256:3cf6a9d7621b897c9ff23cd02113826141b3dd3d7e90273b661efc4dc05f84e2"}, + {file = "botocore-1.31.38-py3-none-any.whl", hash = "sha256:86a4c253bba046f775e07f6585ff6c3d75c21a21c171e5bfcf68bc59f29d7b4c"}, + {file = "botocore-1.31.38.tar.gz", hash = "sha256:b02de7898f0a7de0f6569be1c87046035a974006c31fd641f4b97a8dba1fad21"}, ] [package.dependencies] @@ -2118,6 +2118,39 @@ files = [ [package.dependencies] rapidfuzz = ">=2.3.0,<4.0.0" +[[package]] +name = "llvmlite" +version = "0.40.1rc1" +description = "lightweight wrapper around basic LLVM functionality" +optional = false +python-versions = ">=3.8" +files = [ + {file = "llvmlite-0.40.1rc1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:31b606ae4923a897fe7122fe9a75fa39713279e796b335b83cb929c5d9e8661b"}, + {file = "llvmlite-0.40.1rc1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d08de4135dd8652f46de42e795b744dcad8cc11de3b6044a7326a61636887655"}, + {file = "llvmlite-0.40.1rc1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94043bb283395963b48fa964c776670889084be5117cbc4f831ab357005365d1"}, + {file = "llvmlite-0.40.1rc1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:659b400cf61e567c5c30159f60eac8562133bf5e497f481388d22e6b5dd00044"}, + {file = "llvmlite-0.40.1rc1-cp310-cp310-win32.whl", hash = "sha256:84f5c569fdcc503a7ce5018d2115ebac3a385743774ed22c6cc8dade673eae33"}, + {file = "llvmlite-0.40.1rc1-cp310-cp310-win_amd64.whl", hash = "sha256:a775e87d6ee6f6fcdae5ead0dec171243719002fc39c500c4813babb3609f6d9"}, + {file = "llvmlite-0.40.1rc1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:239eaeef72267566538b9f4cba8a41fb3e39ac99881c2a9a8100aff60c645edb"}, + {file = "llvmlite-0.40.1rc1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b8ceb9c436acdc87c3f5ab2dd6e3d003cf938abf55d3470d059abd99dee63d3"}, + {file = "llvmlite-0.40.1rc1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0475c5107334cf528c607275e0e1cd7836c31fe07c6e45994cd02dd45a95e3b1"}, + {file = "llvmlite-0.40.1rc1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3bc15e54522695ef16b5225cb40e89ef7f80d2d37cb0a8ddf3ffe3200fa238ff"}, + {file = "llvmlite-0.40.1rc1-cp311-cp311-win_amd64.whl", hash = "sha256:957c5f18726362fd2426f39997b9090c88a6a1cb11d4330b50b4946fa0c857a7"}, + {file = "llvmlite-0.40.1rc1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:38bcca23eb8919279619bebb4db6946f0d3dfedd879dfe9f741041789c83e36b"}, + {file = "llvmlite-0.40.1rc1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:753359a969f0606c30d3ef38988ae46c65ef2d3bcc7afb4ada0c37a2f4416a68"}, + {file = "llvmlite-0.40.1rc1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae6c6b04ec4d83b5bd3437dd4ef7a9e6d4461437e615fa0895ac355709b6f10"}, + {file = "llvmlite-0.40.1rc1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f095b6c6e94fcb26705614d9da2267c739118f1e97ba6bb3ea5c9fbc77764171"}, + {file = "llvmlite-0.40.1rc1-cp38-cp38-win32.whl", hash = "sha256:92918a7c60bacebf72297b4caeca2bcf2a6cffb50362e915cc1dc202ac556586"}, + {file = "llvmlite-0.40.1rc1-cp38-cp38-win_amd64.whl", hash = "sha256:2585ea726f6cd012279ea5a0d84d999e436061dc7df67bdaea1cbae998a16f9f"}, + {file = "llvmlite-0.40.1rc1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5ad3dd8a0c600533650e14cc908874c2dbeca5ea749acfc262564f15586dc94"}, + {file = "llvmlite-0.40.1rc1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2daa1de68d1bc0fd78757bb01a96c434373dca83d28460ff16b1accb1f171aff"}, + {file = "llvmlite-0.40.1rc1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e9930a222cd98487dd4e8f916c3c92c0311ea294136fc4f3cd0bab6265e28b0"}, + {file = "llvmlite-0.40.1rc1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86e9060e28796c0b38a73f59802d0f6af31a1bb7c6e3b766cb96237d862fe26c"}, + {file = "llvmlite-0.40.1rc1-cp39-cp39-win32.whl", hash = "sha256:b047d0e35b61dcbeaa1a86afac696c2dd9ca48430cb63638417e837cc1f0e60a"}, + {file = "llvmlite-0.40.1rc1-cp39-cp39-win_amd64.whl", hash = "sha256:da0b97219fa1053ab9a964e4703fcfca4ef6077614e7dce21de71bbbe6e4a4e9"}, + {file = "llvmlite-0.40.1rc1.tar.gz", hash = "sha256:8a6465075a0449fd802c9274130abb4f4ccf926972e84e8eac365769b7ec48fc"}, +] + [[package]] name = "loguru" version = "0.6.0" @@ -2146,10 +2179,13 @@ files = [ {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, + {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, + {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, @@ -2158,6 +2194,7 @@ files = [ {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, @@ -2177,6 +2214,7 @@ files = [ {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, @@ -2186,6 +2224,7 @@ files = [ {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, @@ -2195,6 +2234,7 @@ files = [ {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, @@ -2204,6 +2244,7 @@ files = [ {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, @@ -2214,13 +2255,16 @@ files = [ {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, @@ -2426,6 +2470,17 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "more-itertools" +version = "10.1.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, + {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, +] + [[package]] name = "moviepy" version = "1.0.3" @@ -2718,37 +2773,78 @@ files = [ jeepney = {version = "*", markers = "sys_platform == \"linux\""} loguru = ">=0.5.3,<=0.6.0" +[[package]] +name = "numba" +version = "0.57.0" +description = "compiling Python code using LLVM" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numba-0.57.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2e2c14c411545e80bf0f1a33232fb0bd6aa3368f86e56eeffc7f6d3ac16ea3fd"}, + {file = "numba-0.57.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b3382c56d805ffcdc7b46eb69a906be733dd35b84be14abba8e5fd27d7916b2"}, + {file = "numba-0.57.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:133cba9b5002bf67f6f73d9b3050d919c1be91326bbdcccfdf3259bcfb1cec0e"}, + {file = "numba-0.57.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d92a17ee849574665c5d94e9c9b862e469e1231d3dbb9e58e58b30b4bb0cbce9"}, + {file = "numba-0.57.0-cp310-cp310-win32.whl", hash = "sha256:abc90c3d303a67ae5194770a6f0d0a83edf076683b8a426349a27b91d98e00d1"}, + {file = "numba-0.57.0-cp310-cp310-win_amd64.whl", hash = "sha256:430f43c96f866ca4fe6008d8aa28bb608911d908ff94f879e0dbad7768ef9869"}, + {file = "numba-0.57.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:069f7d8fddad4c0eb1d7534c2a18098fe50473dc77832b409176002e9011b96f"}, + {file = "numba-0.57.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:79daa130fc9e4ebd1eea0a594d1de86d8a4366989f5fab93c482246b502520db"}, + {file = "numba-0.57.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:274f4db4814ebd5ec81697acfc36df04a865b86610d7714905185b753f3f9baf"}, + {file = "numba-0.57.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0106ee441e3f69cc6f17cb470c4fcccd592e0606567d43245635d72b071ab88e"}, + {file = "numba-0.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5d31b4d95000d86ffa9652ab5bcfa0ea30e6c3fc40e610147d4f2f00116703d"}, + {file = "numba-0.57.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e0b8de39bf17519435937b53276dfb02e2eb8bc27cd211c8eeb01ffed1cab6b"}, + {file = "numba-0.57.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:18d90fa6fcd5b796999392a8ea67f2fbccecf8dabcea726e2e721c79f40566a6"}, + {file = "numba-0.57.0-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4f62528c7c8c5f97e9689fd788e420b68c67ee0a1a9a7715a57fd584b7aef1e"}, + {file = "numba-0.57.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fd12cf0b431676c08057685e229ea5daaa1ec8efba2506c38671734ace49c2d7"}, + {file = "numba-0.57.0-cp38-cp38-win32.whl", hash = "sha256:e5f11b1d435fb4d1d1b68fa68ff456d632dc4bfd40b18825ff80d6081d1afb26"}, + {file = "numba-0.57.0-cp38-cp38-win_amd64.whl", hash = "sha256:5810ed2d6d22eb3c48bedfac2187fc44bb90e05f02d47fd31059e69207ae4106"}, + {file = "numba-0.57.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eddba74493d4003a42cd61ff7feca4928a94a45553d1fddac77a5cc339f6f4f9"}, + {file = "numba-0.57.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:110be5e1213d0a3d5fc691e921a000779500620196d99cee9908fce83d1e48df"}, + {file = "numba-0.57.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f949018ab9c467d38f14fe17db4df0d4a1c664be802189e2d6c5a434d9ffd4f6"}, + {file = "numba-0.57.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9fc0cd4ec93a1e3877985e10ed5837ed2991c83aa4b7ca574caae5c8b448cc4b"}, + {file = "numba-0.57.0-cp39-cp39-win32.whl", hash = "sha256:83d4f21c98eed3001e9896a43a1ce9c825999c03f7eb39ddd1c2d07a76708392"}, + {file = "numba-0.57.0-cp39-cp39-win_amd64.whl", hash = "sha256:9173d00c6753212b68e4fd319cfa96c21b2263949452c97b034e78ce09539dee"}, + {file = "numba-0.57.0.tar.gz", hash = "sha256:2af6d81067a5bdc13960c6d2519dbabbf4d5d597cf75d640c5aeaefd48c6420a"}, +] + +[package.dependencies] +llvmlite = "==0.40.*" +numpy = ">=1.21,<1.25" + [[package]] name = "numpy" -version = "1.25.2" +version = "1.24.4" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" files = [ - {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, - {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, - {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, - {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, - {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, - {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, - {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, - {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, - {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, - {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, - {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, + {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, + {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, + {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, + {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, + {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, + {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, + {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, + {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, + {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, + {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, + {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, ] [[package]] @@ -2859,6 +2955,32 @@ dev = ["black (>=21.6b0,<22.0)", "pytest (==6.*)", "pytest-asyncio", "pytest-moc embeddings = ["matplotlib", "numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "plotly", "scikit-learn (>=1.0.2)", "scipy", "tenacity (>=8.0.1)"] wandb = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "wandb"] +[[package]] +name = "openai-whisper" +version = "20230314" +description = "Robust Speech Recognition via Large-Scale Weak Supervision" +optional = false +python-versions = ">=3.8" +files = [] +develop = false + +[package.dependencies] +more-itertools = "*" +numba = "*" +numpy = "*" +tiktoken = "0.3.3" +torch = "*" +tqdm = "*" + +[package.extras] +dev = ["black", "flake8", "isort", "pytest", "scipy"] + +[package.source] +type = "git" +url = "https://github.com/openai/whisper.git" +reference = "HEAD" +resolved_reference = "e8622f9afc4eba139bf796c210f5c01081000472" + [[package]] name = "opencv-python" version = "4.8.0.76" @@ -3774,23 +3896,23 @@ plugins = ["importlib-metadata"] [[package]] name = "pyinstaller" -version = "5.13.1" +version = "5.13.2" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false python-versions = "<3.13,>=3.7" files = [ - {file = "pyinstaller-5.13.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:3c9cfe6d5d2f392d5d47389f6d377a8f225db460cdd01048b5a3de1d99c24ebe"}, - {file = "pyinstaller-5.13.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:29341d2e86d5ce7df993e797ee96ef679041fc85376d31c35c7b714085a21299"}, - {file = "pyinstaller-5.13.1-py3-none-manylinux2014_i686.whl", hash = "sha256:ad6e31a8f35a463c6140e4cf979859197edc9831a1039253408b0fe5eec274dc"}, - {file = "pyinstaller-5.13.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:5d801db3ceee58d01337473ea897e96e4bb21421a169dd7cf8716754617ff7fc"}, - {file = "pyinstaller-5.13.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:2519db3edec87d8c33924c2c4b7e176d8c1bbd9ba892d77efb67281925e621d6"}, - {file = "pyinstaller-5.13.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e033218c8922f0342b6095fb444ecb3bc6747dfa58cac5eac2b985350f4b681e"}, - {file = "pyinstaller-5.13.1-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:086e68aa1e72f6aa13b9d170a395755e2b194b8ab410caeed02d16b432410c8c"}, - {file = "pyinstaller-5.13.1-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:aa609aca62edd8cdcf7740677a21525e6c23b5e9a8f821ec8a80c68947771b5d"}, - {file = "pyinstaller-5.13.1-py3-none-win32.whl", hash = "sha256:b8d4000af72bf72f8185d420cd0a0aee0961f03a5c3511dc3ff08cdaef0583de"}, - {file = "pyinstaller-5.13.1-py3-none-win_amd64.whl", hash = "sha256:b70ebc10811b30bbea4cf5b81fd1477db992c2614cf215edc987cda9c5468911"}, - {file = "pyinstaller-5.13.1-py3-none-win_arm64.whl", hash = "sha256:78d1601a11475b95dceff6eaf0c9cd74d93e3f47b5ce4ad63cd76e7a369d3d04"}, - {file = "pyinstaller-5.13.1.tar.gz", hash = "sha256:a2e7a1d76a7ac26f1db849d691a374f2048b0e204233028d25d79a90ecd1fec8"}, + {file = "pyinstaller-5.13.2-py3-none-macosx_10_13_universal2.whl", hash = "sha256:16cbd66b59a37f4ee59373a003608d15df180a0d9eb1a29ff3bfbfae64b23d0f"}, + {file = "pyinstaller-5.13.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8f6dd0e797ae7efdd79226f78f35eb6a4981db16c13325e962a83395c0ec7420"}, + {file = "pyinstaller-5.13.2-py3-none-manylinux2014_i686.whl", hash = "sha256:65133ed89467edb2862036b35d7c5ebd381670412e1e4361215e289c786dd4e6"}, + {file = "pyinstaller-5.13.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:7d51734423685ab2a4324ab2981d9781b203dcae42839161a9ee98bfeaabdade"}, + {file = "pyinstaller-5.13.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:2c2fe9c52cb4577a3ac39626b84cf16cf30c2792f785502661286184f162ae0d"}, + {file = "pyinstaller-5.13.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c63ef6133eefe36c4b2f4daf4cfea3d6412ece2ca218f77aaf967e52a95ac9b8"}, + {file = "pyinstaller-5.13.2-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:aadafb6f213549a5906829bb252e586e2cf72a7fbdb5731810695e6516f0ab30"}, + {file = "pyinstaller-5.13.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b2e1c7f5cceb5e9800927ddd51acf9cc78fbaa9e79e822c48b0ee52d9ce3c892"}, + {file = "pyinstaller-5.13.2-py3-none-win32.whl", hash = "sha256:421cd24f26144f19b66d3868b49ed673176765f92fa9f7914cd2158d25b6d17e"}, + {file = "pyinstaller-5.13.2-py3-none-win_amd64.whl", hash = "sha256:ddcc2b36052a70052479a9e5da1af067b4496f43686ca3cdda99f8367d0627e4"}, + {file = "pyinstaller-5.13.2-py3-none-win_arm64.whl", hash = "sha256:27cd64e7cc6b74c5b1066cbf47d75f940b71356166031deb9778a2579bb874c6"}, + {file = "pyinstaller-5.13.2.tar.gz", hash = "sha256:c8e5d3489c3a7cc5f8401c2d1f48a70e588f9967e391c3b06ddac1f685f8d5d2"}, ] [package.dependencies] @@ -3807,13 +3929,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.7" +version = "2023.8" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.7.tar.gz", hash = "sha256:0c436a4c3506020e34116a8a7ddfd854c1ad6ddca9a8cd84500bd6e69c9e68f9"}, - {file = "pyinstaller_hooks_contrib-2023.7-py2.py3-none-any.whl", hash = "sha256:3c10df14c0f71ab388dfbf1625375b087e7330d9444cbfd2b310ba027fa0cff0"}, + {file = "pyinstaller-hooks-contrib-2023.8.tar.gz", hash = "sha256:318ccc316fb2b8c0bbdff2456b444bf1ce0e94cb3948a0f4dd48f6fc33d41c01"}, + {file = "pyinstaller_hooks_contrib-2023.8-py2.py3-none-any.whl", hash = "sha256:d091a52fbeed71cde0359aa9ad66288521a8441cfba163d9446606c5136c72a8"}, ] [[package]] @@ -4368,13 +4490,13 @@ files = [ [[package]] name = "pywebview" -version = "4.2.2" +version = "4.3.1" description = "Build GUI for your Python program with JavaScript, HTML, and CSS" optional = false python-versions = ">=3.7" files = [ - {file = "pywebview-4.2.2-py3-none-any.whl", hash = "sha256:1db26c0fcf9e856195467464b2a3e00189d231457189a102747d4a8694c974e4"}, - {file = "pywebview-4.2.2.tar.gz", hash = "sha256:85be4215cd65ceacca5c7faef9d271e10bfe3ca25d2498c1c6d2e59a4d56e86c"}, + {file = "pywebview-4.3.1-py3-none-any.whl", hash = "sha256:545234d50aee886f040a24d316943852dad6e991d768b141ff96be8a88dde01d"}, + {file = "pywebview-4.3.1.tar.gz", hash = "sha256:14b864529c0172cc14ee0846bfde69c82cec5d1bf76647cfbff4ec1fc3318f7f"}, ] [package.dependencies] @@ -4457,6 +4579,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -4464,8 +4587,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -4482,6 +4612,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -4489,6 +4620,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -5101,6 +5233,49 @@ files = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] +[[package]] +name = "sounddevice" +version = "0.4.6" +description = "Play and Record Sound with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sounddevice-0.4.6-py3-none-any.whl", hash = "sha256:5de768ba6fe56ad2b5aaa2eea794b76b73e427961c95acad2ee2ed7f866a4b20"}, + {file = "sounddevice-0.4.6-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:8b0b806c205dd3e3cd5a97262b2482624fd21db7d47083b887090148a08051c8"}, + {file = "sounddevice-0.4.6-py3-none-win32.whl", hash = "sha256:e3ba6e674ffa8f79a591d744a1d4ab922fe5bdfd4faf8b25069a08e051010b7b"}, + {file = "sounddevice-0.4.6-py3-none-win_amd64.whl", hash = "sha256:7830d4f8f8570f2e5552942f81d96999c5fcd9a0b682d6fc5d5c5529df23be2c"}, + {file = "sounddevice-0.4.6.tar.gz", hash = "sha256:3236b78f15f0415bdf006a620cef073d0c0522851d66f4a961ed6d8eb1482fe9"}, +] + +[package.dependencies] +CFFI = ">=1.0" + +[package.extras] +numpy = ["NumPy"] + +[[package]] +name = "soundfile" +version = "0.12.1" +description = "An audio library based on libsndfile, CFFI and NumPy" +optional = false +python-versions = "*" +files = [ + {file = "soundfile-0.12.1-py2.py3-none-any.whl", hash = "sha256:828a79c2e75abab5359f780c81dccd4953c45a2c4cd4f05ba3e233ddf984b882"}, + {file = "soundfile-0.12.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d922be1563ce17a69582a352a86f28ed8c9f6a8bc951df63476ffc310c064bfa"}, + {file = "soundfile-0.12.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:bceaab5c4febb11ea0554566784bcf4bc2e3977b53946dda2b12804b4fe524a8"}, + {file = "soundfile-0.12.1-py2.py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:2dc3685bed7187c072a46ab4ffddd38cef7de9ae5eb05c03df2ad569cf4dacbc"}, + {file = "soundfile-0.12.1-py2.py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:074247b771a181859d2bc1f98b5ebf6d5153d2c397b86ee9e29ba602a8dfe2a6"}, + {file = "soundfile-0.12.1-py2.py3-none-win32.whl", hash = "sha256:59dfd88c79b48f441bbf6994142a19ab1de3b9bb7c12863402c2bc621e49091a"}, + {file = "soundfile-0.12.1-py2.py3-none-win_amd64.whl", hash = "sha256:0d86924c00b62552b650ddd28af426e3ff2d4dc2e9047dae5b3d8452e0a49a77"}, + {file = "soundfile-0.12.1.tar.gz", hash = "sha256:e8e1017b2cf1dda767aef19d2fd9ee5ebe07e050d430f77a0a7c66ba08b8cdae"}, +] + +[package.dependencies] +cffi = ">=1.0" + +[package.extras] +numpy = ["numpy"] + [[package]] name = "spacy" version = "3.6.1" @@ -5734,40 +5909,40 @@ files = [ [[package]] name = "tiktoken" -version = "0.4.0" +version = "0.3.3" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.8" files = [ - {file = "tiktoken-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:176cad7f053d2cc82ce7e2a7c883ccc6971840a4b5276740d0b732a2b2011f8a"}, - {file = "tiktoken-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:450d504892b3ac80207700266ee87c932df8efea54e05cefe8613edc963c1285"}, - {file = "tiktoken-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d662de1e7986d129139faf15e6a6ee7665ee103440769b8dedf3e7ba6ac37f"}, - {file = "tiktoken-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5727d852ead18b7927b8adf558a6f913a15c7766725b23dbe21d22e243041b28"}, - {file = "tiktoken-0.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c06cd92b09eb0404cedce3702fa866bf0d00e399439dad3f10288ddc31045422"}, - {file = "tiktoken-0.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9ec161e40ed44e4210d3b31e2ff426b4a55e8254f1023e5d2595cb60044f8ea6"}, - {file = "tiktoken-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:1e8fa13cf9889d2c928b9e258e9dbbbf88ab02016e4236aae76e3b4f82dd8288"}, - {file = "tiktoken-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bb2341836b725c60d0ab3c84970b9b5f68d4b733a7bcb80fb25967e5addb9920"}, - {file = "tiktoken-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ca30367ad750ee7d42fe80079d3092bd35bb266be7882b79c3bd159b39a17b0"}, - {file = "tiktoken-0.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dc3df19ddec79435bb2a94ee46f4b9560d0299c23520803d851008445671197"}, - {file = "tiktoken-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d980fa066e962ef0f4dad0222e63a484c0c993c7a47c7dafda844ca5aded1f3"}, - {file = "tiktoken-0.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:329f548a821a2f339adc9fbcfd9fc12602e4b3f8598df5593cfc09839e9ae5e4"}, - {file = "tiktoken-0.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b1a038cee487931a5caaef0a2e8520e645508cde21717eacc9af3fbda097d8bb"}, - {file = "tiktoken-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:08efa59468dbe23ed038c28893e2a7158d8c211c3dd07f2bbc9a30e012512f1d"}, - {file = "tiktoken-0.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f3020350685e009053829c1168703c346fb32c70c57d828ca3742558e94827a9"}, - {file = "tiktoken-0.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba16698c42aad8190e746cd82f6a06769ac7edd415d62ba027ea1d99d958ed93"}, - {file = "tiktoken-0.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c15d9955cc18d0d7ffcc9c03dc51167aedae98542238b54a2e659bd25fe77ed"}, - {file = "tiktoken-0.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64e1091c7103100d5e2c6ea706f0ec9cd6dc313e6fe7775ef777f40d8c20811e"}, - {file = "tiktoken-0.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e87751b54eb7bca580126353a9cf17a8a8eaadd44edaac0e01123e1513a33281"}, - {file = "tiktoken-0.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e063b988b8ba8b66d6cc2026d937557437e79258095f52eaecfafb18a0a10c03"}, - {file = "tiktoken-0.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9c6dd439e878172dc163fced3bc7b19b9ab549c271b257599f55afc3a6a5edef"}, - {file = "tiktoken-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d1d97f83697ff44466c6bef5d35b6bcdb51e0125829a9c0ed1e6e39fb9a08fb"}, - {file = "tiktoken-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b6bce7c68aa765f666474c7c11a7aebda3816b58ecafb209afa59c799b0dd2d"}, - {file = "tiktoken-0.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a73286c35899ca51d8d764bc0b4d60838627ce193acb60cc88aea60bddec4fd"}, - {file = "tiktoken-0.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0394967d2236a60fd0aacef26646b53636423cc9c70c32f7c5124ebe86f3093"}, - {file = "tiktoken-0.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:dae2af6f03ecba5f679449fa66ed96585b2fa6accb7fd57d9649e9e398a94f44"}, - {file = "tiktoken-0.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55e251b1da3c293432179cf7c452cfa35562da286786be5a8b1ee3405c2b0dd2"}, - {file = "tiktoken-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:c835d0ee1f84a5aa04921717754eadbc0f0a56cf613f78dfc1cf9ad35f6c3fea"}, - {file = "tiktoken-0.4.0.tar.gz", hash = "sha256:59b20a819969735b48161ced9b92f05dc4519c17be4015cfb73b65270a243620"}, + {file = "tiktoken-0.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1f37fa75ba70c1bc7806641e8ccea1fba667d23e6341a1591ea333914c226a9"}, + {file = "tiktoken-0.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3d7296c38392a943c2ccc0b61323086b8550cef08dcf6855de9949890dbc1fd3"}, + {file = "tiktoken-0.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c84491965e139a905280ac28b74baaa13445b3678e07f96767089ad1ef5ee7b"}, + {file = "tiktoken-0.3.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65970d77ea85ce6c7fce45131da9258cd58a802ffb29ead8f5552e331c025b2b"}, + {file = "tiktoken-0.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bd3f72d0ba7312c25c1652292121a24c8f1711207b63c6d8dab21afe4be0bf04"}, + {file = "tiktoken-0.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:719c9e13432602dc496b24f13e3c3ad3ec0d2fbdb9aace84abfb95e9c3a425a4"}, + {file = "tiktoken-0.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:dc00772284c94e65045b984ed7e9f95d000034f6b2411df252011b069bd36217"}, + {file = "tiktoken-0.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db2c40f79f8f7a21a9fdbf1c6dee32dea77b0d7402355dc584a3083251d2e15"}, + {file = "tiktoken-0.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3c0f2231aa3829a1a431a882201dc27858634fd9989898e0f7d991dbc6bcc9d"}, + {file = "tiktoken-0.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48c13186a479de16cfa2c72bb0631fa9c518350a5b7569e4d77590f7fee96be9"}, + {file = "tiktoken-0.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6674e4e37ab225020135cd66a392589623d5164c6456ba28cc27505abed10d9e"}, + {file = "tiktoken-0.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4a0c1357f6191211c544f935d5aa3cb9d7abd118c8f3c7124196d5ecd029b4af"}, + {file = "tiktoken-0.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2e948d167fc3b04483cbc33426766fd742e7cefe5346cd62b0cbd7279ef59539"}, + {file = "tiktoken-0.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:5dca434c8680b987eacde2dbc449e9ea4526574dbf9f3d8938665f638095be82"}, + {file = "tiktoken-0.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:984758ebc07cd8c557345697c234f1f221bd730b388f4340dd08dffa50213a01"}, + {file = "tiktoken-0.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:891012f29e159a989541ae47259234fb29ff88c22e1097567316e27ad33a3734"}, + {file = "tiktoken-0.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:210f8602228e4c5d706deeb389da5a152b214966a5aa558eec87b57a1969ced5"}, + {file = "tiktoken-0.3.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd783564f80d4dc44ff0a64b13756ded8390ed2548549aefadbe156af9188307"}, + {file = "tiktoken-0.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:03f64bde9b4eb8338bf49c8532bfb4c3578f6a9a6979fc176d939f9e6f68b408"}, + {file = "tiktoken-0.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1ac369367b6f5e5bd80e8f9a7766ac2a9c65eda2aa856d5f3c556d924ff82986"}, + {file = "tiktoken-0.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:94600798891f78db780e5aa9321456cf355e54a4719fbd554147a628de1f163f"}, + {file = "tiktoken-0.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e59db6fca8d5ccea302fe2888917364446d6f4201a25272a1a1c44975c65406a"}, + {file = "tiktoken-0.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19340d8ba4d6fd729b2e3a096a547ded85f71012843008f97475f9db484869ee"}, + {file = "tiktoken-0.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542686cbc9225540e3a10f472f82fa2e1bebafce2233a211dee8459e95821cfd"}, + {file = "tiktoken-0.3.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a43612b2a09f4787c050163a216bf51123851859e9ab128ad03d2729826cde9"}, + {file = "tiktoken-0.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a11674f0275fa75fb59941b703650998bd4acb295adbd16fc8af17051aaed19d"}, + {file = "tiktoken-0.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:65fc0a449630bab28c30b4adec257442a4706d79cffc2337c1d9df3e91825cdd"}, + {file = "tiktoken-0.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:0b9a7a9a8b781a50ee9289e85e28771d7e113cc0c656eadfb6fc6d3a106ff9bb"}, + {file = "tiktoken-0.3.3.tar.gz", hash = "sha256:97b58b7bfda945791ec855e53d166e8ec20c6378942b93851a6c919ddf9d0496"}, ] [package.dependencies] @@ -6214,13 +6389,13 @@ pscript = ">=0.7.0,<0.8.0" [[package]] name = "virtualenv" -version = "20.24.3" +version = "20.24.4" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.3-py3-none-any.whl", hash = "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02"}, - {file = "virtualenv-20.24.3.tar.gz", hash = "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc"}, + {file = "virtualenv-20.24.4-py3-none-any.whl", hash = "sha256:29c70bb9b88510f6414ac3e55c8b413a1f96239b6b789ca123437d5e892190cb"}, + {file = "virtualenv-20.24.4.tar.gz", hash = "sha256:772b05bfda7ed3b8ecd16021ca9716273ad9f4467c801f27e83ac73430246dca"}, ] [package.dependencies] @@ -6229,7 +6404,7 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<4" [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] @@ -6558,4 +6733,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "3.10.x" -content-hash = "89726872f12f1c780732db0531d257e6156b8e0c3aca9865d8ad2dc03ec030e2" +content-hash = "09f677ee1d0b7bbfb4ce051ece7b85fc84ef43782d0285f0c90cf4321e4ad5df"