INITIAL COMMIT
This commit is contained in:
commit
9121ad819d
12
.fleet/run.json
Normal file
12
.fleet/run.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"type": "command",
|
||||
"name": "Command configuration",
|
||||
"program": "poetry",
|
||||
"args": ["run", "python", "main.py"],
|
||||
},
|
||||
|
||||
|
||||
]
|
||||
}
|
23
README.md
Normal file
23
README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Flüsterpost
|
||||
|
||||
Flüsterpost takes audio files and turns them into text. It's made to do so easily, quickly, and locally.
|
||||
|
||||
## How to Run
|
||||
|
||||
This repo uses the poetry package manager for Python. If you don't have it, you'll need to install it before using this.
|
||||
(i can recommend it. it's great.)
|
||||
|
||||
`cd` into the repository. Then, run `poetry install`.
|
||||
Then, do `poetry run main.py`.
|
||||
|
||||
### Deps
|
||||
This project uses [flet](https://flet.dev/) for the GUI. On Linux systems, you'll need `gstreamer` installed.
|
||||
You probably have this shipped with your system, but if you get the error message
|
||||
`error while loading shared libraries: libgstapp-1.0.so.0: cannot open shared object file: No such file or directory`,
|
||||
install it. More info on the [flet docs page](https://flet.dev/docs/guides/python/getting-started).
|
||||
|
||||
## License
|
||||
This project is licensed under the MIT license.
|
||||
|
||||
## Contributions
|
||||
Any contributions to this project will be licensed under the MIT license, unless explicitly noted otherwise.
|
BIN
__pycache__/nn_model_manager.cpython-311.pyc
Normal file
BIN
__pycache__/nn_model_manager.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/utils.cpython-311.pyc
Normal file
BIN
__pycache__/utils.cpython-311.pyc
Normal file
Binary file not shown.
322
main.py
Normal file
322
main.py
Normal file
@ -0,0 +1,322 @@
|
||||
import os
|
||||
import pprint
|
||||
import traceback
|
||||
|
||||
import utils
|
||||
import flet as ft
|
||||
|
||||
from typing import DefaultDict
|
||||
|
||||
import pygame
|
||||
|
||||
import nn_model_manager as mm
|
||||
|
||||
|
||||
def main(page):
|
||||
pygame.mixer.init()
|
||||
|
||||
first_name = ft.Ref[ft.TextField]()
|
||||
last_name = ft.Ref[ft.TextField]()
|
||||
greetings = ft.Ref[ft.Column]()
|
||||
|
||||
file_tree = ft.Ref[ft.Column]()
|
||||
file_tree_empty_text = ft.Ref[ft.Text]()
|
||||
|
||||
load_model_text = ft.Ref[ft.Text]()
|
||||
model_size_select = ft.Ref[ft.Dropdown]()
|
||||
model_device_select = ft.Ref[ft.Dropdown]()
|
||||
# model_bits_select = ft.Ref[ft.Dropdown]()
|
||||
model_load_unload_button = ft.Ref[ft.IconButton]()
|
||||
model_loading_spinner = ft.Ref[ft.ProgressRing]()
|
||||
|
||||
transcribe_buttons: list[ft.Ref[ft.IconButton]] = []
|
||||
|
||||
output_text_container = ft.Ref[ft.Container]()
|
||||
output_text_col = ft.Ref[ft.Column]()
|
||||
|
||||
def generate_file_tree(path: str, tree_dict: dict | DefaultDict):
|
||||
if path[-1] == os.sep:
|
||||
path = path[:-1]
|
||||
|
||||
folder_name = utils.get_last_segment(path)
|
||||
print(f"DEBUG: generating tree for folder {folder_name}")
|
||||
|
||||
# find folders, and add dict for each
|
||||
print(f"adding name {folder_name} to ui")
|
||||
|
||||
controls = [
|
||||
ft.Row(
|
||||
[
|
||||
ft.Icon(ft.icons.FOLDER, color=ft.colors.BLUE),
|
||||
ft.Text(folder_name, size=14, weight=ft.FontWeight.BOLD),
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
for folder_name, value in tree_dict.items():
|
||||
if folder_name == utils.FILES_KEY or folder_name == '.':
|
||||
continue # skip for now
|
||||
|
||||
controls.append(generate_file_tree(path + os.sep + folder_name, value))
|
||||
|
||||
# now folders are there, let's do files
|
||||
if utils.FILES_KEY not in tree_dict and '.' in tree_dict:
|
||||
tree_dict = tree_dict['.'] # if root dir, enter root dir (.) directory
|
||||
|
||||
files_controls = []
|
||||
|
||||
for file in tree_dict[utils.FILES_KEY]:
|
||||
control = [ft.Text(file)]
|
||||
|
||||
if not file.endswith('.mp3'):
|
||||
continue
|
||||
|
||||
def start_playing(filepath: str, button_ref: ft.Ref[ft.IconButton]):
|
||||
print(f"trying to play {filepath}...")
|
||||
if pygame.mixer.music.get_busy() or not os.path.isfile(filepath):
|
||||
return
|
||||
|
||||
print("starting playback")
|
||||
|
||||
pygame.mixer.music.load(filepath)
|
||||
pygame.mixer.music.play()
|
||||
|
||||
button_ref.current.icon = ft.icons.PAUSE_CIRCLE_FILLED_OUTLINED
|
||||
button_ref.current.on_click = lambda _, f=filepath, r=button_ref: stop_playing(f, r)
|
||||
page.update()
|
||||
|
||||
def stop_playing(filepath: str, button_ref: ft.Ref[ft.IconButton]):
|
||||
print("stopping playback")
|
||||
|
||||
pygame.mixer.music.stop()
|
||||
|
||||
button_ref.current.icon = ft.icons.PLAY_CIRCLE_OUTLINED
|
||||
button_ref.current.on_click = lambda _, f=filepath, r=button_ref: start_playing(f, r)
|
||||
page.update()
|
||||
|
||||
full_file_path = path + os.sep + file
|
||||
|
||||
_button_ref = ft.Ref[ft.IconButton]()
|
||||
|
||||
control.append(ft.IconButton(icon=ft.icons.PLAY_CIRCLE_OUTLINED, ref=_button_ref,
|
||||
on_click=lambda _, f=full_file_path, r=_button_ref: start_playing(f, r)))
|
||||
|
||||
def transcribe(filepath: str):
|
||||
print(f"DEBUG: trying to transcribe file {filepath}")
|
||||
if not mm.is_model_loaded() or not filepath.endswith('.mp3'):
|
||||
return
|
||||
|
||||
print(f"DEBUG: starting transcription")
|
||||
output_text_container.current.alignment = ft.alignment.center
|
||||
output_text_col.current.controls = [ft.ProgressRing()]
|
||||
|
||||
# set all transcribe buttons to disabled
|
||||
for btn in transcribe_buttons:
|
||||
btn.current.disabled = True
|
||||
page.update()
|
||||
|
||||
try:
|
||||
segments, info = mm.transcribe_from_file(filepath)
|
||||
|
||||
txt = ''
|
||||
|
||||
for seg in segments:
|
||||
txt += seg.text + '\n'
|
||||
|
||||
output_text_container.current.alignment = ft.alignment.top_left
|
||||
output_text_col.current.controls = [ft.Text(txt, selectable=True)] # TODO
|
||||
|
||||
except Exception as e:
|
||||
output_text_container.current.alignment = ft.alignment.center
|
||||
output_text_col.current.controls = [ft.Text(f"Transcribing failed: {str(e)}")] # TODO
|
||||
|
||||
finally:
|
||||
# set all transcribe buttons to disabled
|
||||
for btn in transcribe_buttons:
|
||||
btn.current.disabled = False
|
||||
page.update()
|
||||
|
||||
|
||||
transcribe_button_ref = ft.Ref[ft.IconButton]()
|
||||
|
||||
control.append(ft.IconButton(icon=ft.icons.FORMAT_ALIGN_LEFT, disabled=not mm.is_model_loaded(), ref=transcribe_button_ref,
|
||||
on_click=lambda _, f=full_file_path: transcribe(f)))
|
||||
|
||||
transcribe_buttons.append(transcribe_button_ref)
|
||||
|
||||
files_controls.append(ft.Row(control))
|
||||
|
||||
if len(files_controls) == 0:
|
||||
files_controls.append(ft.Text('No mp3 Files found', color='grey'))
|
||||
|
||||
return ft.Row([
|
||||
ft.VerticalDivider(),
|
||||
ft.Column(controls + [ft.Row([ft.VerticalDivider(), ft.Column(files_controls)])])
|
||||
]
|
||||
)
|
||||
|
||||
def btn_click(e):
|
||||
greetings.current.controls.append(
|
||||
ft.Text(f"Hello, {first_name.current.value} {last_name.current.value}!")
|
||||
)
|
||||
first_name.current.value = ""
|
||||
last_name.current.value = ""
|
||||
page.update()
|
||||
first_name.current.focus()
|
||||
|
||||
def on_dialog_result(e: ft.FilePickerResultEvent):
|
||||
path = e.path
|
||||
if path:
|
||||
print(f"path is {path}")
|
||||
try:
|
||||
if os.path.isdir(path):
|
||||
tree = utils.build_file_tree(path)
|
||||
|
||||
if '.' in tree: # if there is actually a proper file tree
|
||||
# add to view
|
||||
file_tree.current.controls.append(
|
||||
generate_file_tree(path, utils.defaultdict_to_dict(tree))
|
||||
)
|
||||
file_tree_empty_text.current.visible = False
|
||||
|
||||
page.update()
|
||||
except e:
|
||||
print("didn't work aaa") # TODO: fix
|
||||
|
||||
def load_model():
|
||||
|
||||
load_model_text.current.value = 'Loading... This may take a while.'
|
||||
|
||||
model_size_select.current.disabled = True
|
||||
model_device_select.current.disabled = True
|
||||
# model_bits_select.current.disabled = True
|
||||
model_load_unload_button.current.disabled = True
|
||||
model_loading_spinner.current.visible = True
|
||||
page.update()
|
||||
|
||||
try:
|
||||
mm.set_model(
|
||||
size=model_size_select.current.value or 'base',
|
||||
device=model_device_select.current.value or 'auto',
|
||||
# compute_type=model_bits_select.current.value or '16bit',
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"loading model failed. Exception: {str(e)}")
|
||||
print(traceback.format_exc())
|
||||
load_model_text.current.value = f'Loading failed. Reason:\n{str(e)}'
|
||||
model_size_select.current.disabled = False
|
||||
model_device_select.current.disabled = False
|
||||
# model_bits_select.current.disabled = False
|
||||
|
||||
# raise e
|
||||
|
||||
model_loading_spinner.current.visible = False
|
||||
model_load_unload_button.current.disabled = False
|
||||
|
||||
if mm.is_model_loaded():
|
||||
load_model_text.current.value = f'Loaded.'
|
||||
model_load_unload_button.current.icon = ft.icons.CLOSE
|
||||
model_load_unload_button.current.on_click = lambda _: unload_model()
|
||||
|
||||
# if successful, save to shared preferences
|
||||
page.client_storage.set('model_size', model_size_select.current.value)
|
||||
page.client_storage.set('device_select', model_device_select.current.value)
|
||||
|
||||
# set all transcribe buttons to enabled
|
||||
for btn in transcribe_buttons:
|
||||
btn.current.disabled = False
|
||||
|
||||
page.update()
|
||||
|
||||
def unload_model():
|
||||
model_load_unload_button.current.disabled = True
|
||||
|
||||
# set all transcribe buttons to disabled
|
||||
for btn in transcribe_buttons:
|
||||
btn.current.disabled = True
|
||||
|
||||
page.update()
|
||||
|
||||
if mm.is_model_loaded():
|
||||
mm.unload_model()
|
||||
|
||||
load_model_text.current.value = 'Select parameters, and then load transcription model.'
|
||||
model_size_select.current.disabled = False
|
||||
model_device_select.current.disabled = False
|
||||
# model_bits_select.current.disabled = False
|
||||
model_load_unload_button.current.disabled = False
|
||||
model_load_unload_button.current.icon = ft.icons.START
|
||||
model_load_unload_button.current.on_click = lambda _: load_model()
|
||||
model_loading_spinner.current.visible = False
|
||||
page.update()
|
||||
|
||||
|
||||
|
||||
|
||||
# set up file picker
|
||||
file_picker = ft.FilePicker(on_result=on_dialog_result)
|
||||
|
||||
page.overlay.append(file_picker)
|
||||
|
||||
page.add(
|
||||
ft.Text("Flüsterpost", style=ft.TextThemeStyle.TITLE_LARGE),
|
||||
ft.Divider()
|
||||
)
|
||||
|
||||
page.add(
|
||||
ft.ResponsiveRow([
|
||||
ft.Container(
|
||||
ft.Column([
|
||||
ft.ElevatedButton("Add Folder", on_click=lambda _: file_picker.get_directory_path()),
|
||||
ft.Column(ref=file_tree, scroll=ft.ScrollMode.ALWAYS, expand=True),
|
||||
# ft.ListView(ref=file_tree),
|
||||
ft.Text("No Folder Open Yet", style=ft.TextTheme.body_small, color="grey",
|
||||
ref=file_tree_empty_text),
|
||||
], expand=True), expand=True, col=4),
|
||||
ft.Container(expand=True, content=ft.Column(expand=True, controls=[
|
||||
ft.Column([
|
||||
ft.Text('Select parameters, and then load transcription model.', ref=load_model_text),
|
||||
ft.Row([
|
||||
ft.Dropdown(
|
||||
ref=model_size_select,
|
||||
width=100,
|
||||
hint_text='model size',
|
||||
value=page.client_storage.get('model_size') if page.client_storage.contains_key('model_size') else 'base',
|
||||
options=[ft.dropdown.Option(x) for x in mm.ModelSize.__args__], # __args__ is not perfect here. But works.
|
||||
),
|
||||
ft.Dropdown(
|
||||
ref=model_device_select,
|
||||
width=100,
|
||||
hint_text='device',
|
||||
value=page.client_storage.get('device_select') if page.client_storage.contains_key('device_select') else 'auto',
|
||||
options=[ft.dropdown.Option(x) for x in mm.Device.__args__] # __args__ is not perfect here. But works.
|
||||
),
|
||||
# ft.Dropdown(
|
||||
# ref=model_bits_select,
|
||||
# width=100,
|
||||
# hint_text='bits',
|
||||
# value='16bit',
|
||||
# options=[ft.dropdown.Option(x) for x in mm.ComputeType.__args__] # __args__ is not perfect here. But works.
|
||||
#),
|
||||
ft.IconButton(
|
||||
icon=ft.icons.START,
|
||||
ref=model_load_unload_button,
|
||||
on_click=lambda _: load_model(),
|
||||
),
|
||||
ft.ProgressRing(ref=model_loading_spinner, visible=False)
|
||||
])
|
||||
]),
|
||||
ft.Container(expand=True, padding=12, border=ft.border.all(2, 'grey'),
|
||||
alignment=ft.alignment.center,
|
||||
ref=output_text_container,
|
||||
content=ft.Column(
|
||||
[ft.Text('Nothing to see here!', text_align=ft.TextAlign.CENTER)],
|
||||
ref=output_text_col,
|
||||
expand=True,
|
||||
scroll=ft.ScrollMode.ADAPTIVE)),
|
||||
]), col=8)
|
||||
], expand=True),
|
||||
)
|
||||
|
||||
|
||||
ft.app(target=main)
|
65
nn_model_manager.py
Normal file
65
nn_model_manager.py
Normal file
@ -0,0 +1,65 @@
|
||||
import threading
|
||||
|
||||
from faster_whisper import WhisperModel
|
||||
import faster_whisper
|
||||
from typing import Literal, Iterable, Tuple
|
||||
|
||||
_model: WhisperModel | None = None
|
||||
|
||||
ModelSize = Literal["tiny", "base", "small", "medium", "large-v1", "large-v2"]
|
||||
Device = Literal["cuda", "cpu", "auto"]
|
||||
ComputeType = Literal["8bit", "16bit", "32bit"]
|
||||
|
||||
|
||||
def set_model(size: ModelSize, device: Device): #, compute_type: ComputeType):
|
||||
|
||||
'''
|
||||
compute = None
|
||||
if compute_type == '8bit':
|
||||
if device == 'cuda' or device == 'auto':
|
||||
compute = 'int8_float16'
|
||||
else:
|
||||
compute = 'int8'
|
||||
elif compute_type == '16bit':
|
||||
if device == 'cuda' or device == 'auto':
|
||||
compute = 'int8'
|
||||
else:
|
||||
raise Exception("Cannot do 16 bit computing on CPU")
|
||||
elif compute_type == '32bit':
|
||||
compute = 'float'
|
||||
else:
|
||||
raise Exception(f"Invalid Compute / Device configuration (device {device} with {compute_type})")
|
||||
'''
|
||||
|
||||
global _model
|
||||
_model = WhisperModel(size, device=device)
|
||||
|
||||
|
||||
def unload_model():
|
||||
if not is_model_loaded():
|
||||
return
|
||||
|
||||
global _model
|
||||
_model = None # TODO: check if this works
|
||||
|
||||
def is_model_loaded() -> bool:
|
||||
global _model
|
||||
return _model is not None
|
||||
|
||||
|
||||
def transcribe_from_file(mp3_path: str) -> Tuple[Iterable[faster_whisper.transcribe.Segment], faster_whisper.transcribe.TranscriptionInfo] | None:
|
||||
"""
|
||||
Transcribe audio from an MP3 file.
|
||||
Note that this can - and will - crash if you don't catch exceptions.
|
||||
|
||||
If the model isn't loaded yet, this will return None.
|
||||
Otherwise, it will return the raw transcription from `faster-whisper`.
|
||||
"""
|
||||
if not is_model_loaded():
|
||||
return None
|
||||
|
||||
global _model
|
||||
segments, info = _model.transcribe(mp3_path, beam_size=5)
|
||||
# transcribe, and throw all exceptions to application to handle
|
||||
|
||||
return segments, info
|
2043
poetry.lock
generated
Normal file
2043
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
pyproject.toml
Normal file
19
pyproject.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[tool.poetry]
|
||||
name = "fluesterpost"
|
||||
version = "0.1.0"
|
||||
description = "App for transcribing audio files to text"
|
||||
authors = ["Yandrik <me@yandrik.dev>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
flet = "^0.10.3"
|
||||
faster-whisper = "^0.9.0"
|
||||
pygame = "^2.5.2"
|
||||
torch = "2.0.0"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
285
temp
Normal file
285
temp
Normal file
@ -0,0 +1,285 @@
|
||||
{ '.': { '_i_files': [ 'getting-started.md',
|
||||
'inbox.md',
|
||||
'readme.md',
|
||||
'skill_stack.md',
|
||||
'player_famillarity.md',
|
||||
'todo.md']},
|
||||
'.foam': { '_i_files': [],
|
||||
'templates': { '_i_files': [ 'anki-cards.md',
|
||||
'your-first-template.md',
|
||||
'zettel-refs.md',
|
||||
'zettel.md']}},
|
||||
'.git': { '_i_files': [ 'description',
|
||||
'packed-refs',
|
||||
'HEAD',
|
||||
'config',
|
||||
'COMMIT_EDITMSG',
|
||||
'FETCH_HEAD',
|
||||
'ORIG_HEAD',
|
||||
'index'],
|
||||
'branches': {'_i_files': []},
|
||||
'hooks': { '_i_files': [ 'applypatch-msg.sample',
|
||||
'commit-msg.sample',
|
||||
'post-update.sample',
|
||||
'pre-applypatch.sample',
|
||||
'pre-commit.sample',
|
||||
'pre-merge-commit.sample',
|
||||
'pre-push.sample',
|
||||
'pre-receive.sample',
|
||||
'push-to-checkout.sample',
|
||||
'update.sample',
|
||||
'fsmonitor-watchman.sample',
|
||||
'pre-rebase.sample',
|
||||
'prepare-commit-msg.sample']},
|
||||
'info': {'_i_files': ['exclude']},
|
||||
'logs': { '_i_files': ['HEAD'],
|
||||
'refs': { '_i_files': [],
|
||||
'heads': {'_i_files': ['master']},
|
||||
'remotes': { '_i_files': [],
|
||||
'origin': { '_i_files': [ 'HEAD',
|
||||
'master']}}}},
|
||||
'objects': { '01': { '_i_files': [ '92178cee01a3ad18cb621cac1ba64ad8e4e8a3']},
|
||||
'02': { '_i_files': [ '8c8217c849a00a010fec6cfa99a9e92e8c9375',
|
||||
'557a1b00b8c95a84b967c50c1e1aa7974a69ce']},
|
||||
'05': { '_i_files': [ 'f4816f07fc4f96c0c2010cf0d7449f00fcce19',
|
||||
'0a51af521fb44d8aacd7604d14b8766238d66f']},
|
||||
'0b': { '_i_files': [ 'f3fdf9465baf34897ad3252be2341bc1bf7db8']},
|
||||
'0f': { '_i_files': [ 'd094c8d57913f667e462632d49b2f052bab5de']},
|
||||
'11': { '_i_files': [ 'ae42db7bb29c0a5f1987e75db0c49fbdf8f957']},
|
||||
'13': { '_i_files': [ 'b1d98f14fd2acff4afa59cf80bf7150a44d8d5']},
|
||||
'18': { '_i_files': [ 'b5e3934b5d99910733c118c634fcd17c58b369']},
|
||||
'1d': { '_i_files': [ '8661f5e25c86324e1bba1933948c3900126c06']},
|
||||
'20': { '_i_files': [ '0e31f1ae6fcde97e64e5dc1ea7d5f10008dea4']},
|
||||
'24': { '_i_files': [ '5d1d3c006b36d48b007298349f2f33bf3b14d8']},
|
||||
'28': { '_i_files': [ 'b6a59bbb64bd2705e586f53a880ce2d4c00515']},
|
||||
'2b': { '_i_files': [ '25ddb4990745c5df76289a21a4d7d6e333d054']},
|
||||
'30': { '_i_files': [ 'e393907000c0219d86dbffd28bb1e548c7a078']},
|
||||
'32': { '_i_files': [ '711ab584d672d6e5aa62da57b5a45c8d231689']},
|
||||
'34': { '_i_files': [ '7998770c00191c4a76a470c5a4a1311c2265fc']},
|
||||
'35': { '_i_files': [ 'bb5e4cb76375d51162c6143c3ebd998f469714']},
|
||||
'38': { '_i_files': [ 'b873fbb24e1d38a5896f9161b0d4e98aec472f']},
|
||||
'39': { '_i_files': [ 'cdb1347792036be3a4ceb5fe49faace3c5f579']},
|
||||
'3a': { '_i_files': [ 'ba671d641c06e76036016b91e577f91a4b02ad',
|
||||
'44ad749af1d6dc5866356beb964f766eee5128']},
|
||||
'42': { '_i_files': [ 'a79846fbcee6a7b10a0ce6a6fca8994fc80876']},
|
||||
'45': { '_i_files': [ 'dc2d782c3fe9faf18d6517f11f183cf78f177a']},
|
||||
'53': { '_i_files': [ '196f6105b690fd51e81484deb1321976b1daa9']},
|
||||
'56': { '_i_files': [ '729062c3620d0cc078c7b7033f93e64982bfc4',
|
||||
'b44a575108bdb87f385b47c30f68d418a3eaaa']},
|
||||
'5c': { '_i_files': [ '13490222138a9d52e50f4782969284fd43e8ce']},
|
||||
'5f': { '_i_files': [ '3ff320973f57bb17f5abd596af5f73b15bd601']},
|
||||
'67': { '_i_files': [ 'e12b1d2f0a5307acc8f06fa0edb883ef86acfe']},
|
||||
'6e': { '_i_files': [ 'd2d0031d1ffb6c25f58046a1971661e0cf1cc4']},
|
||||
'70': { '_i_files': [ '92cf531a29dd7fdc0a4d09acb194c4a57d8803',
|
||||
'b9d0a5d2154b5be599f50ca39883642ef681ec']},
|
||||
'71': { '_i_files': [ '45b359be3dd773a9ccbc6aaeba7b9be9ba074c']},
|
||||
'74': { '_i_files': [ '83a25779adf27f8c37221076cb0e61a2025a5c']},
|
||||
'75': { '_i_files': [ '490a494632a140b2119a894e6521161d302769']},
|
||||
'76': { '_i_files': [ 'd9e26bda3f744c90704e94004f2350a3b145ba']},
|
||||
'78': { '_i_files': [ '4442d08c18a84bd0570ecc56518073f4a02430']},
|
||||
'79': { '_i_files': [ 'bcbd1fa1badcf3677e52ddd76721bf7ddfc115']},
|
||||
'7a': { '_i_files': [ '5177de86b242d198edb87bd20a5613f106e596']},
|
||||
'82': { '_i_files': [ '65b4da954fcdc135ffaf0019b31cdb228e59ec',
|
||||
'a06086a94bc1fa94b8f054e7c48c3a8d45a580']},
|
||||
'85': { '_i_files': [ '29e2398baf641413f194a652305c3632c757c7']},
|
||||
'88': { '_i_files': [ '83fca5338d4419f32e57377106986fea85f81e']},
|
||||
'8c': { '_i_files': [ '972d8832336104982d5f66037246ed75050c94']},
|
||||
'8d': { '_i_files': [ '200ac5a1dd8d235bb786bcdf94527297c6019c']},
|
||||
'90': { '_i_files': [ 'f26b565b09edc34d98bfe9b01dfc88d01b3e05']},
|
||||
'92': { '_i_files': [ '048c50fec01e3a6b9cc7d301b48f62f0d47ad6',
|
||||
'b464c0c03c745287234e4ccbbd0dc5285fc545']},
|
||||
'93': { '_i_files': [ 'a00093b5319ab79d4b7a9c490cb4e2a661907e']},
|
||||
'94': { '_i_files': [ '05bfdc224d3e5dfe9fe6116a4fd2955ab10a72']},
|
||||
'95': { '_i_files': [ '0027f29e86396e4d7416487d16100fc559fb2d',
|
||||
'7a01dc4f30e7f3424d004a17d7b5571d0e3879']},
|
||||
'96': { '_i_files': [ '2e3e24df9db27f23050dc22c057793b40455cc']},
|
||||
'97': { '_i_files': [ 'f9215e573ba5b7d2273bc7b368d025b37f0f7a']},
|
||||
'9c': { '_i_files': [ '6b61bb1fdfba8f5b242f7f98284a528a9e17c8']},
|
||||
'9d': { '_i_files': [ '9fe831567fe46ae777733412cad5157982c44a']},
|
||||
'9e': { '_i_files': [ '26dfeeb6e641a33dae4961196235bdb965b21b',
|
||||
'a7cd9ba86b57ada4037f24526eee3579efd26d']},
|
||||
'_i_files': [],
|
||||
'a6': { '_i_files': [ 'ffab47a1ec45c43ed1927ddc6097a088fcad4f']},
|
||||
'a8': { '_i_files': [ 'a84552bead93885c351952ca9781d4ddbe5780',
|
||||
'edeadbf5b40ab9eb7598d729263e270ddd9e63']},
|
||||
'b3': { '_i_files': [ '2e1b1d3d1bbd905aca74818820b184001fea13']},
|
||||
'b5': { '_i_files': [ 'cd6cf2d939352ff74ac7ca30018decc0647b65']},
|
||||
'b9': { '_i_files': [ '0227ebf336c51d2364f2c6ed9cb9896a236f49']},
|
||||
'ba': { '_i_files': [ '4fad78807ee9ec608eca8c6355b54146b52a68']},
|
||||
'bb': { '_i_files': [ '90408b3c6db106c5fe19e6bd142d2bf6b7b528']},
|
||||
'bc': { '_i_files': [ '4057206a45a261e46f8b6f4896d7c5b843876f']},
|
||||
'c0': { '_i_files': [ 'd770dce01d3d0ca290ddd7e494714960c77326',
|
||||
'190d5c75633f5ea107de72845dc5387a518400',
|
||||
'8336e9a181a2788e9bbcba484415698701638a',
|
||||
'0153d83030dad37d32e38628fe10e304f3377c']},
|
||||
'c3': { '_i_files': [ '31566dd29ec2f17a6ba736a2fe0a45fa65e89d']},
|
||||
'c4': { '_i_files': [ '1c5acd753957dda0d5bdeb25fb47e793080453']},
|
||||
'c8': { '_i_files': [ 'c365d89b8b1bad158cba37051fc32cbc784c65']},
|
||||
'ce': { '_i_files': [ '8bbb33980fd7c40f017d1063950fd8fdab2d6b']},
|
||||
'cf': { '_i_files': [ 'dc0b4420769cfc9219fa741447ae43b8129b75']},
|
||||
'd8': { '_i_files': [ 'ee8b9b4c05ce67098abbdfe9e351d63b21f18e',
|
||||
'eeb04612a7818d4c0872b312f4e2241f996190']},
|
||||
'd9': { '_i_files': [ '54566657c342600667bb4836f83a8407e32807']},
|
||||
'dd': { '_i_files': [ '2a192a1c242d0691fb8db2fdd08733c63a0dfb']},
|
||||
'e1': { '_i_files': [ '2b226b929c2c1996829b1dd45539f71fba1d4b']},
|
||||
'e5': { '_i_files': [ 'c523af28c3a765a63e7e5fbf44a40a44cd0d05']},
|
||||
'e6': { '_i_files': [ '80ac775c8245442f5dec43644a2aa7aac6dbc2',
|
||||
'a347474ea106d1b0979e5aead00e695710eb34']},
|
||||
'ea': { '_i_files': [ '514bc29f92feccb4fc8fb53dcbee24cb9863b2']},
|
||||
'ee': { '_i_files': [ '41eb2f83cb8a36fb5773cc3501a73d6b5a484f']},
|
||||
'ef': { '_i_files': [ '998d5afc023c58a624a7b01261f15b416ef286']},
|
||||
'f1': { '_i_files': [ '3dba7d7450e0ea935bc15b087c2e62e3f9f62b',
|
||||
'053bfb26234ebea971e874a0150b221f5f598d',
|
||||
'ae4aac4535bfe3d569f4376e1992acdd5e5fde']},
|
||||
'f2': { '_i_files': [ '6065a6e08f05d5d0dad0e8f9de825648938a04',
|
||||
'432313c1f432e922ed0dc4baea17c8d5043cfb']},
|
||||
'f3': { '_i_files': [ 'ebbe30d46a1d7d56b95ab0d64f64b7cfede59a']},
|
||||
'f6': { '_i_files': [ 'd65179b69864c0b1d88f4fa32b35a9089bfa83']},
|
||||
'f8': { '_i_files': [ '5851a1afaca442587934e62f1b182f3e09e3d1',
|
||||
'af88bf54d71d06fba7335ff4662722019a1f9b',
|
||||
'61ad4ac94746c2347df1aa6ae2dafafe7f9538']},
|
||||
'fb': { '_i_files': [ '8be1fd027914d656729cc5217b6f9a7c1cb4f3']},
|
||||
'fd': { '_i_files': [ '0d011ded41de68be2f0039c97890ed4117f7ff',
|
||||
'204664448169af25c5126c3c8c11917ec5cb6f']},
|
||||
'info': {'_i_files': []},
|
||||
'pack': { '_i_files': [ 'pack-8b0ef94d8431e1b37110e10fedb27318a852f391.pack',
|
||||
'pack-8b0ef94d8431e1b37110e10fedb27318a852f391.idx']}},
|
||||
'refs': { '_i_files': [],
|
||||
'heads': {'_i_files': ['master']},
|
||||
'remotes': { '_i_files': [],
|
||||
'origin': { '_i_files': [ 'HEAD',
|
||||
'master']}},
|
||||
'tags': {'_i_files': []}}},
|
||||
'.obsidian': { '_i_files': [ 'hotkeys.json',
|
||||
'core-plugins.json',
|
||||
'core-plugins-migration.json',
|
||||
'app.json',
|
||||
'appearance.json',
|
||||
'workspace.json',
|
||||
'graph.json',
|
||||
'community-plugins.json'],
|
||||
'plugins': { '_i_files': [],
|
||||
'dataview': { '_i_files': [ 'manifest.json',
|
||||
'main.js',
|
||||
'styles.css']},
|
||||
'obsidian-git': { '_i_files': [ 'manifest.json',
|
||||
'main.js',
|
||||
'styles.css',
|
||||
'data.json']}}},
|
||||
'.vscode': { '_i_files': [ 'extensions.json',
|
||||
'foam.json',
|
||||
'settings.json',
|
||||
'spellright.dict']},
|
||||
'_layouts': {'_i_files': ['home.html', 'page.html']},
|
||||
'abawo': { '_i_files': ['corporate-design.md'],
|
||||
'keant': { '_i_files': [],
|
||||
'emc': {'_i_files': ['keant-emc-testing.md']}}},
|
||||
'ai': { '_i_files': [ 'clustering.md',
|
||||
'gradient_descent.md',
|
||||
'nn_neurons.md'],
|
||||
'basics': { '_i_files': [ 'embedding.md',
|
||||
'latent-space.md',
|
||||
'few-shot.md',
|
||||
'zero-shot.md']},
|
||||
'combination': {'_i_files': ['query-net.md']},
|
||||
'ffn': {'_i_files': ['multi_layer_perceptron.md']},
|
||||
'gan': {'_i_files': ['gan.md']},
|
||||
'learn': { '_i_files': [ 'active_learning.md',
|
||||
'backpropagation_learn.md',
|
||||
'fitting.md',
|
||||
'learning_kinds.md',
|
||||
'semi-supervised_text_classification.md']},
|
||||
'llm': { '_i_files': [ 'autoregressive.md',
|
||||
'language_models.md',
|
||||
'llm.md'],
|
||||
'google': {'_i_files': ['bard.md']},
|
||||
'langchain': {'_i_files': ['langchain.md']},
|
||||
'openai': {'_i_files': ['openai.md', 'gpt4.md']},
|
||||
'oss': {'_i_files': ['vicuna.md']},
|
||||
'prompting': {'_i_files': ['cot.md']},
|
||||
'research': { '_i_files': [ 'using-llms-for-reading-thoughts.md']}},
|
||||
'multi-receptors': {'_i_files': ['nn-neurotransmitters.md']},
|
||||
'news': {'_i_files': ['google-codered.md', 'we-have-no-moat.md']},
|
||||
'rnn': { '_i_files': [ 'bci-rnn.md',
|
||||
'bionets.md',
|
||||
'echo-state-networks.md',
|
||||
'rnn.md',
|
||||
'rnn_overlearning.md',
|
||||
'rnn_timer_example.md',
|
||||
'rnn_usefulness.md',
|
||||
'rockets_rnn.md']},
|
||||
'tools': { '_i_files': [ 'chatgpt.md',
|
||||
'github-copilot.md',
|
||||
'vector-database.md',
|
||||
'bing-chat.md']}},
|
||||
'assets': {'_i_files': [], 'css': {'_i_files': ['style.scss']}},
|
||||
'attachments': { '_i_files': [ '2022-04-26-19-39-39.png',
|
||||
'Josefin_Sans.zip',
|
||||
'Montserrat.zip',
|
||||
'abawo-color-palette-v1.pdf',
|
||||
'abawo-color-palette-v1.png',
|
||||
'abawo-logo-square.png',
|
||||
'abawo-logo-square.svg',
|
||||
'abawo-logo.png',
|
||||
'abawo-logo.svg',
|
||||
'bid_ask_spread.png',
|
||||
'clustering_ni-seminar_fabian-karl_2022-02-03-12-37-28.png',
|
||||
'foam-icon.png',
|
||||
'gamedev_tutorial_properties.png',
|
||||
'gen-discr-training.svg',
|
||||
'jaeger_feedforward.png',
|
||||
'rnn-esn-talk_daniel-ruepp_ni-seminar_3.2.2022.png',
|
||||
'rnn_feedforward_net.png',
|
||||
'rnn_structure_jaeger_tutorial_timer.png',
|
||||
'viofs-folder-on-iso.png',
|
||||
'virt-manager-folder-share.png',
|
||||
'2023-05-12-12-09-30-langchain-memory.png',
|
||||
'2023-05-19-12-22-28.png',
|
||||
'cot-prompting-yaoTreeThoughtsDeliberate2023.png',
|
||||
'evalResults-yaoTreeThoughtsDeliberate2023.png',
|
||||
'evalTable-yaoTreeThoughtsDeliberate2023.png',
|
||||
'titleImg-panDragYourGAN.png',
|
||||
'tot-prompting-yaoTreeThoughtsDeliberate2023.png']},
|
||||
'cad': {'_i_files': ['CalculiX.md']},
|
||||
'cs': {'_i_files': ['reservoir-computing.md']},
|
||||
'furntiture': {'_i_files': ['element-shelving-system.md']},
|
||||
'gamedev': { '_i_files': ['prototyping.md'],
|
||||
'mechanics': {'_i_files': ['difficulty.md', 'tutorial.md']},
|
||||
'target_audience': {'_i_files': ['game_literacy.md']}},
|
||||
'helium': { '_i_files': ['helium.md'],
|
||||
'manufacturing': { '_i_files': [],
|
||||
'hs1': { '_i_files': [ 'hs1-ethernet-driver.md',
|
||||
'hs1-processor.md',
|
||||
'hs1.md']}}},
|
||||
'investing': { '_i_files': [ 'diversification.md',
|
||||
'international_investing.md',
|
||||
'value_vs_growth_stocks.md'],
|
||||
'brokers': { '_i_files': [ 'broker.md',
|
||||
'zero_commission_brokers.md']},
|
||||
'value': { '_i_files': [ 'bottom_up_market_research.md',
|
||||
'in-depth_company_research.md',
|
||||
'value_investing.md']}},
|
||||
'job_search': {'_i_files': ['ats.md', 'resume_writing.md']},
|
||||
'linux': { '_i_files': [],
|
||||
'kvm': { '_i_files': [ 'kvm-virtualization.md',
|
||||
'libvirt.md',
|
||||
'libvirtd-folder-share-windows-guest.md',
|
||||
'qemu.md',
|
||||
'virt-manager.md']}},
|
||||
'manufacturing': { '_i_files': [],
|
||||
'oem': { '_i_files': [ 'oem-in-china.md',
|
||||
'oem.md',
|
||||
'value-added-reseller.md']}},
|
||||
'maths': { '_i_files': [],
|
||||
'game_theory': { '_i_files': [ 'positive_sum_game.md',
|
||||
'zero_sum_game.md']}},
|
||||
'meditation': {'_i_files': ['shunya.md']},
|
||||
'people': {'_i_files': [], 'developers': {'_i_files': ['hwchase17.md']}},
|
||||
'psychology': {'_i_files': ['the-faschist-mindset.md']},
|
||||
'robotics': {'_i_files': ['arco.md']},
|
||||
'skills': { '_i_files': [ 'jack_of_all_trades_master_of_one.md',
|
||||
'skill_stacking.md']},
|
||||
'uni': { '_i_files': [],
|
||||
's5': { '_i_files': ['thomas-thuem-cb-pruefung-stuff.md'],
|
||||
'compilers': {'_i_files': ['lexing_anki.md']},
|
||||
'grn': {'_i_files': ['questions.md']}}}}
|
46
utils.py
Normal file
46
utils.py
Normal file
@ -0,0 +1,46 @@
|
||||
from collections import defaultdict
|
||||
import os
|
||||
|
||||
from typing import DefaultDict, Dict, List
|
||||
|
||||
|
||||
def tree() -> DefaultDict:
|
||||
return defaultdict(tree)
|
||||
|
||||
|
||||
def get_last_segment(path: str) -> str:
|
||||
if path[-1] == '/':
|
||||
path = path[:-1]
|
||||
return path.split(os.sep)[-1]
|
||||
|
||||
|
||||
FILES_KEY = '_i_files'
|
||||
|
||||
|
||||
def build_file_tree(root_dir: str) -> DefaultDict:
|
||||
file_tree = tree()
|
||||
root_dir = os.path.normpath(root_dir) # Normalize the path
|
||||
|
||||
for dirpath, dirnames, files in os.walk(root_dir):
|
||||
# Get the subdirectory path relative to the root directory
|
||||
subdir = os.path.relpath(dirpath, root_dir)
|
||||
|
||||
# Split the path into components to navigate the nested dictionary
|
||||
path_components = subdir.split(os.sep)
|
||||
|
||||
# Navigate to the current subdirectory in the file tree
|
||||
current_subdir = file_tree
|
||||
for component in path_components:
|
||||
current_subdir = current_subdir[component]
|
||||
|
||||
# Add files to the current subdirectory in the file tree
|
||||
current_subdir[FILES_KEY] = files
|
||||
|
||||
return file_tree
|
||||
|
||||
|
||||
# Function to convert defaultdict to dict (for readability)
|
||||
def defaultdict_to_dict(d: defaultdict) -> dict:
|
||||
if isinstance(d, defaultdict):
|
||||
d = {k: defaultdict_to_dict(v) for k, v in d.items()}
|
||||
return d
|
Loading…
Reference in New Issue
Block a user