first commit

This commit is contained in:
mike
2025-12-17 13:07:01 +01:00
commit c0ca907b01
19 changed files with 3838 additions and 0 deletions

305
.gitignore vendored Executable file
View File

@@ -0,0 +1,305 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
free-cluely/node_modules/
jspm_packages/
dist-electron/
dist/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
#####
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

11
.idea/go.imports.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoImports">
<option name="excludedPackages">
<array>
<option value="github.com/pkg/errors" />
<option value="golang.org/x/net/context" />
</array>
</option>
</component>
</project>

View File

@@ -0,0 +1,621 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AddOperatorModifier" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="AddVarianceModifier" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ArrayInDataClass" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="BintrayPublishingPlugin" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="BooleanLiteralArgument" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="CanBeParameter" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CanBePrimaryConstructorProperty" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CanBeVal" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CanSealedSubClassBeObject" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="CascadeIf" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="CdiAlternativeInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiDecoratorInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiDisposerMethodInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiDomBeans" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CdiInjectInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiInjectionPointsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CdiInterceptorInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiManagedBeanInconsistencyInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiNormalScopeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CdiObservesInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiScopeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CdiSpecializesInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiStereotypeInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiStereotypeRestrictionsInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiTypedAnnotationInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CdiUnknownProducersForDisposerMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CdiUnproxyableBeanTypesInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="ChangeToMethod" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ChangeToOperator" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ClashingTraitMethods" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ClassCanBeRecord" enabled="false" level="WEAK WARNING" enabled_by_default="false">
<option name="myConversionStrategy" value="SILENTLY" />
</inspection_tool>
<inspection_tool class="ClassName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ComplexRedundantLet" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ComposeUnknownKeys" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="ComposeUnknownValues" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="ConflictingExtensionProperty" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ConstPropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ConstantConditionIf" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ControlFlowWithEmptyBody" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ConvertCallChainIntoSequence" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ConvertLambdaToReference" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ConvertNaNEquality" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ConvertPairConstructorToToFunction" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ConvertReferenceToLambda" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ConvertSecondaryConstructorToPrimary" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ConvertToStringTemplate" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ConvertTryFinallyToUseCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ConvertTwoComparisonsToRangeCheck" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="CopyWithoutNamedArguments" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="CssBrowserCompatibilityForProperties" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CssConvertColorToHexInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CssConvertColorToRgbInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CssMissingSemicolon" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CucumberExamplesColon" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CucumberMissedExamples" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CucumberTableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CucumberUndefinedStep" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DataClassPrivateConstructor" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DeferredIsResult" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="DeferredResultUnused" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DelegatesTo" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DelegationToVarProperty" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DeprecatedCallableAddReplaceWith" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="DeprecatedGradleDependency" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DeprecatedMavenDependency" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DestructuringWrongName" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DifferentKotlinGradleVersion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DifferentKotlinMavenVersion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DifferentMavenStdlibVersion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DifferentStdlibGradleVersion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DockerFileAddOrCopySemantic" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DockerFileArgumentCount" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="DockerFileAssignments" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="EmptyRange" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="EmptyWebServiceClass" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="EnumEntryName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="EqualsOrHashCode" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ExplicitThis" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="FakeJvmFieldConstant" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="FoldInitializerAndIfToElvis" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ForEachParameterNotUsed" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="ERROR" enabled_by_default="true">
<option name="configuration">
<list>
<option value="\Codeception\Util\Debug::debug" />
<option value="\Codeception\Util\Debug::pause" />
<option value="\Doctrine\Common\Util\Debug::dump" />
<option value="\Doctrine\Common\Util\Debug::export" />
<option value="\Illuminate\Support\Debug\Dumper::dump" />
<option value="\Symfony\Component\Debug\Debug::enable" />
<option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
<option value="\Symfony\Component\Debug\ErrorHandler::register" />
<option value="\Symfony\Component\Debug\ExceptionHandler::register" />
<option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
<option value="\Zend\Debug\Debug::dump" />
<option value="\Zend\Di\Display\Console::export" />
<option value="dd" />
<option value="debug_print_backtrace" />
<option value="debug_zval_dump" />
<option value="dpm" />
<option value="dpq" />
<option value="dsm" />
<option value="dump" />
<option value="dvm" />
<option value="error_log" />
<option value="kpr" />
<option value="phpinfo" />
<option value="print_r" />
<option value="var_dump" />
<option value="var_export" />
<option value="wp_die" />
<option value="xdebug_break" />
<option value="xdebug_call_class" />
<option value="xdebug_call_file" />
<option value="xdebug_call_function" />
<option value="xdebug_call_line" />
<option value="xdebug_code_coverage_started" />
<option value="xdebug_debug_zval" />
<option value="xdebug_debug_zval_stdout" />
<option value="xdebug_dump_superglobals" />
<option value="xdebug_enable" />
<option value="xdebug_get_code_coverage" />
<option value="xdebug_get_collected_errors" />
<option value="xdebug_get_declared_vars" />
<option value="xdebug_get_function_stack" />
<option value="xdebug_get_headers" />
<option value="xdebug_get_monitored_functions" />
<option value="xdebug_get_profiler_filename" />
<option value="xdebug_get_stack_depth" />
<option value="xdebug_get_tracefile_name" />
<option value="xdebug_is_enabled" />
<option value="xdebug_memory_usage" />
<option value="xdebug_peak_memory_usage" />
<option value="xdebug_print_function_stack" />
<option value="xdebug_start_code_coverage" />
<option value="xdebug_start_error_collection" />
<option value="xdebug_start_function_monitor" />
<option value="xdebug_start_trace" />
<option value="xdebug_stop_code_coverage" />
<option value="xdebug_stop_error_collection" />
<option value="xdebug_stop_function_monitor" />
<option value="xdebug_stop_trace" />
<option value="xdebug_time_index" />
<option value="xdebug_var_dump" />
</list>
</option>
<option name="migratedIntoUserSpace" value="true" />
</inspection_tool>
<inspection_tool class="FunctionName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="FunctionWithLambdaExpressionBody" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="GherkinBrokenTableInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="GherkinMisplacedBackground" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="GherkinScenarioToScenarioOutline" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="GrAnnotationReferencingUnknownIdentifiers" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrDeprecatedAPIUsage" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrEqualsBetweenInconvertibleTypes" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrFinalVariableAccess" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrMethodMayBeStatic" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrNamedVariantLabels" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrPackage" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrReassignedInClosureLocalVar" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrUnnecessaryAlias" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="GrUnnecessaryDefModifier" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrUnnecessaryPublicModifier" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrUnnecessarySemicolon" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrUnresolvedAccess" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyAccessToStaticFieldLockedOnInstance" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyAccessibility" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyAssignabilityCheck" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyConditionalWithIdenticalBranches" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyConstantConditional" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyConstantIfStatement" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyConstructorNamedArguments" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyDivideByZero" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyDocCheck" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="GroovyDoubleNegation" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyDuplicateSwitchBranch" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyEmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyFallthrough" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyGStringKey" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyIfStatementWithIdenticalBranches" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyImplicitNullArgumentCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyInArgumentCheck" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyInfiniteLoopStatement" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyInfiniteRecursion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyLabeledStatement" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyMissingReturnStatement" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyPointlessBoolean" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyResultOfObjectAllocationIgnored" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovySillyAssignment" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovySynchronizationOnNonFinalField" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovySynchronizationOnVariableInitializedWithLiteral" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyTrivialConditional" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyTrivialIf" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUncheckedAssignmentOfMemberOfRawType" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUnnecessaryContinue" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUnnecessaryReturn" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUnreachableStatement" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUnsynchronizedMethodOverridesSynchronizedMethod" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUnusedAssignment" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUnusedCatchParameter" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUnusedDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUnusedIncOrDec" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyVariableNotAssigned" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HasPlatformType" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="16">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="style" />
<item index="7" class="java.lang.String" itemvalue="nuxt-img" />
<item index="8" class="java.lang.String" itemvalue="nuxt-picture" />
<item index="9" class="java.lang.String" itemvalue="nuxt-content" />
<item index="10" class="java.lang.String" itemvalue="contentdoc" />
<item index="11" class="java.lang.String" itemvalue="contentquery" />
<item index="12" class="java.lang.String" itemvalue="nuxt" />
<item index="13" class="java.lang.String" itemvalue="ogimagescreenshot" />
<item index="14" class="java.lang.String" itemvalue="seokit" />
<item index="15" class="java.lang.String" itemvalue="nuxtlink" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="HttpRequestContentLengthIsIgnored" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestPlaceholder" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IfThenToElvis" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="IfThenToSafeAccess" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ImplicitNullableNothingType" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ImplicitThis" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ImplicitlyExposedWebServiceMethods" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IntroduceWhenSubject" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JBoss" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JCenterRepository" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JSConstructorReturnsPrimitive" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JSObsoletePrivateAccessSyntax" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JSUnresolvedReactComponent" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JavaCollectionsStaticMethod" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JavaCollectionsStaticMethodOnImmutableList" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JavaFxColorRgb" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JavaFxDefaultTag" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JavaFxEventHandler" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JavaFxRedundantPropertyValue" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JavaFxResourcePropertyValue" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JavaFxUnresolvedFxIdReference" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JavaFxUnresolvedStyleClassReference" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JavaFxUnusedImports" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JavaMapForEach" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JavaStylePropertiesInvocation" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="JoinDeclarationAndAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JpaAttributeMemberSignatureInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaAttributeTypeInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaConfigDomFacetInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JpaDataSourceORMDomInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaDataSourceORMInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaDomInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaEntityListenerInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaEntityListenerWarningsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JpaMissingIdInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaModelReferenceInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaORMDomInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaObjectClassSignatureInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaQlInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="JpaQueryApiInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="KDocUnresolvedReference" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="KotlinCovariantEquals" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="KotlinDeprecation" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="KotlinDoubleNegation" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="KotlinEqualsBetweenInconvertibleTypes" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="KotlinInternalInJava" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="KotlinInvalidBundleOrProperty" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="KotlinMavenPluginPhase" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="KotlinRedundantOverride" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="KotlinSealedInheritorsInJava" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="KotlinTestJUnit" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="KotlinThrowableNotThrown" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="KotlinUnusedImport" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LSPLocalInspectionTool" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LateinitVarOverridesLateinitVar" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LeakingThis" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LessResolvedByNameOnly" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="LessUnresolvedMixin" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LessUnresolvedVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LiftReturnOrAssignment" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="LocalVariableName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="LoopToCallChain" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="MainFunctionReturnUnit" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MapGetWithNotNullAssertionOperator" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="MayBeConstant" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="MemberVisibilityCanBePrivate" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="MicronautDataMethodInconsistencyInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="MicronautDataRepositoryMethodParametersInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MicronautDataRepositoryMethodReturnTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MigrateDiagnosticSuppression" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoreObjectMethods" value="true" />
<option name="ignoreAnonymousClassMethods" value="false" />
</inspection_tool>
<inspection_tool class="MnProperties" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MnUnresolvedPathVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MnYaml" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MongoJSDeprecationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MongoJSResolveInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MongoJSSideEffectsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MoveLambdaOutsideParentheses" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="MoveSuspiciousCallableReferenceIntoParentheses" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="MoveVariableDeclarationIntoWhen" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="MsOrderByInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="MultipleMethodDesignatorsInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="MultipleRepositoryUrls" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MysqlLoadDataPathInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MysqlParsingInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NestedLambdaShadowedImplicitParameter" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="NewInstanceOfSingleton" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NonJaxWsWebServices" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NullChecksToSafeCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="NullableBooleanElvis" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ObjectLiteralToLambda" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ObjectPropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ObsoleteExperimentalCoroutines" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="ObsoleteKotlinJsPackages" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="OneWayWebMethod" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="OptionalExpectation" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="OraMissingBodyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OraOverloadInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OraUnmatchedForwardDeclarationInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="OverridingDeprecatedMember" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PackageDirectoryMismatch" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="PackageName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PathAnnotation" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PgSelectFromProcedureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PlatformExtensionReceiverOfInline" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PrivatePropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ProtectedInFinal" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyArgumentEqualDefaultInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PyBehaveInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyClassicStyleClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
<list size="2">
<item index="0" class="java.lang.String" itemvalue="2.7" />
<item index="1" class="java.lang.String" itemvalue="3.11" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyMandatoryEncodingInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyMissingOrEmptyDocstringInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="RecursiveEqualsCall" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RecursivePropertyAccessor" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantAsSequence" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantAsync" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantCompanionReference" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantElseInIf" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="RedundantElvisReturnNull" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantEmptyInitializerBlock" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="RedundantEnumConstructorInvocation" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantExplicitType" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantGetter" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantIf" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantLambdaArrow" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantLambdaOrAnonymousFunction" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantModalityModifier" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantNullableReturnType" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantObjectTypeCheck" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="RedundantRequireNotNullCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantReturnLabel" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantRunCatching" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantSamConstructor" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantSemicolon" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantSetter" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantSuspendModifier" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantUnitExpression" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantUnitReturnType" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantVisibilityModifier" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantWith" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveCurlyBracesFromTemplate" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveEmptyClassBody" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="RemoveEmptyParenthesesFromAnnotationEntry" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveEmptyParenthesesFromLambdaCall" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="RemoveEmptyPrimaryConstructor" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveEmptySecondaryConstructorBody" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveExplicitSuperQualifier" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveExplicitTypeArguments" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveForLoopIndices" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveRedundantBackticks" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveRedundantCallsOfConversionMethods" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveRedundantQualifierName" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveRedundantSpreadOperator" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveSetterParameterType" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveSingleExpressionStringTemplate" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RemoveToStringInStringTemplate" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceArrayEqualityOpWithArraysEquals" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceArrayOfWithLiteral" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceAssertBooleanWithAssertEquality" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceAssociateFunction" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceCallWithBinaryOperator" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceGetOrSet" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ReplaceGuardClauseWithFunctionCall" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ReplaceIsEmptyWithIfEmpty" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceJavaStaticMethodWithKotlinAnalog" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceManualRangeWithIndicesCalls" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ReplaceNegatedIsEmptyWithIsNotEmpty" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplacePutWithAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceRangeStartEndInclusiveWithFirstLast" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceRangeToWithUntil" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceSizeCheckWithIsNotEmpty" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceSizeZeroCheckWithIsEmpty" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceStringFormatWithLiteral" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ReplaceSubstringWithDropLast" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ReplaceSubstringWithIndexingOperation" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ReplaceSubstringWithSubstringAfter" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ReplaceSubstringWithSubstringBefore" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ReplaceSubstringWithTake" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ReplaceToStringWithStringTemplate" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="ReplaceToWithInfixForm" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceWithEnumMap" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceWithIgnoreCaseEquals" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ReplaceWithOperatorAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RestParamTypeInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="RestResourceMethodInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="RestWrongDefaultValueInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="SafeCastWithReturn" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SassScssResolvedByNameOnly" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SassScssUnresolvedMixin" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SassScssUnresolvedPlaceholderSelector" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SassScssUnresolvedVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ScopeFunctionConversion" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="SecondUnsafeCall" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="optionConfiguration">
<list>
<option value="barryvdh/laravel-debugbar" />
<option value="behat/behat" />
<option value="brianium/paratest" />
<option value="codeception/codeception" />
<option value="codedungeon/phpunit-result-printer" />
<option value="composer/composer" />
<option value="doctrine/coding-standard" />
<option value="filp/whoops" />
<option value="friendsofphp/php-cs-fixer" />
<option value="humbug/humbug" />
<option value="infection/infection" />
<option value="jakub-onderka/php-parallel-lint" />
<option value="johnkary/phpunit-speedtrap" />
<option value="kalessil/production-dependencies-guard" />
<option value="mikey179/vfsStream" />
<option value="mockery/mockery" />
<option value="mybuilder/phpunit-accelerator" />
<option value="orchestra/testbench" />
<option value="pdepend/pdepend" />
<option value="phan/phan" />
<option value="phing/phing" />
<option value="phpcompatibility/php-compatibility" />
<option value="phpmd/phpmd" />
<option value="phpro/grumphp" />
<option value="phpspec/phpspec" />
<option value="phpspec/prophecy" />
<option value="phpstan/phpstan" />
<option value="phpunit/phpunit" />
<option value="povils/phpmnd" />
<option value="roave/security-advisories" />
<option value="satooshi/php-coveralls" />
<option value="sebastian/phpcpd" />
<option value="slevomat/coding-standard" />
<option value="spatie/phpunit-watcher" />
<option value="squizlabs/php_codesniffer" />
<option value="sstalle/php7cc" />
<option value="symfony/debug" />
<option value="symfony/maker-bundle" />
<option value="symfony/phpunit-bridge" />
<option value="symfony/var-dumper" />
<option value="vimeo/psalm" />
<option value="wimg/php-compatibility" />
<option value="wp-coding-standards/wpcs" />
<option value="yiisoft/yii2-coding-standards" />
<option value="yiisoft/yii2-debug" />
<option value="yiisoft/yii2-gii" />
<option value="zendframework/zend-coding-standard" />
<option value="zendframework/zend-debug" />
<option value="zendframework/zend-test" />
</list>
</option>
</inspection_tool>
<inspection_tool class="SelfAssignment" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SelfReferenceConstructorParameter" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SetterBackingFieldAssignment" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SimpleRedundantLet" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SimplifiableCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SimplifiableCallChain" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SimplifyAssertNotNull" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="SimplifyBooleanWithConstants" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SimplifyNegatedBinaryExpression" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SimplifyNestedEachInScopeFunction" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SimplifyWhenWithBooleanConstantCondition" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SingletonConstructor" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="SortModifiers" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SqlAddNotNullColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlAggregatesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlAmbiguousColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlAutoIncrementDuplicateInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlCallNotationInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="SqlCaseVsCoalesceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlCaseVsIfInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlCheckUsingColumnsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlConstantConditionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlCurrentSchemaInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDeprecateTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDerivedTableAliasInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDialectInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDropIndexedColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDtInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDuplicateColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlErrorHandlingInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="SqlIdentifierInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlIdentifierLengthInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="SqlIllegalCursorStateInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlInsertIntoGeneratedColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlInsertNullIntoNotNullInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlInsertValuesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlJoinWithoutOnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlMisleadingReferenceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlMissingReturnInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="SqlMultipleLimitClausesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlNullComparisonInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlRedundantAliasInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlRedundantCodeInCoalesceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlRedundantElseNullInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlRedundantLimitInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlRedundantOrderingDirectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlResolveInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="SqlShadowingAliasInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlShouldBeInGroupByInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlSideEffectsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlSignatureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlStorageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlStringLengthExceededInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlTransactionStatementInTriggerInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlTriggerTransitionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlUnicodeStringLiteralInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlUnreachableCodeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlUnusedCteInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlUnusedSubqueryItemInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlUnusedVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlWithoutWhereInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="SuspendFunctionOnCoroutineScope" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SuspiciousAsDynamic" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SuspiciousCollectionReassignment" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SuspiciousEqualsCombination" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SuspiciousVarProperty" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="TestFunctionName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="TrailingComma" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="TypeCustomizer" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="TypeScriptUnresolvedFunction" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="TypeScriptUnresolvedReactComponent" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="TypeScriptUnresolvedVariable" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="TypeScriptValidateJSTypes" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="UnlabeledReturnInsideLambda" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="UnnecessaryQualifiedReference" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnnecessaryVariable" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="UnresolvedReference" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="UnresolvedRestParam" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="UnsafeCastFromDynamic" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="UnstableApiUsage" enabled="true" level="WARNING" enabled_by_default="true">
<option name="unstableApiAnnotations">
<set>
<option value="io.reactivex.annotations.Beta" />
<option value="io.reactivex.annotations.Experimental" />
<option value="org.apache.http.annotation.Beta" />
<option value="org.gradle.api.Incubating" />
<option value="org.jetbrains.annotations.ApiStatus.Experimental" />
<option value="org.jetbrains.annotations.ApiStatus.Internal" />
<option value="org.jetbrains.annotations.ApiStatus.ScheduledForRemoval" />
<option value="rx.annotations.Beta" />
<option value="rx.annotations.Experimental" />
</set>
</option>
</inspection_tool>
<inspection_tool class="UnusedDataClassCopyResult" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedEquals" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedLambdaExpressionBody" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedReceiverParameter" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedUnaryOperator" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UseExpressionBody" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="UsePropertyAccessSyntax" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="UseWithIndex" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="UselessCallOnCollection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UselessCallOnNotNull" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ValidExternallyBoundObject" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="VoidMethodAnnotatedWithGET" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="W3CssValidation" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myCssVersion" value="css3svg" />
<option name="myIgnoreVendorSpecificProperties" value="false" />
</inspection_tool>
<inspection_tool class="WSReferenceInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="WadlDomInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="WebpackConfigHighlighting" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="WrapUnaryOperator" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="WsdlHighlightingInspection" enabled="false" level="ERROR" enabled_by_default="false" />
</profile>
</component>

17
.idea/material_theme_project_new.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="f935243:19b09dbde27:-7ffe" />
</MTProjectMetadataState>
</option>
<option name="titleBarState">
<MTProjectTitleBarConfigState>
<option name="overrideColor" value="false" />
</MTProjectTitleBarConfigState>
</option>
</component>
</project>

9
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ComposerSettings">
<execution />
</component>
<component name="ProjectRootManager" version="2">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/transcribe.iml" filepath="$PROJECT_DIR$/transcribe.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

216
README.md Normal file
View File

@@ -0,0 +1,216 @@
# Verbatim Dicta
Real-time audio transcription using Whisper AI with optional LLM-powered analysis. Captures system audio via loopback and transcribes it with configurable models and processing options.
## Features
- Real-time transcription of system audio (Windows/Linux)
- Multiple Whisper model sizes (tiny to large)
- Multi-language support
- Optional LLM analysis for fact-checking and question generation (via Ollama)
- GPU acceleration support
- Flexible audio device configuration
## Quick Start
```bash
# Install dependencies
pip install -r requirements.txt
# Basic transcription
python transcribe_speakers.py
# With LLM analysis
python transcribe_speakers.py --enable-llm
# List audio devices
python transcribe_speakers.py --list-devices
```
## Requirements
- **OS**: Windows 10/11 or Linux
- **Python**: 3.8+
- **Audio**: Loopback device (Stereo Mix/VB-Cable on Windows, PulseAudio on Linux)
- **Optional**: CUDA-capable GPU, Ollama for LLM features
## Installation
### 1. Install Dependencies
```bash
pip install -r requirements.txt
```
### 2. GPU Support (Optional)
For CUDA 11.8:
```bash
pip install torch==2.8.0+cu118 --index-url https://download.pytorch.org/whl/cu118
```
For CUDA 12.1:
```bash
pip install torch==2.8.0+cu121 --index-url https://download.pytorch.org/whl/cu121
```
### 3. Audio Loopback Setup
**Windows - Option A (Stereo Mix):**
1. Right-click speaker icon → Sounds → Recording tab
2. Right-click → Show Disabled Devices
3. Enable and set Stereo Mix as default
**Windows - Option B (VB-Cable, recommended):**
1. Download from [vb-audio.com](https://vb-audio.com/Cable/)
2. Install and restart
3. Use `--device "CABLE Output"`
**Linux:**
Configure PulseAudio loopback or use `transcribe_dual_linux.py`
### 4. LLM Features (Optional)
```bash
# Install Ollama from ollama.ai
ollama pull llama3.2
```
## Usage
### Available Scripts
- `transcribe_speakers.py` - Main script with all features
- `transcribe_speakers_llm.py` - LLM-enabled version
- `transcribe_No_llm.py` - Basic version without LLM support
- `transcribe_dual_linux.py` - Linux-specific with dual audio support
### Common Commands
```bash
# Specify device and model
python transcribe_speakers.py --device "CABLE Output" --model medium
# Save to file with language
python transcribe_speakers.py --language es --output transcript.txt
# Fast mode (low latency)
python transcribe_speakers.py --fast-mode --model tiny --interval 3
# Maximum accuracy with LLM
python transcribe_speakers.py --model large --enable-llm --output enriched.txt
# Force CPU (avoid GPU issues)
python transcribe_speakers.py --force-cpu
```
### Key Options
| Option | Description | Default |
|--------|-------------|---------|
| `--model` | Model size: tiny/base/small/medium/large | base |
| `--language` | Language code (en/es/fr/de/ja/etc.) | en |
| `--device` | Audio device name (partial match) | Auto |
| `--interval` | Processing interval (seconds) | 8.0 |
| `--min-duration` | Minimum audio duration | 3.0 |
| `--fast-mode` | Fast mode (3-5x faster, lower accuracy) | False |
| `--enable-llm` | Enable fact-checking and questions | False |
| `--llm-model` | Ollama model to use | llama3.2 |
| `--output` | Save to file | None |
| `--force-cpu` | Disable GPU | False |
| `--gpu-index` | GPU device index | 0 |
## Model Performance
| Model | Size | Speed | Quality | Best For |
|-------|------|-------|---------|----------|
| tiny | ~75 MB | Fastest | Basic | Quick tests, low-latency |
| base | ~145 MB | Fast | Good | General real-time use |
| small | ~485 MB | Moderate | Better | Balanced accuracy/speed |
| medium | ~1.5 GB | Slow | Great | High accuracy needs |
| large | ~3 GB | Slowest | Best | Maximum accuracy |
## Optimization Presets
**Low Latency (Real-Time):**
```bash
python transcribe_speakers.py --model tiny --fast-mode --interval 2 --min-duration 1.5
```
**Balanced:**
```bash
python transcribe_speakers.py --model base --interval 5
```
**High Accuracy:**
```bash
python transcribe_speakers.py --model large --interval 10 --enable-llm
```
## Troubleshooting
**No loopback device:**
- Windows: Enable Stereo Mix or install VB-Cable
- Linux: Configure PulseAudio loopback
**CUDA errors:**
```bash
python transcribe_speakers.py --force-cpu
```
**No audio captured:**
- Verify audio is playing
- Check device: `--list-devices`
- Increase system volume
**Poor quality:**
- Use larger model: `--model medium`
- Increase interval: `--interval 10`
- Specify language: `--language <code>`
**Ollama errors:**
- Ensure Ollama is running
- Pull model: `ollama pull llama3.2`
## Output Format
**Standard:**
```
[14:23:15] Transcribed audio segment.
[14:23:23] Another segment with timestamp.
```
**With LLM (--enable-llm):**
```
======================================================================
[14:23:15] The Earth revolves around the Sun in 365 days.
📊 Fact Check: FACTUAL (confidence: 0.98)
💡 Scientifically accurate. Earth's orbital period is 365.25 days.
❓ Questions:
1. Why do we need leap years?
2. How does Earth's orbit affect seasons?
======================================================================
```
## Technical Stack
- **Audio**: sounddevice, soundfile (16kHz mono, 16-bit PCM)
- **Transcription**: faster-whisper (optimized Whisper)
- **LLM**: Ollama (local inference)
- **Capture**: WASAPI loopback (Windows), PulseAudio (Linux)
## Future Work
- Real-time streaming transcription with reduced buffering
- Speaker diarization improvements
- Web interface for remote monitoring
- Multi-device simultaneous transcription
- Cloud LLM integration options
- Custom vocabulary and domain adaptation
- Noise reduction preprocessing
## License
Uses [Whisper](https://github.com/openai/whisper) (OpenAI), [faster-whisper](https://github.com/SYSTRAN/faster-whisper) (SYSTRAN), and [Ollama](https://ollama.ai).

226
enriched.txt Executable file
View File

@@ -0,0 +1,226 @@
[23:31:46] So it helps us get back into a grounded information terrain and then also it requires us.
📊 Fact Check: NOT_FACTUAL (confidence: 0.90)
💡 The statement is a vague, nonspecific claim that cannot be verified against any factual evidence.
❓ Questions:
1. What specific processes or actions help us return to a grounded information terrain?
2. In what ways does this approach require us to change our current practices or mindset?
3. How does re-establishing a grounded information terrain impact the overall effectiveness of the project?
======================================================================
[23:31:54] to take the time to pay attention to information, really absorb it properly, and then to make decisions based on that. So we need to bring people into the process of
📊 Fact Check: NOT_FACTUAL (confidence: 0.99)
💡 The statement is an incomplete, nonfactual description of a process, not a verifiable claim.
❓ Questions:
1. What are the key points here?
2. What evidence supports this?
3. What are the implications?
======================================================================
[23:32:02] decision making and at the same time as part of that bring them into a terrain of really
📊 Fact Check: NOT_FACTUAL (confidence: 1.00)
💡 The statement is a nonsensical fragment and does not convey a verifiable factual claim.
❓ Questions:
1. What does the phrase "bring them into a terrain of really" refer to in the context of decision making?
2. How does the process of decision making simultaneously involve "bringing them into a terrain" as mentioned?
3. Can you explain how the concept of "terrain"?
======================================================================
[23:32:10] curing and discerning information properly and then engage in the decision-making process. That's the only way we're actually going to turn this around. It's not going to be good enough to...
📊 Fact Check: NOT_FACTUAL (confidence: 0.90)
💡 The fragment is an incomplete quote with no verifiable factual claim.
❓ Questions:
1. What does "curing and discerning information properly" entail in the context of this statement?
2. How does engaging in the decisionmaking process contribute to turning the situation around?
3. Why is simply having information or a plan not sufficient according to the speaker?
======================================================================
[23:32:18] to elect new politicians because the underlying problem of the way we absorb, process and deal with information now remains. And the only way we can do that is actually to do it.
📊 Fact Check: NOT_FACTUAL (confidence: 0.95)
💡 The statement is an opinion about politics and information processing, not a verifiable factual claim.
❓ Questions:
1. What specific aspects of the way we absorb, process, and deal with information are identified as the underlying problem in the statement?
2. How does the statement justify the election of new politicians as a solution to the information-related issue it describes?
3. What practical steps or strategies does the statement imply we should take to "actually do it" in addressing the information problem?
======================================================================
[23:32:26] actually by bringing people in on a mass basis, having huge numbers of citizens, juries around the country on a regular basis for decisions we're making involving the
📊 Fact Check: NOT_FACTUAL (confidence: 0.90)
💡 The statement is incomplete and lacks context, making it impossible to verify its factual accuracy.
❓ Questions:
1. What are the key points here?
2. What evidence supports this?
3. What are the implications?
======================================================================
[23:32:35] public that's the only way we're going to be able to turn this around and not just think that okay let's just wait for another Kamala Harris or somebody like that to come along and win an election then
📊 Fact Check: NOT_FACTUAL (confidence: 0.95)
💡 The quoted phrase
❓ Questions:
1. What specific actions does the speaker believe are necessary?
2. Q1: What specific actions does the speaker believe are necessary?
3. What are the implications?
======================================================================
[23:32:42] we'll all be right and we'll be able to turn the clock back. It won't work like that. The problem is far too deep seated than that. So, yes, we are becoming
📊 Fact Check: NOT_FACTUAL (confidence: 0.90)
💡 The statement is a vague, incomplete fragment with no verifiable factual claim.
❓ Questions:
1. What specific problem is the speaker implying is "far too deep seated" to be solved by simply "turning the clock back"?
2. How does the speaker's claim that "we'll all be right" relate to the broader context or argument being presented?
3. In what ways might the statement "So, yes, we are becoming" reflect a shift in perspective or identity for the speaker or the audience?
======================================================================
[23:32:50] That's basically what's going on at the moment. But that doesn't mean that we can lose hope, because there are mechanisms in which we can actually turn that around.
📊 Fact Check: NOT_FACTUAL (confidence: 0.90)
💡 The statement is a general, nonspecific claim that cannot be verified as true or false.
❓ Questions:
1. What specific situation or issue is being described as "what's going on at the moment"?
2. What mechanisms are being referred to that could help "turn that around"?
3. How does the speaker justify maintaining hope despite the current challenges?
======================================================================
[23:32:58] by actually engaging in the political process ourselves, which would force us to then utilise information in a different way.
📊 Fact Check: DUBIOUS (confidence: 0.70)
💡 The claim is a speculative assertion about how political engagement might change information use, and it cannot be verified as a factual statement.
❓ Questions:
1. What are the key points here?
2. What evidence supports this?
3. What are the implications?
======================================================================
[23:33:06] hopefully in the end come to different conclusions, but be part of that decision-making process too. So it's an important realization. What's happening to us is
📊 Fact Check: NOT_FACTUAL (confidence: 1.00)
💡 The statement is a subjective expression of hope and realization, not a verifiable factual claim.
❓ Questions:
1. What does the speaker mean by "hopefully in the end come to different conclusions" and how does that relate to the decision-making process mentioned?
2. In what ways might being part of the decision-making process influence the outcomes described in the statement?
3. What specific "important realization" is referenced, and how does it connect to "what's happening to us"?
======================================================================
[23:33:25] species in terms of our intelligence but it more importantly gives us a very important call to action. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think. We need to think.
📊 Fact Check: NOT_FACTUAL (confidence: 1.00)
💡 The statement is a nonsensical fragment that does not present any verifiable factual claim.
❓ Questions:
1. What is the main message conveyed by?
2. Q1: What is the main message conveyed by?
3. What are the implications?
======================================================================
[23:33:40] differently about how we govern ourselves going forward if we are to reverse this genuine decline. I hope you like my video. As a psychiatrist who loves politics and economics and philosophy I love to make videos like this and you can really help promote this video to other people and get it on your feed more by liking and commenting and subscribing to the video.
📊 Fact Check: NOT_FACTUAL (confidence: 1.00)
💡 The statement is a personal comment and request for promotion, not a claim that can be verified as true or false.
❓ Questions:
1. What specific strategies does the speaker propose for reversing the "genuine decline" in governance mentioned in the statement?
2. How does the speakers background as a psychiatrist influence their perspective on politics, economics, and philosophy?
3. In what ways does the speaker suggest viewers can effectively promote the video to reach a wider audience?
======================================================================
[23:33:46] well. We have a wonderful community of people here who comment and support each other through this very traumatic period of world history.
📊 Fact Check: DUBIOUS (confidence: 0.60)
💡 The claim is a subjective, unverified assertion about a communitys nature and cannot be confirmed or refuted with available evidence.
❓ Questions:
1. Who are the members of the community mentioned in the statement?
2. Which specific traumatic period of world history is being referred to?
3. In what ways do the community members comment and support each other during this period?
======================================================================
[23:33:54] that we're going through right now. I also hope you consider becoming a subscriber to the channel and also subscribing to my.
📊 Fact Check: NOT_FACTUAL (confidence: 1.00)
💡 The sentence is a fragment expressing a hope, not a verifiable factual claim.
❓ Questions:
1. What are the key points here?
2. What evidence supports this?
3. What are the implications?
======================================================================
[23:34:02] E-newsletter, there's a link in the description, and that way we can stay in touch outside the channel so you can stay, keep a
📊 Fact Check: NOT_FACTUAL (confidence: 0.90)
💡 [one sentence]"
❓ Questions:
1. What are the key points here?
2. What evidence supports this?
3. What are the implications?
======================================================================
[23:34:10] rest of all of the content that I'm making on an ongoing basis. The latest of which actually is my latest book called We the People.
📊 Fact Check: DUBIOUS (confidence: 0.50)
💡 The statement is a fragment with no verifiable context or evidence that the speakers latest book is titled *We the People*.
❓ Questions:
1. What are the key points here?
2. What evidence supports this?
3. What are the implications?
======================================================================
[23:34:19] very very proud of this book. It's actually a novel, a fiction book, written by myself and the famous award-winning author T.J. McGregor.
📊 Fact Check: NOT_FACTUAL (confidence: 0.90)
💡 No verifiable record exists of a novel coauthored by the user and an awardwinning author
❓ Questions:
1. How did you and T.J. McGregor collaborate on the novel?
2. What inspired you to co-write a fiction book with an awardwinning author?
3. What genre and themes does the novel?
======================================================================
[23:34:27] Together we wrote a book about what the future might look like. Bit of a dystopian novel, but what might happen if autocracy goes to its next stage?
📊 Fact Check: NOT_FACTUAL (confidence: 0.90)
💡 There is no verifiable evidence that the speaker and the other person coauthored a book on future dystopias.
❓ Questions:
1. What core themes and motifs did the book explore to envision the next stage of autocracy?
2. How does the narrative structure of the novel reflect the progression of authoritarian power in a dystopian future?
3. What real-world events or historical patterns inspired the authors to imagine a future where autocracy has evolved beyond its current form?

21
requirements.txt Executable file
View File

@@ -0,0 +1,21 @@
# Core dependencies for Windows Real-Time Audio Transcription
# Audio Processing
sounddevice==0.5.3
soundfile==0.13.1
numpy==2.2.2
# Whisper (faster-whisper backend)
faster-whisper==1.2.1
ctranslate2==4.6.1
# PyTorch (CPU version - see README for GPU installation)
torch==2.8.0
# NOTE: For GPU support, uninstall torch first, then run ONE of these commands:
# pip install torch==2.8.0+cu118 --index-url https://download.pytorch.org/whl/cu118
# pip install torch==2.8.0+cu121 --index-url https://download.pytorch.org/whl/cu121
# LLM Analysis (optional - for fact-checking and question generation)
# Requires Ollama to be installed and running
# Install from: https://ollama.ai
ollama==0.6.1

4
run.sh Executable file
View File

@@ -0,0 +1,4 @@
# Install all dependencies with correct NumPy version
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
pip install "numpy<2.0"
pip install faster-whisper pyannote.audio==3.1.1 ollama torchaudio sounddevice pydub

14
run_transcribe.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
# Run transcription with proper cuDNN library paths
cd "$(dirname "$0")"
source .venv/bin/activate
# Set cuDNN library path
CUDNN_PATH=".venv/lib/python3.13/site-packages/nvidia/cudnn/lib"
CUBLAS_PATH=".venv/lib/python3.13/site-packages/nvidia/cublas/lib"
export LD_LIBRARY_PATH="${CUDNN_PATH}:${CUBLAS_PATH}:${LD_LIBRARY_PATH}"
# Run the transcription script with all arguments
python3 transcribe_dual_linux.py "$@"

12
transcribe.iml Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="GENERAL_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.git" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

596
transcribe_No_llm.py Executable file
View File

@@ -0,0 +1,596 @@
#!/usr/bin/env python3
"""
Real-time transcription of Windows speaker output using loopback capture.
Captures system audio and transcribes with Whisper in near real-time.
"""
import sounddevice as sd
import numpy as np
import threading
import queue
import time
import os
import argparse
import json
from datetime import datetime
# Choose your Whisper backend here:
# For faster-whisper (recommended):
from faster_whisper import WhisperModel
# LLM integration
try:
import ollama
OLLAMA_AVAILABLE = True
except ImportError:
OLLAMA_AVAILABLE = False
# # For regular whisper (comment out the line above and uncomment these):
# import whisper
class WindowsLoopbackAudioCapture:
"""Capture Windows speaker output using WASAPI loopback"""
def __init__(self, device_name=None, sample_rate=16000, chunk_size=2048):
self.sample_rate = sample_rate
self.chunk_size = chunk_size
# Find loopback device
self.device_info = self._find_loopback_device(device_name)
if not self.device_info:
raise RuntimeError(
"No loopback device found.\n"
"1. Ensure your speakers/headphones are connected\n"
"2. Enable 'Stereo Mix' in Sound settings\n"
"3. Or install VB-Cable virtual audio device"
)
print(f"✓ Using device: {self.device_info['name']} (index {self.device_info['index']})")
# Queue for audio data
self.audio_queue = queue.Queue()
self.stop_event = threading.Event()
# Start the stream
try:
self.stream = sd.InputStream(
device=self.device_info['index'],
channels=1,
samplerate=sample_rate,
blocksize=chunk_size,
dtype='int16',
latency='low',
callback=self._audio_callback
)
self.stream.start()
print("✓ Audio capture stream started")
except Exception as e:
raise RuntimeError(f"Failed to start audio stream: {e}")
def _find_loopback_device(self, device_name):
"""Find the speaker device with loopback capability"""
devices = sd.query_devices()
# If device name specified, find exact match
if device_name:
for dev in devices:
if (device_name.lower() in dev['name'].lower() and
dev['max_input_channels'] > 0):
return dev
# Auto-detect: look for WASAPI speakers/headphones
for dev in devices:
if (dev['max_input_channels'] > 0 and
any(x in dev['name'] for x in ['Speakers', 'Headphones', 'Output'])):
return dev
# Fallback: Stereo Mix or similar
for dev in devices:
if 'Stereo Mix' in dev['name']:
return dev
return None
def _audio_callback(self, indata, frames, time_info, status):
"""Callback for audio data"""
if status:
print(f"⚠ Audio status: {status}")
self.audio_queue.put(indata.copy())
def read_chunk(self):
"""Read audio data from queue"""
try:
return self.audio_queue.get(timeout=0.05).flatten()
except queue.Empty:
return None
def close(self):
"""Cleanup resources"""
if hasattr(self, 'stream'):
self.stream.stop()
self.stream.close()
class WhisperStreamTranscriber:
"""Process audio chunks with Whisper/faster-whisper"""
def __init__(self, model_name="base", language="en", force_cpu=False):
print(f"Loading Whisper model '{model_name}'...")
# Check for CUDA availability
import torch
has_cuda = torch.cuda.is_available() and not force_cpu
# Force CPU if CUDA libraries incompatible
device = "cpu"
compute_type = "int8"
if has_cuda:
try:
# Test if CTranslate2 can actually use CUDA
import ctranslate2
cuda_count = ctranslate2.get_cuda_device_count()
if cuda_count > 0:
device = "cuda"
compute_type = "float16"
print(f"Using device: cuda ({torch.cuda.get_device_name(0)})")
else:
print(f"CUDA available in PyTorch but not in CTranslate2. Using CPU.")
except Exception as e:
print(f"CUDA libraries not found ({e}). Using CPU.")
else:
print("Using device: cpu")
# FASTER-WHISPER (recommended):
model_kwargs = {
"device": device,
"compute_type": compute_type
}
if not has_cuda:
model_kwargs["cpu_threads"] = 4
self.model = WhisperModel(model_name, **model_kwargs)
self.language = language
self.audio_buffer = np.array([], dtype=np.float32)
self.lock = threading.Lock()
# # REGULAR WHISPER:
# self.model = whisper.load_model(model_name)
# self.language = language
# self.audio_buffer = np.array([], dtype=np.float32)
# self.lock = threading.Lock()
def add_audio(self, audio_chunk):
"""Add new audio data to buffer"""
with self.lock:
audio_float = audio_chunk.astype(np.float32) / 32768.0
self.audio_buffer = np.concatenate([self.audio_buffer, audio_float])
def transcribe_chunk(self, min_duration=5.0):
"""Transcribe accumulated audio if enough duration"""
with self.lock:
duration = len(self.audio_buffer) / 16000
if duration < min_duration:
return None
audio_to_process = self.audio_buffer.copy()
self.audio_buffer = np.array([], dtype=np.float32)
# Process with FASTER-WHISPER:
try:
segments, _ = self.model.transcribe(
audio_to_process,
language=self.language,
beam_size=5,
vad_filter=True,
vad_parameters=dict(min_silence_duration_ms=500),
word_timestamps=False
)
text = " ".join([segment.text for segment in segments]).strip()
return text if text else None
except Exception as e:
print(f"❌ Transcription error: {e}")
return None
# # REGULAR WHISPER:
# try:
# result = self.model.transcribe(
# audio_to_process,
# language=self.language,
# task="transcribe",
# fp16=False
# )
# return result["text"].strip()
# except Exception as e:
# print(f"❌ Transcription error: {e}")
# return None
class LocalLLMAnalyzer:
"""Local LLM for fact-checking and question generation using Ollama"""
def __init__(self, model="llama3.2"):
if not OLLAMA_AVAILABLE:
raise RuntimeError(
"Ollama package not installed.\n"
"Install with: pip install ollama"
)
self.model = model
self._test_connection()
def _test_connection(self):
"""Test connection to Ollama service"""
try:
ollama.list()
print(f"✓ Ollama connected using model: {self.model}")
except Exception as e:
raise RuntimeError(
f"Cannot connect to Ollama. Ensure it's installed and running.\n"
f"Error: {e}\n"
f"Install from: https://ollama.ai\n"
f"Then run: ollama pull {self.model}"
)
def _extract_json(self, text):
"""Extract JSON from text that might contain markdown or other formatting"""
# Try to find JSON block in markdown code fence
import re
json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', text, re.DOTALL)
if json_match:
return json_match.group(1)
# Try to find raw JSON object
json_match = re.search(r'\{.*\}', text, re.DOTALL)
if json_match:
return json_match.group(0)
return text
def fact_check(self, text, context=""):
"""Analyze text for factual accuracy"""
prompt = f"""You are a fact-checking assistant. Analyze this statement for factual accuracy.
Context: {context}
Statement: "{text}"
You must respond with ONLY valid JSON in this exact format, no other text:
{{
"verdict": "factual",
"confidence": 0.95,
"explanation": "Brief explanation here",
"sources": ["source1"],
"corrections": ""
}}
Valid verdict values: "factual", "dubious", "not_factual"
Confidence must be a number between 0.0 and 1.0."""
try:
response = ollama.generate(
model=self.model,
prompt=prompt,
options={"temperature": 0.1, "num_predict": 200}
)
# Extract and parse JSON
response_text = response['response']
json_text = self._extract_json(response_text)
result = json.loads(json_text)
# Validate required fields
if 'verdict' not in result or 'confidence' not in result:
raise ValueError("Missing required fields")
# Ensure defaults for optional fields
result.setdefault('explanation', 'No explanation provided')
result.setdefault('sources', [])
result.setdefault('corrections', '')
return result
except (json.JSONDecodeError, ValueError) as e:
# Return a simple analysis without JSON parsing
return {
"verdict": "dubious",
"confidence": 0.5,
"explanation": f"Could not parse LLM response properly. Model may need JSON format support.",
"sources": [],
"corrections": ""
}
except Exception as e:
return {
"verdict": "error",
"confidence": 0.0,
"explanation": f"Analysis failed: {str(e)}",
"sources": [],
"corrections": ""
}
def generate_augmenting_questions(self, text, context=""):
"""Generate insightful questions based on the text"""
prompt = f"""Based on this statement, generate 3 insightful questions that would help understand the topic better.
Statement: "{text}"
Context: {context}
Respond with JSON only:
{{
"questions": ["Question 1", "Question 2", "Question 3"],
"topics": ["key_topic_1", "key_topic_2"]
}}"""
try:
response = ollama.generate(
model=self.model,
prompt=prompt,
format="json",
options={"temperature": 0.7}
)
return json.loads(response['response'])
except json.JSONDecodeError:
return {
"questions": ["Error: LLM response was not valid JSON"],
"topics": []
}
except Exception as e:
return {
"questions": [f"Error: {str(e)}"],
"topics": []
}
def list_audio_devices():
"""Print all available audio input devices"""
print("\nAvailable audio capture devices:")
devices = sd.query_devices()
for i, dev in enumerate(devices):
if dev['max_input_channels'] > 0:
print(f" [{i}] {dev['name']}")
print(f" Channels: {dev['max_input_channels']} | Sample Rate: {dev['default_samplerate']}")
print()
def save_transcript(text, timestamp, filename):
"""Append transcript to file"""
os.makedirs(os.path.dirname(filename) if os.path.dirname(filename) else '.', exist_ok=True)
with open(filename, "a", encoding="utf-8") as f:
f.write(f"[{timestamp}] {text}\n")
def save_enriched_transcript(data, filename):
"""Save enriched transcript with LLM analysis"""
os.makedirs(os.path.dirname(filename) if os.path.dirname(filename) else '.', exist_ok=True)
with open(filename, "a", encoding="utf-8") as f:
f.write(f"\n{'='*70}\n")
f.write(f"[{data['timestamp']}] {data['text']}\n\n")
if 'fact_check' in data:
fc = data['fact_check']
f.write(f"📊 Fact Check: {fc.get('verdict', 'N/A').upper()} "
f"(confidence: {fc.get('confidence', 0):.2f})\n")
f.write(f"💡 {fc.get('explanation', 'N/A')}\n")
if fc.get('corrections'):
f.write(f"✏️ Correction: {fc['corrections']}\n")
f.write("\n")
if 'questions' in data and data['questions'].get('questions'):
f.write("❓ Questions:\n")
for i, q in enumerate(data['questions']['questions'], 1):
f.write(f"{i}. {q}\n")
f.write("\n")
def display_enriched_output(text, timestamp, fact_check=None, questions=None):
"""Display transcript with LLM analysis"""
print(f"\n[{timestamp}] {text}")
if fact_check:
verdict_emoji = {
'factual': '',
'dubious': '⚠️',
'not_factual': '',
'error': '⚠️'
}
emoji = verdict_emoji.get(fact_check.get('verdict', 'error'), '')
print(f"\n{emoji} Fact Check: {fact_check.get('verdict', 'N/A').upper()} "
f"(confidence: {fact_check.get('confidence', 0):.2f})")
print(f"💡 {fact_check.get('explanation', 'N/A')}")
if fact_check.get('corrections'):
print(f"✏️ Correction: {fact_check['corrections']}")
if questions and questions.get('questions'):
print(f"\n❓ Questions:")
for i, q in enumerate(questions['questions'], 1):
print(f" {i}. {q}")
def main():
parser = argparse.ArgumentParser(
description="Real-time transcription of Windows speaker output",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python transcribe_speakers.py
python transcribe_speakers.py --model small --language es --interval 5
python transcribe_speakers.py --device "Speakers" --output "meeting.txt"
python transcribe_speakers.py --model medium --interval 10 --output transcripts/live.txt
"""
)
parser.add_argument("--model", default="base",
choices=["tiny", "base", "small", "medium", "large"],
help="Whisper model size (default: base)")
parser.add_argument("--language", default="en",
help="Language code (default: en)")
parser.add_argument("--device", metavar="NAME",
help="Audio device name (partial match). If not specified, auto-detects")
parser.add_argument("--interval", type=float, default=8.0,
help="Processing interval in seconds (default: 8.0)")
parser.add_argument("--output", "-o", metavar="FILE",
help="Save transcript to file (e.g., transcript.txt)")
parser.add_argument("--list-devices", action="store_true",
help="List all available audio devices and exit")
parser.add_argument("--force-cpu", action="store_true",
help="Force CPU processing (disable GPU acceleration)")
parser.add_argument("--enable-llm", action="store_true",
help="Enable LLM analysis (fact-checking and questions)")
parser.add_argument("--llm-model", default="gpt-oss:20b",
help="Ollama model to use for LLM analysis (default: gpt-oss:20b)")
args = parser.parse_args()
if args.list_devices:
list_audio_devices()
return
print("=== Windows Real-Time Audio Transcription ===")
print(f"Model: {args.model} | Language: {args.language} | Interval: {args.interval}s")
if args.output:
print(f"Output: {args.output}")
if args.enable_llm:
print(f"LLM Analysis: Enabled ({args.llm_model})")
# Initialize audio capture
try:
capturer = WindowsLoopbackAudioCapture(
device_name=args.device,
sample_rate=16000,
chunk_size=2048
)
except RuntimeError as e:
print(f"\n❌ Audio Error: {e}")
print("\nTo fix this:")
print("1. Right-click speaker icon → Sounds → Recording tab")
print("2. Right-click in empty area → Show Disabled Devices")
print("3. Enable 'Stereo Mix' → Set as Default Device")
print("\nAlternative: Install VB-Cable (free) from vb-audio.com")
print(" Then use: --device 'CABLE Output'")
list_audio_devices()
return
# Initialize transcriber
try:
transcriber = WhisperStreamTranscriber(
model_name=args.model,
language=args.language,
force_cpu=args.force_cpu
)
except Exception as e:
print(f"\n❌ Model Error: {e}")
print("Make sure you installed Whisper correctly")
return
# Initialize LLM analyzer (optional)
llm_analyzer = None
if args.enable_llm:
try:
llm_analyzer = LocalLLMAnalyzer(model=args.llm_model)
except RuntimeError as e:
print(f"\n❌ LLM Error: {e}")
print("Continuing without LLM analysis...")
llm_analyzer = None
# Main processing loop
print(f"\n✅ Started transcription. Press Ctrl+C to stop.\n{'=' * 50}")
last_process_time = time.time()
total_duration = 0
segment_count = 0
try:
while True:
# Collect audio
chunk = capturer.read_chunk()
if chunk is not None:
transcriber.add_audio(chunk)
total_duration += len(chunk) / 16000
# Process at intervals
current_time = time.time()
if current_time - last_process_time >= args.interval:
text = transcriber.transcribe_chunk()
if text:
segment_count += 1
timestamp = datetime.now().strftime("%H:%M:%S")
# LLM Analysis
fact_check = None
questions = None
if llm_analyzer:
context = f"Segment {segment_count}"
fact_check = llm_analyzer.fact_check(text, context)
questions = llm_analyzer.generate_augmenting_questions(text, context)
# Display output
if llm_analyzer:
display_enriched_output(text, timestamp, fact_check, questions)
else:
print(f"[{timestamp}] {text}")
# Save output
if args.output:
if llm_analyzer:
data = {
'timestamp': timestamp,
'text': text,
'fact_check': fact_check,
'questions': questions
}
save_enriched_transcript(data, args.output)
else:
save_transcript(text, timestamp, args.output)
last_process_time = current_time
except KeyboardInterrupt:
print(f"\n{'=' * 50}\n🛑 Stopping transcription...")
# Cleanup
capturer.close()
# Process remaining audio
print("\nProcessing remaining audio...")
final_text = transcriber.transcribe_chunk(min_duration=0)
if final_text:
timestamp = datetime.now().strftime("%H:%M:%S")
# LLM Analysis for final segment
fact_check = None
questions = None
if llm_analyzer:
fact_check = llm_analyzer.fact_check(final_text, "Final segment")
questions = llm_analyzer.generate_augmenting_questions(final_text)
# Display output
if llm_analyzer:
display_enriched_output(final_text, timestamp, fact_check, questions)
else:
print(f"[{timestamp}] {final_text}")
# Save output
if args.output:
if llm_analyzer:
data = {
'timestamp': timestamp,
'text': final_text,
'fact_check': fact_check,
'questions': questions
}
save_enriched_transcript(data, args.output)
else:
save_transcript(final_text, timestamp, args.output)
# Summary
print(f"\n✅ Complete! Processed {total_duration:.1f}s of audio")
print(f" Generated {segment_count} transcript segments")
if args.output and os.path.exists(args.output):
abs_path = os.path.abspath(args.output)
print(f"💾 Transcript saved to: {abs_path}")
if __name__ == "__main__":
main()

347
transcribe_dual_linux.py Executable file
View File

@@ -0,0 +1,347 @@
#!/usr/bin/env python3
"""
Real-time transcription with dual audio capture (microphone + speaker monitor).
Linux/PipeWire optimized with Ollama LLM fact-checking.
"""
import sounddevice as sd
import numpy as np
import threading
import queue
import time
import argparse
from datetime import datetime
from faster_whisper import WhisperModel
try:
import ollama
OLLAMA_AVAILABLE = True
except ImportError:
OLLAMA_AVAILABLE = False
class DualAudioCapture:
"""Capture both microphone and speaker output simultaneously"""
def __init__(self, mic_device=None, monitor_device=None, sample_rate=16000, chunk_size=2048):
self.sample_rate = sample_rate
self.chunk_size = chunk_size
self.audio_queue = queue.Queue()
# Find devices
devices = sd.query_devices()
# Microphone (default input or specified)
if mic_device is None:
self.mic_device = sd.default.device[0] # Default input
else:
self.mic_device = self._find_device(mic_device, input_required=True)
# Monitor/Loopback (for speaker output)
if monitor_device:
self.monitor_device = self._find_device(monitor_device, input_required=True)
else:
self.monitor_device = None
print(f"✓ Microphone: {devices[self.mic_device]['name']} (index {self.mic_device})")
if self.monitor_device:
print(f"✓ Monitor: {devices[self.monitor_device]['name']} (index {self.monitor_device})")
else:
print("⚠ No monitor device - capturing microphone only")
# Start streams
self.mic_stream = sd.InputStream(
device=self.mic_device,
channels=1,
samplerate=sample_rate,
blocksize=chunk_size,
dtype='int16',
callback=self._mic_callback
)
if self.monitor_device:
self.monitor_stream = sd.InputStream(
device=self.monitor_device,
channels=1,
samplerate=sample_rate,
blocksize=chunk_size,
dtype='int16',
callback=self._monitor_callback
)
else:
self.monitor_stream = None
self.mic_stream.start()
if self.monitor_stream:
self.monitor_stream.start()
print("✓ Audio capture started")
def _find_device(self, device_name, input_required=True):
"""Find device by name substring"""
devices = sd.query_devices()
for i, dev in enumerate(devices):
if device_name.lower() in dev['name'].lower():
if not input_required or dev['max_input_channels'] > 0:
return i
raise RuntimeError(f"Device '{device_name}' not found")
def _mic_callback(self, indata, frames, time_info, status):
"""Microphone audio callback"""
if status:
print(f"⚠ Mic status: {status}")
self.audio_queue.put(('mic', indata.copy()))
def _monitor_callback(self, indata, frames, time_info, status):
"""Monitor/speaker audio callback"""
if status:
print(f"⚠ Monitor status: {status}")
self.audio_queue.put(('monitor', indata.copy()))
def read_chunk(self):
"""Read audio data from queue"""
try:
return self.audio_queue.get(timeout=0.05)
except queue.Empty:
return None
def close(self):
"""Cleanup resources"""
self.mic_stream.stop()
self.mic_stream.close()
if self.monitor_stream:
self.monitor_stream.stop()
self.monitor_stream.close()
class WhisperTranscriber:
"""Process audio with Whisper"""
def __init__(self, model_name="base", language="en", force_cpu=False):
print(f"Loading Whisper model '{model_name}'...")
import torch
has_cuda = torch.cuda.is_available() and not force_cpu
device = "cpu"
compute_type = "int8"
if has_cuda:
try:
import ctranslate2
if ctranslate2.get_cuda_device_count() > 0:
device = "cuda"
compute_type = "float16"
print(f"✓ Using GPU: {torch.cuda.get_device_name(0)}")
except Exception as e:
print(f"⚠ CUDA unavailable: {e}")
if device == "cpu":
print("✓ Using CPU")
model_kwargs = {"device": device, "compute_type": compute_type}
if device == "cpu":
model_kwargs["cpu_threads"] = 4
self.model = WhisperModel(model_name, **model_kwargs)
self.language = language
self.mic_buffer = np.array([], dtype=np.float32)
self.monitor_buffer = np.array([], dtype=np.float32)
self.lock = threading.Lock()
def add_audio(self, source, audio_chunk):
"""Add audio to appropriate buffer"""
with self.lock:
audio_float = audio_chunk.flatten().astype(np.float32) / 32768.0
if source == 'mic':
self.mic_buffer = np.concatenate([self.mic_buffer, audio_float])
else:
self.monitor_buffer = np.concatenate([self.monitor_buffer, audio_float])
def transcribe_chunk(self, min_duration=3.0):
"""Transcribe accumulated audio"""
with self.lock:
mic_duration = len(self.mic_buffer) / 16000
monitor_duration = len(self.monitor_buffer) / 16000
results = {}
# Transcribe microphone
if mic_duration >= min_duration:
mic_audio = self.mic_buffer.copy()
self.mic_buffer = np.array([], dtype=np.float32)
results['mic'] = self._transcribe(mic_audio)
# Transcribe monitor
if monitor_duration >= min_duration:
monitor_audio = self.monitor_buffer.copy()
self.monitor_buffer = np.array([], dtype=np.float32)
results['monitor'] = self._transcribe(monitor_audio)
return results if results else None
def _transcribe(self, audio):
"""Internal transcription"""
try:
segments, _ = self.model.transcribe(
audio,
language=self.language,
beam_size=3, # Faster than default 5
vad_filter=True,
vad_parameters=dict(min_silence_duration_ms=500)
)
text = " ".join([seg.text for seg in segments]).strip()
return text if text else None
except Exception as e:
print(f"❌ Transcription error: {e}")
return None
class LLMFactChecker:
"""Fast fact-checking with Ollama"""
def __init__(self, model="qwen2.5:3b"):
if not OLLAMA_AVAILABLE:
raise RuntimeError("Ollama not installed: pip install ollama")
self.model = model
try:
ollama.list()
print(f"✓ Ollama connected: {self.model}")
except Exception as e:
raise RuntimeError(f"Ollama not running: {e}")
def fact_check(self, text):
"""Quick fact-check"""
prompt = f"""Fact-check this statement. Reply ONLY with:
VERDICT: factual/dubious/false
CONFIDENCE: 0.0-1.0
REASON: one sentence
Statement: "{text}" """
try:
response = ollama.generate(
model=self.model,
prompt=prompt,
options={"temperature": 0.1, "num_predict": 80}
)
import re
text = response['response']
verdict = re.search(r'VERDICT:\s*(\w+)', text, re.I)
confidence = re.search(r'CONFIDENCE:\s*([\d.]+)', text, re.I)
reason = re.search(r'REASON:\s*(.+?)(?:\n|$)', text, re.I | re.DOTALL)
return {
'verdict': verdict.group(1).lower() if verdict else 'unknown',
'confidence': float(confidence.group(1)) if confidence else 0.5,
'reason': reason.group(1).strip() if reason else text[:150]
}
except Exception as e:
return {'verdict': 'error', 'confidence': 0.0, 'reason': str(e)}
def main():
parser = argparse.ArgumentParser(description="Dual audio transcription with fact-checking")
parser.add_argument("--model", default="tiny", choices=["tiny", "base", "small", "medium"],
help="Whisper model (default: tiny for speed)")
parser.add_argument("--language", default="en", help="Language code")
parser.add_argument("--mic", help="Microphone device name (partial match)")
parser.add_argument("--monitor", help="Monitor device name for speaker capture")
parser.add_argument("--interval", type=float, default=5.0, help="Processing interval (seconds)")
parser.add_argument("--min-duration", type=float, default=2.0, help="Min audio duration")
parser.add_argument("--enable-llm", action="store_true", help="Enable fact-checking")
parser.add_argument("--llm-model", default="qwen2.5:3b", help="Ollama model")
parser.add_argument("--list-devices", action="store_true", help="List audio devices")
parser.add_argument("--force-cpu", action="store_true", help="Force CPU")
args = parser.parse_args()
if args.list_devices:
print("\nAvailable audio devices:")
for i, dev in enumerate(sd.query_devices()):
in_ch = dev['max_input_channels']
out_ch = dev['max_output_channels']
if in_ch > 0:
print(f" [{i:2d}] {dev['name']:<50} IN:{in_ch} OUT:{out_ch}")
return
print("=== Dual Audio Transcription with Fact-Checking ===")
print(f"Model: {args.model} | Language: {args.language} | Interval: {args.interval}s")
# Initialize capture
try:
capturer = DualAudioCapture(
mic_device=args.mic,
monitor_device=args.monitor,
sample_rate=16000,
chunk_size=2048
)
except Exception as e:
print(f"\n❌ Audio Error: {e}")
print("\nTip: Use --list-devices to see available devices")
print(" Use --mic and --monitor to specify devices")
return
# Initialize transcriber
try:
transcriber = WhisperTranscriber(
model_name=args.model,
language=args.language,
force_cpu=args.force_cpu
)
except Exception as e:
print(f"\n❌ Whisper Error: {e}")
return
# Initialize fact checker
fact_checker = None
if args.enable_llm:
try:
fact_checker = LLMFactChecker(model=args.llm_model)
except Exception as e:
print(f"\n⚠ LLM Error: {e}")
print("Continuing without fact-checking...")
# Main loop
print(f"\n✅ Started. Press Ctrl+C to stop.\n{'='*60}")
last_process = time.time()
try:
while True:
# Collect audio
chunk = capturer.read_chunk()
if chunk:
source, audio = chunk
transcriber.add_audio(source, audio)
# Process at intervals
if time.time() - last_process >= args.interval:
results = transcriber.transcribe_chunk(min_duration=args.min_duration)
if results:
timestamp = datetime.now().strftime("%H:%M:%S")
for source, text in results.items():
if text:
source_emoji = "🎤" if source == 'mic' else "🔊"
print(f"\n{source_emoji} [{timestamp}] {text}")
if fact_checker:
fc = fact_checker.fact_check(text)
verdict_emoji = {'factual': '', 'dubious': '⚠️', 'false': ''}.get(fc['verdict'], '')
print(f" {verdict_emoji} {fc['verdict'].upper()} ({fc['confidence']:.2f}): {fc['reason']}")
last_process = time.time()
except KeyboardInterrupt:
print(f"\n{'='*60}\n🛑 Stopping...")
capturer.close()
print("\n✅ Done!")
if __name__ == "__main__":
main()

739
transcribe_speakers.py Executable file
View File

@@ -0,0 +1,739 @@
#!/usr/bin/env python3
"""
Real-time transcription of Windows speaker output using loopback capture.
Captures system audio and transcribes with Whisper in near real-time.
"""
import sounddevice as sd
import numpy as np
import threading
import queue
import time
import os
import argparse
import json
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
# Choose your Whisper backend here:
# For faster-whisper (recommended):
from faster_whisper import WhisperModel
# LLM integration
try:
import ollama
OLLAMA_AVAILABLE = True
except ImportError:
OLLAMA_AVAILABLE = False
# # For regular whisper (comment out the line above and uncomment these):
# import whisper
class WindowsLoopbackAudioCapture:
"""Capture Windows speaker output using WASAPI loopback"""
def __init__(self, device_name=None, sample_rate=16000, chunk_size=2048):
self.sample_rate = sample_rate
self.chunk_size = chunk_size
# Find loopback device
self.device_info = self._find_loopback_device(device_name)
if not self.device_info:
raise RuntimeError(
"No loopback device found.\n"
"1. Ensure your speakers/headphones are connected\n"
"2. Enable 'Stereo Mix' in Sound settings\n"
"3. Or install VB-Cable virtual audio device"
)
print(f"✓ Using device: {self.device_info['name']} (index {self.device_info['index']})")
# Queue for audio data
self.audio_queue = queue.Queue()
self.stop_event = threading.Event()
# Start the stream
try:
self.stream = sd.InputStream(
device=self.device_info['index'],
channels=1,
samplerate=sample_rate,
blocksize=chunk_size,
dtype='int16',
latency='low',
callback=self._audio_callback
)
self.stream.start()
print("✓ Audio capture stream started")
except Exception as e:
raise RuntimeError(f"Failed to start audio stream: {e}")
def _find_loopback_device(self, device_name):
"""Find the speaker device with loopback capability"""
devices = sd.query_devices()
# If device name specified, find exact match
if device_name:
for dev in devices:
if (device_name.lower() in dev['name'].lower() and
dev['max_input_channels'] > 0):
return dev
# Auto-detect: look for WASAPI speakers/headphones
for dev in devices:
if (dev['max_input_channels'] > 0 and
any(x in dev['name'] for x in ['Speakers', 'Headphones', 'Output'])):
return dev
# Fallback: Stereo Mix or similar
for dev in devices:
if 'Stereo Mix' in dev['name']:
return dev
return None
def _audio_callback(self, indata, frames, time_info, status):
"""Callback for audio data"""
if status:
print(f"⚠ Audio status: {status}")
self.audio_queue.put(indata.copy())
def read_chunk(self):
"""Read audio data from queue"""
try:
return self.audio_queue.get(timeout=0.05).flatten()
except queue.Empty:
return None
def close(self):
"""Cleanup resources"""
if hasattr(self, 'stream'):
self.stream.stop()
self.stream.close()
class WhisperStreamTranscriber:
"""Process audio chunks with Whisper/faster-whisper"""
def __init__(self, model_name="base", language="en", force_cpu=False, device_index=0):
print(f"Loading Whisper model '{model_name}'...")
# Check for CUDA availability
import torch
has_cuda = torch.cuda.is_available() and not force_cpu
# Force CPU if CUDA libraries incompatible
device = "cpu"
compute_type = "int8"
if has_cuda:
try:
# Test if CTranslate2 can actually use CUDA
import ctranslate2
cuda_count = ctranslate2.get_cuda_device_count()
if cuda_count > 0:
# Validate device index
if device_index >= cuda_count:
print(f"⚠️ GPU index {device_index} not available. Found {cuda_count} GPU(s). Using GPU 0.")
device_index = 0
# CTranslate2 uses "cuda" + device_index parameter, not "cuda:N"
device = "cuda"
compute_type = "float16"
print(f"Using device: cuda:{device_index} ({torch.cuda.get_device_name(device_index)})")
else:
print(f"CUDA available in PyTorch but not in CTranslate2. Using CPU.")
device = "cpu"
compute_type = "int8"
except Exception as e:
print(f"CUDA libraries not found ({e}). Using CPU.")
device = "cpu"
compute_type = "int8"
else:
print("Using device: cpu")
# FASTER-WHISPER (recommended):
model_kwargs = {
"device": device,
"compute_type": compute_type
}
if device == "cuda":
model_kwargs["device_index"] = device_index
elif device == "cpu":
model_kwargs["cpu_threads"] = 4
self.model = WhisperModel(model_name, **model_kwargs)
self.language = language
self.audio_buffer = np.array([], dtype=np.float32)
self.lock = threading.Lock()
# # REGULAR WHISPER:
# self.model = whisper.load_model(model_name)
# self.language = language
# self.audio_buffer = np.array([], dtype=np.float32)
# self.lock = threading.Lock()
def add_audio(self, audio_chunk):
"""Add new audio data to buffer"""
with self.lock:
audio_float = audio_chunk.astype(np.float32) / 32768.0
self.audio_buffer = np.concatenate([self.audio_buffer, audio_float])
def transcribe_chunk(self, min_duration=5.0, fast_mode=False):
"""Transcribe accumulated audio if enough duration"""
with self.lock:
duration = len(self.audio_buffer) / 16000
if duration < min_duration:
return None
audio_to_process = self.audio_buffer.copy()
self.audio_buffer = np.array([], dtype=np.float32)
# Process with FASTER-WHISPER:
try:
# Optimize parameters for speed vs accuracy
if fast_mode:
# Fast mode: lower beam size, no VAD
segments, _ = self.model.transcribe(
audio_to_process,
language=self.language,
beam_size=1, # Greedy decoding (fastest)
best_of=1,
temperature=0.0,
vad_filter=False,
word_timestamps=False
)
else:
# Balanced mode: moderate beam size with VAD
segments, _ = self.model.transcribe(
audio_to_process,
language=self.language,
beam_size=3, # Reduced from 5
vad_filter=True,
vad_parameters=dict(min_silence_duration_ms=500),
word_timestamps=False
)
text = " ".join([segment.text for segment in segments]).strip()
return text if text else None
except Exception as e:
print(f"❌ Transcription error: {e}")
return None
# # REGULAR WHISPER:
# try:
# result = self.model.transcribe(
# audio_to_process,
# language=self.language,
# task="transcribe",
# fp16=False
# )
# return result["text"].strip()
# except Exception as e:
# print(f"❌ Transcription error: {e}")
# return None
class LocalLLMAnalyzer:
"""Local LLM for fact-checking and question generation using Ollama"""
def __init__(self, model="llama3.2", debug=False):
if not OLLAMA_AVAILABLE:
raise RuntimeError(
"Ollama package not installed.\n"
"Install with: pip install ollama"
)
self.model = model
self.debug = debug
self._test_connection()
def _test_connection(self):
"""Test connection to Ollama service"""
try:
ollama.list()
print(f"✓ Ollama connected using model: {self.model}")
except Exception as e:
raise RuntimeError(
f"Cannot connect to Ollama. Ensure it's installed and running.\n"
f"Error: {e}\n"
f"Install from: https://ollama.ai\n"
f"Then run: ollama pull {self.model}"
)
def _extract_json(self, text):
"""Extract JSON from text that might contain markdown or other formatting"""
# Try to find JSON block in markdown code fence
import re
json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', text, re.DOTALL)
if json_match:
return json_match.group(1)
# Try to find raw JSON object
json_match = re.search(r'\{.*\}', text, re.DOTALL)
if json_match:
return json_match.group(0)
return text
def fact_check(self, text, context=""):
"""Analyze text for factual accuracy"""
# Try simple structured format first
prompt = f"""Analyze this for accuracy. Reply in this exact format:
VERDICT: [factual/dubious/not_factual]
CONFIDENCE: [0.0-1.0]
EXPLANATION: [one sentence]
Statement: "{text}"
"""
try:
response = ollama.generate(
model=self.model,
prompt=prompt,
options={"temperature": 0.1, "num_predict": 250}
)
response_text = response['response'].strip()
if self.debug:
print(f"\n[DEBUG] Fact-check response:\n{response_text}\n")
# Try to parse structured text format
verdict = "dubious"
confidence = 0.5
explanation = "No explanation provided"
# Extract VERDICT
import re
verdict_match = re.search(r'VERDICT:\s*(\w+)', response_text, re.IGNORECASE)
if verdict_match:
verdict = verdict_match.group(1).lower()
# Extract CONFIDENCE
conf_match = re.search(r'CONFIDENCE:\s*([\d.]+)', response_text, re.IGNORECASE)
if conf_match:
try:
confidence = float(conf_match.group(1))
confidence = max(0.0, min(1.0, confidence)) # Clamp to 0-1
except ValueError:
pass
# Extract EXPLANATION
expl_match = re.search(r'EXPLANATION:\s*(.+?)(?:\n\n|\Z)', response_text, re.IGNORECASE | re.DOTALL)
if expl_match:
explanation = expl_match.group(1).strip()
# Clean up incomplete sentences
if explanation and not explanation[-1] in '.!?':
# Try to find last complete sentence
last_period = max(explanation.rfind('.'), explanation.rfind('!'), explanation.rfind('?'))
if last_period > 20: # Keep at least some text
explanation = explanation[:last_period + 1]
return {
"verdict": verdict,
"confidence": confidence,
"explanation": explanation[:250] if explanation else "Analysis completed",
"sources": [],
"corrections": ""
}
except Exception as e:
if self.debug:
print(f"[DEBUG] Fact-check error: {e}")
return {
"verdict": "error",
"confidence": 0.0,
"explanation": f"Analysis failed: {str(e)}",
"sources": [],
"corrections": ""
}
def generate_augmenting_questions(self, text, context=""):
"""Generate insightful questions based on the text"""
prompt = f"""Generate 3 questions about this. Reply in this exact format:
Q1: [question]
Q2: [question]
Q3: [question]
Statement: "{text}"
"""
try:
response = ollama.generate(
model=self.model,
prompt=prompt,
options={"temperature": 0.7, "num_predict": 250}
)
response_text = response['response'].strip()
if self.debug:
print(f"\n[DEBUG] Questions response:\n{response_text}\n")
# Extract questions
import re
questions = []
for i in range(1, 4):
q_match = re.search(rf'Q{i}:\s*(.+?)(?:\n|$)', response_text, re.IGNORECASE)
if q_match:
question = q_match.group(1).strip()
# Clean up incomplete questions
if question and not question[-1] in '?':
# Try to find last complete question
last_q = question.rfind('?')
if last_q > 10:
question = question[:last_q + 1]
else:
question = question + "?"
questions.append(question)
# If we couldn't parse, try to split by newlines and take first 3 non-empty lines
if len(questions) < 3:
lines = [line.strip() for line in response_text.split('\n') if line.strip()]
# Filter out lines that look like question markers
lines = [l for l in lines if not re.match(r'^Q\d+:?\s*$', l)]
for line in lines[:3]:
if not line.endswith('?'):
line = line + "?"
questions.append(line)
# Ensure we have exactly 3 questions
default_questions = [
"What are the key points here?",
"What evidence supports this?",
"What are the implications?"
]
while len(questions) < 3:
questions.append(default_questions[len(questions)])
return {
"questions": questions[:3],
"topics": []
}
except Exception as e:
if self.debug:
print(f"[DEBUG] Questions error: {e}")
return {
"questions": [
"What are the key points?",
"What supports this claim?",
"What are the implications?"
],
"topics": []
}
def list_audio_devices():
"""Print all available audio input devices"""
print("\nAvailable audio capture devices:")
devices = sd.query_devices()
for i, dev in enumerate(devices):
if dev['max_input_channels'] > 0:
print(f" [{i}] {dev['name']}")
print(f" Channels: {dev['max_input_channels']} | Sample Rate: {dev['default_samplerate']}")
print()
def save_transcript(text, timestamp, filename):
"""Append transcript to file"""
os.makedirs(os.path.dirname(filename) if os.path.dirname(filename) else '.', exist_ok=True)
with open(filename, "a", encoding="utf-8") as f:
f.write(f"[{timestamp}] {text}\n")
def save_enriched_transcript(data, filename):
"""Save enriched transcript with LLM analysis"""
os.makedirs(os.path.dirname(filename) if os.path.dirname(filename) else '.', exist_ok=True)
with open(filename, "a", encoding="utf-8") as f:
f.write(f"\n{'='*70}\n")
f.write(f"[{data['timestamp']}] {data['text']}\n\n")
if 'fact_check' in data:
fc = data['fact_check']
f.write(f"📊 Fact Check: {fc.get('verdict', 'N/A').upper()} "
f"(confidence: {fc.get('confidence', 0):.2f})\n")
f.write(f"💡 {fc.get('explanation', 'N/A')}\n")
if fc.get('corrections'):
f.write(f"✏️ Correction: {fc['corrections']}\n")
f.write("\n")
if 'questions' in data and data['questions'].get('questions'):
f.write("❓ Questions:\n")
for i, q in enumerate(data['questions']['questions'], 1):
f.write(f"{i}. {q}\n")
f.write("\n")
def display_enriched_output(text, timestamp, fact_check=None, questions=None):
"""Display transcript with LLM analysis"""
print(f"\n[{timestamp}] {text}")
if fact_check:
verdict_emoji = {
'factual': '',
'dubious': '⚠️',
'not_factual': '',
'error': '⚠️'
}
emoji = verdict_emoji.get(fact_check.get('verdict', 'error'), '')
print(f"\n{emoji} Fact Check: {fact_check.get('verdict', 'N/A').upper()} "
f"(confidence: {fact_check.get('confidence', 0):.2f})")
print(f"💡 {fact_check.get('explanation', 'N/A')}")
if fact_check.get('corrections'):
print(f"✏️ Correction: {fact_check['corrections']}")
if questions and questions.get('questions'):
print(f"\n❓ Questions:")
for i, q in enumerate(questions['questions'], 1):
print(f" {i}. {q}")
def main():
parser = argparse.ArgumentParser(
description="Real-time transcription of Windows speaker output",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python transcribe_speakers.py
python transcribe_speakers.py --model small --language es --interval 5
python transcribe_speakers.py --device "Speakers" --output "meeting.txt"
python transcribe_speakers.py --model medium --interval 10 --output transcripts/live.txt
"""
)
parser.add_argument("--model", default="base",
choices=["tiny", "base", "small", "medium", "large"],
help="Whisper model size (default: base)")
parser.add_argument("--language", default="en",
help="Language code (default: en)")
parser.add_argument("--device", metavar="NAME",
help="Audio device name (partial match). If not specified, auto-detects")
parser.add_argument("--interval", type=float, default=8.0,
help="Processing interval in seconds (default: 8.0)")
parser.add_argument("--min-duration", type=float, default=3.0,
help="Minimum audio duration before transcription (default: 3.0)")
parser.add_argument("--fast-mode", action="store_true",
help="Enable fast mode (lower accuracy, faster transcription)")
parser.add_argument("--output", "-o", metavar="FILE",
help="Save transcript to file (e.g., transcript.txt)")
parser.add_argument("--list-devices", action="store_true",
help="List all available audio devices and exit")
parser.add_argument("--force-cpu", action="store_true",
help="Force CPU processing (disable GPU acceleration)")
parser.add_argument("--gpu-index", type=int, default=0,
help="GPU device index to use (default: 0)")
parser.add_argument("--enable-llm", action="store_true",
help="Enable LLM analysis (fact-checking and questions)")
parser.add_argument("--llm-model", default="gpt-oss:20b",
help="Ollama model to use for LLM analysis (default: gpt-oss:20b)")
parser.add_argument("--llm-debug", action="store_true",
help="Show LLM raw responses for debugging")
args = parser.parse_args()
if args.list_devices:
list_audio_devices()
return
print("=== Windows Real-Time Audio Transcription ===")
print(f"Model: {args.model} | Language: {args.language} | Interval: {args.interval}s")
if args.output:
print(f"Output: {args.output}")
if args.enable_llm:
print(f"LLM Analysis: Enabled ({args.llm_model})")
# Initialize audio capture
try:
capturer = WindowsLoopbackAudioCapture(
device_name=args.device,
sample_rate=16000,
chunk_size=2048
)
except RuntimeError as e:
print(f"\n❌ Audio Error: {e}")
print("\nTo fix this:")
print("1. Right-click speaker icon → Sounds → Recording tab")
print("2. Right-click in empty area → Show Disabled Devices")
print("3. Enable 'Stereo Mix' → Set as Default Device")
print("\nAlternative: Install VB-Cable (free) from vb-audio.com")
print(" Then use: --device 'CABLE Output'")
list_audio_devices()
return
# Initialize transcriber
try:
transcriber = WhisperStreamTranscriber(
model_name=args.model,
language=args.language,
force_cpu=args.force_cpu,
device_index=args.gpu_index
)
except Exception as e:
print(f"\n❌ Model Error: {e}")
print("Make sure you installed Whisper correctly")
return
# Initialize LLM analyzer (optional)
llm_analyzer = None
if args.enable_llm:
try:
llm_analyzer = LocalLLMAnalyzer(model=args.llm_model, debug=args.llm_debug)
except RuntimeError as e:
print(f"\n❌ LLM Error: {e}")
print("Continuing without LLM analysis...")
llm_analyzer = None
# Main processing loop
print(f"\n✅ Started transcription. Press Ctrl+C to stop.\n{'=' * 50}")
last_process_time = time.time()
total_duration = 0
segment_count = 0
# Thread pool for concurrent LLM processing
llm_executor = ThreadPoolExecutor(max_workers=2) if llm_analyzer else None
pending_llm_tasks = {} # Maps segment_count -> future
try:
while True:
# Collect audio
chunk = capturer.read_chunk()
if chunk is not None:
transcriber.add_audio(chunk)
total_duration += len(chunk) / 16000
# Process at intervals
current_time = time.time()
if current_time - last_process_time >= args.interval:
text = transcriber.transcribe_chunk(
min_duration=args.min_duration,
fast_mode=args.fast_mode
)
if text:
segment_count += 1
timestamp = datetime.now().strftime("%H:%M:%S")
# Display transcription immediately (don't wait for LLM)
print(f"[{timestamp}] {text}")
# LLM Analysis (run concurrently in background)
if llm_analyzer:
context = f"Segment {segment_count}"
# Submit LLM tasks to thread pool
def run_llm_analysis(txt, ctx, ts, seg_num):
fc = llm_analyzer.fact_check(txt, ctx)
qs = llm_analyzer.generate_augmenting_questions(txt, ctx)
return {
'timestamp': ts,
'text': txt,
'segment_count': seg_num,
'fact_check': fc,
'questions': qs
}
future = llm_executor.submit(run_llm_analysis, text, context, timestamp, segment_count)
pending_llm_tasks[segment_count] = future
else:
# Save transcript immediately without LLM
if args.output:
save_transcript(text, timestamp, args.output)
last_process_time = current_time
# Check for completed LLM tasks (non-blocking)
if llm_analyzer:
completed_segments = []
for seg_num, future in pending_llm_tasks.items():
if future.done():
try:
result = future.result()
# Display enriched output
display_enriched_output(
result['text'],
result['timestamp'],
result['fact_check'],
result['questions']
)
# Save enriched output
if args.output:
save_enriched_transcript(result, args.output)
completed_segments.append(seg_num)
except Exception as e:
print(f"⚠️ LLM processing error for segment {seg_num}: {e}")
completed_segments.append(seg_num)
# Remove completed tasks
for seg_num in completed_segments:
del pending_llm_tasks[seg_num]
except KeyboardInterrupt:
print(f"\n{'=' * 50}\n🛑 Stopping transcription...")
# Wait for pending LLM tasks to complete
if llm_analyzer and pending_llm_tasks:
print(f"\n⏳ Waiting for {len(pending_llm_tasks)} pending LLM tasks to complete...")
for seg_num, future in pending_llm_tasks.items():
try:
result = future.result(timeout=30)
display_enriched_output(
result['text'],
result['timestamp'],
result['fact_check'],
result['questions']
)
if args.output:
save_enriched_transcript(result, args.output)
except Exception as e:
print(f"⚠️ LLM task {seg_num} failed: {e}")
# Shutdown executor
if llm_executor:
llm_executor.shutdown(wait=True)
# Cleanup
capturer.close()
# Process remaining audio
print("\nProcessing remaining audio...")
final_text = transcriber.transcribe_chunk(min_duration=0)
if final_text:
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] {final_text}")
# LLM Analysis for final segment (synchronous since we're shutting down)
if llm_analyzer:
fact_check = llm_analyzer.fact_check(final_text, "Final segment")
questions = llm_analyzer.generate_augmenting_questions(final_text)
display_enriched_output(final_text, timestamp, fact_check, questions)
if args.output:
data = {
'timestamp': timestamp,
'text': final_text,
'fact_check': fact_check,
'questions': questions
}
save_enriched_transcript(data, args.output)
else:
if args.output:
save_transcript(final_text, timestamp, args.output)
# Summary
print(f"\n✅ Complete! Processed {total_duration:.1f}s of audio")
print(f" Generated {segment_count} transcript segments")
if args.output and os.path.exists(args.output):
abs_path = os.path.abspath(args.output)
print(f"💾 Transcript saved to: {abs_path}")
if __name__ == "__main__":
main()

636
transcribe_speakers_llm.py Executable file
View File

@@ -0,0 +1,636 @@
#!/usr/bin/env python3
"""
Real-time transcription of Windows speaker output using loopback capture.
Captures system audio and transcribes with Whisper in near real-time.
"""
import sounddevice as sd
import numpy as np
import threading
import queue
import time
import os
import argparse
import json
from datetime import datetime
# Choose your Whisper backend here:
# For faster-whisper (recommended):
from faster_whisper import WhisperModel
# LLM integration
try:
import ollama
OLLAMA_AVAILABLE = True
except ImportError:
OLLAMA_AVAILABLE = False
# # For regular whisper (comment out the line above and uncomment these):
# import whisper
class WindowsLoopbackAudioCapture:
"""Capture Windows speaker output using WASAPI loopback"""
def __init__(self, device_name=None, sample_rate=16000, chunk_size=2048):
self.sample_rate = sample_rate
self.chunk_size = chunk_size
# Find loopback device
self.device_info = self._find_loopback_device(device_name)
if not self.device_info:
raise RuntimeError(
"No loopback device found.\n"
"1. Ensure your speakers/headphones are connected\n"
"2. Enable 'Stereo Mix' in Sound settings\n"
"3. Or install VB-Cable virtual audio device"
)
print(f"✓ Using device: {self.device_info['name']} (index {self.device_info['index']})")
# Queue for audio data
self.audio_queue = queue.Queue()
self.stop_event = threading.Event()
# Start the stream
try:
self.stream = sd.InputStream(
device=self.device_info['index'],
channels=1,
samplerate=sample_rate,
blocksize=chunk_size,
dtype='int16',
latency='low',
callback=self._audio_callback
)
self.stream.start()
print("✓ Audio capture stream started")
except Exception as e:
raise RuntimeError(f"Failed to start audio stream: {e}")
def _find_loopback_device(self, device_name):
"""Find the speaker device with loopback capability"""
devices = sd.query_devices()
# If device name specified, find exact match
if device_name:
for dev in devices:
if (device_name.lower() in dev['name'].lower() and
dev['max_input_channels'] > 0):
return dev
# Auto-detect: look for WASAPI speakers/headphones
for dev in devices:
if (dev['max_input_channels'] > 0 and
any(x in dev['name'] for x in ['Speakers', 'Headphones', 'Output'])):
return dev
# Fallback: Stereo Mix or similar
for dev in devices:
if 'Stereo Mix' in dev['name']:
return dev
return None
def _audio_callback(self, indata, frames, time_info, status):
"""Callback for audio data"""
if status:
print(f"⚠ Audio status: {status}")
self.audio_queue.put(indata.copy())
def read_chunk(self):
"""Read audio data from queue"""
try:
return self.audio_queue.get(timeout=0.05).flatten()
except queue.Empty:
return None
def close(self):
"""Cleanup resources"""
if hasattr(self, 'stream'):
self.stream.stop()
self.stream.close()
class WhisperStreamTranscriber:
"""Process audio chunks with Whisper/faster-whisper"""
def __init__(self, model_name="base", language="en", force_cpu=False):
print(f"Loading Whisper model '{model_name}'...")
# Check for CUDA availability
import torch
has_cuda = torch.cuda.is_available() and not force_cpu
# Force CPU if CUDA libraries incompatible
device = "cpu"
compute_type = "int8"
if has_cuda:
try:
# Test if CTranslate2 can actually use CUDA
import ctranslate2
cuda_count = ctranslate2.get_cuda_device_count()
if cuda_count > 0:
device = "cuda"
compute_type = "float16"
print(f"Using device: cuda ({torch.cuda.get_device_name(0)})")
else:
print(f"CUDA available in PyTorch but not in CTranslate2. Using CPU.")
except Exception as e:
print(f"CUDA libraries not found ({e}). Using CPU.")
else:
print("Using device: cpu")
# FASTER-WHISPER (recommended):
model_kwargs = {
"device": device,
"compute_type": compute_type
}
if not has_cuda:
model_kwargs["cpu_threads"] = 4
self.model = WhisperModel(model_name, **model_kwargs)
self.language = language
self.audio_buffer = np.array([], dtype=np.float32)
self.lock = threading.Lock()
# # REGULAR WHISPER:
# self.model = whisper.load_model(model_name)
# self.language = language
# self.audio_buffer = np.array([], dtype=np.float32)
# self.lock = threading.Lock()
def add_audio(self, audio_chunk):
"""Add new audio data to buffer"""
with self.lock:
audio_float = audio_chunk.astype(np.float32) / 32768.0
self.audio_buffer = np.concatenate([self.audio_buffer, audio_float])
def transcribe_chunk(self, min_duration=5.0):
"""Transcribe accumulated audio if enough duration"""
with self.lock:
duration = len(self.audio_buffer) / 16000
if duration < min_duration:
return None
audio_to_process = self.audio_buffer.copy()
self.audio_buffer = np.array([], dtype=np.float32)
# Process with FASTER-WHISPER:
try:
segments, _ = self.model.transcribe(
audio_to_process,
language=self.language,
beam_size=5,
vad_filter=True,
vad_parameters=dict(min_silence_duration_ms=500),
word_timestamps=False
)
text = " ".join([segment.text for segment in segments]).strip()
return text if text else None
except Exception as e:
print(f"❌ Transcription error: {e}")
return None
# # REGULAR WHISPER:
# try:
# result = self.model.transcribe(
# audio_to_process,
# language=self.language,
# task="transcribe",
# fp16=False
# )
# return result["text"].strip()
# except Exception as e:
# print(f"❌ Transcription error: {e}")
# return None
class LocalLLMAnalyzer:
"""Local LLM for fact-checking and question generation using Ollama"""
def __init__(self, model="llama3.2", debug=False):
if not OLLAMA_AVAILABLE:
raise RuntimeError(
"Ollama package not installed.\n"
"Install with: pip install ollama"
)
self.model = model
self.debug = debug
self._test_connection()
def _test_connection(self):
"""Test connection to Ollama service"""
try:
ollama.list()
print(f"✓ Ollama connected using model: {self.model}")
except Exception as e:
raise RuntimeError(
f"Cannot connect to Ollama. Ensure it's installed and running.\n"
f"Error: {e}\n"
f"Install from: https://ollama.ai\n"
f"Then run: ollama pull {self.model}"
)
def _extract_json(self, text):
"""Extract JSON from text that might contain markdown or other formatting"""
# Try to find JSON block in markdown code fence
import re
json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', text, re.DOTALL)
if json_match:
return json_match.group(1)
# Try to find raw JSON object
json_match = re.search(r'\{.*\}', text, re.DOTALL)
if json_match:
return json_match.group(0)
return text
def fact_check(self, text, context=""):
"""Analyze text for factual accuracy"""
# Try simple structured format first
prompt = f"""Analyze this for accuracy. Reply in this exact format:
VERDICT: [factual/dubious/not_factual]
CONFIDENCE: [0.0-1.0]
EXPLANATION: [one sentence]
Statement: "{text}"
"""
try:
response = ollama.generate(
model=self.model,
prompt=prompt,
options={"temperature": 0.1, "num_predict": 150}
)
response_text = response['response'].strip()
if self.debug:
print(f"\n[DEBUG] Fact-check response:\n{response_text}\n")
# Try to parse structured text format
verdict = "dubious"
confidence = 0.5
explanation = response_text
# Extract VERDICT
import re
verdict_match = re.search(r'VERDICT:\s*(\w+)', response_text, re.IGNORECASE)
if verdict_match:
verdict = verdict_match.group(1).lower()
# Extract CONFIDENCE
conf_match = re.search(r'CONFIDENCE:\s*([\d.]+)', response_text, re.IGNORECASE)
if conf_match:
try:
confidence = float(conf_match.group(1))
confidence = max(0.0, min(1.0, confidence)) # Clamp to 0-1
except ValueError:
pass
# Extract EXPLANATION
expl_match = re.search(r'EXPLANATION:\s*(.+?)(?:\n|$)', response_text, re.IGNORECASE | re.DOTALL)
if expl_match:
explanation = expl_match.group(1).strip()
return {
"verdict": verdict,
"confidence": confidence,
"explanation": explanation[:200], # Truncate if too long
"sources": [],
"corrections": ""
}
except Exception as e:
if self.debug:
print(f"[DEBUG] Fact-check error: {e}")
return {
"verdict": "error",
"confidence": 0.0,
"explanation": f"Analysis failed: {str(e)}",
"sources": [],
"corrections": ""
}
def generate_augmenting_questions(self, text, context=""):
"""Generate insightful questions based on the text"""
prompt = f"""Generate 3 questions about this. Reply in this exact format:
Q1: [question]
Q2: [question]
Q3: [question]
Statement: "{text}"
"""
try:
response = ollama.generate(
model=self.model,
prompt=prompt,
options={"temperature": 0.7, "num_predict": 150}
)
response_text = response['response'].strip()
if self.debug:
print(f"\n[DEBUG] Questions response:\n{response_text}\n")
# Extract questions
import re
questions = []
for i in range(1, 4):
q_match = re.search(rf'Q{i}:\s*(.+?)(?:\n|$)', response_text, re.IGNORECASE)
if q_match:
questions.append(q_match.group(1).strip())
# If we couldn't parse, try to split by newlines and take first 3 non-empty lines
if len(questions) < 3:
lines = [line.strip() for line in response_text.split('\n') if line.strip()]
questions = lines[:3] if lines else [
"What are the key points here?",
"What evidence supports this?",
"What are the implications?"
]
# Ensure we have exactly 3 questions
while len(questions) < 3:
questions.append("What else should we consider?")
return {
"questions": questions[:3],
"topics": []
}
except Exception as e:
if self.debug:
print(f"[DEBUG] Questions error: {e}")
return {
"questions": [
"What are the key points?",
"What supports this claim?",
"What are the implications?"
],
"topics": []
}
def list_audio_devices():
"""Print all available audio input devices"""
print("\nAvailable audio capture devices:")
devices = sd.query_devices()
for i, dev in enumerate(devices):
if dev['max_input_channels'] > 0:
print(f" [{i}] {dev['name']}")
print(f" Channels: {dev['max_input_channels']} | Sample Rate: {dev['default_samplerate']}")
print()
def save_transcript(text, timestamp, filename):
"""Append transcript to file"""
os.makedirs(os.path.dirname(filename) if os.path.dirname(filename) else '.', exist_ok=True)
with open(filename, "a", encoding="utf-8") as f:
f.write(f"[{timestamp}] {text}\n")
def save_enriched_transcript(data, filename):
"""Save enriched transcript with LLM analysis"""
os.makedirs(os.path.dirname(filename) if os.path.dirname(filename) else '.', exist_ok=True)
with open(filename, "a", encoding="utf-8") as f:
f.write(f"\n{'='*70}\n")
f.write(f"[{data['timestamp']}] {data['text']}\n\n")
if 'fact_check' in data:
fc = data['fact_check']
f.write(f"📊 Fact Check: {fc.get('verdict', 'N/A').upper()} "
f"(confidence: {fc.get('confidence', 0):.2f})\n")
f.write(f"💡 {fc.get('explanation', 'N/A')}\n")
if fc.get('corrections'):
f.write(f"✏️ Correction: {fc['corrections']}\n")
f.write("\n")
if 'questions' in data and data['questions'].get('questions'):
f.write("❓ Questions:\n")
for i, q in enumerate(data['questions']['questions'], 1):
f.write(f"{i}. {q}\n")
f.write("\n")
def display_enriched_output(text, timestamp, fact_check=None, questions=None):
"""Display transcript with LLM analysis"""
print(f"\n[{timestamp}] {text}")
if fact_check:
verdict_emoji = {
'factual': '',
'dubious': '⚠️',
'not_factual': '',
'error': '⚠️'
}
emoji = verdict_emoji.get(fact_check.get('verdict', 'error'), '')
print(f"\n{emoji} Fact Check: {fact_check.get('verdict', 'N/A').upper()} "
f"(confidence: {fact_check.get('confidence', 0):.2f})")
print(f"💡 {fact_check.get('explanation', 'N/A')}")
if fact_check.get('corrections'):
print(f"✏️ Correction: {fact_check['corrections']}")
if questions and questions.get('questions'):
print(f"\n❓ Questions:")
for i, q in enumerate(questions['questions'], 1):
print(f" {i}. {q}")
def main():
parser = argparse.ArgumentParser(
description="Real-time transcription of Windows speaker output",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python transcribe_speakers.py
python transcribe_speakers.py --model small --language es --interval 5
python transcribe_speakers.py --device "Speakers" --output "meeting.txt"
python transcribe_speakers.py --model medium --interval 10 --output transcripts/live.txt
"""
)
parser.add_argument("--model", default="base",
choices=["tiny", "base", "small", "medium", "large"],
help="Whisper model size (default: base)")
parser.add_argument("--language", default="en",
help="Language code (default: en)")
parser.add_argument("--device", metavar="NAME",
help="Audio device name (partial match). If not specified, auto-detects")
parser.add_argument("--interval", type=float, default=8.0,
help="Processing interval in seconds (default: 8.0)")
parser.add_argument("--output", "-o", metavar="FILE",
help="Save transcript to file (e.g., transcript.txt)")
parser.add_argument("--list-devices", action="store_true",
help="List all available audio devices and exit")
parser.add_argument("--force-cpu", action="store_true",
help="Force CPU processing (disable GPU acceleration)")
parser.add_argument("--enable-llm", action="store_true",
help="Enable LLM analysis (fact-checking and questions)")
parser.add_argument("--llm-model", default="gpt-oss:20b",
help="Ollama model to use for LLM analysis (default: gpt-oss:20b)")
parser.add_argument("--llm-debug", action="store_true",
help="Show LLM raw responses for debugging")
args = parser.parse_args()
if args.list_devices:
list_audio_devices()
return
print("=== Windows Real-Time Audio Transcription ===")
print(f"Model: {args.model} | Language: {args.language} | Interval: {args.interval}s")
if args.output:
print(f"Output: {args.output}")
if args.enable_llm:
print(f"LLM Analysis: Enabled ({args.llm_model})")
# Initialize audio capture
try:
capturer = WindowsLoopbackAudioCapture(
device_name=args.device,
sample_rate=16000,
chunk_size=2048
)
except RuntimeError as e:
print(f"\n❌ Audio Error: {e}")
print("\nTo fix this:")
print("1. Right-click speaker icon → Sounds → Recording tab")
print("2. Right-click in empty area → Show Disabled Devices")
print("3. Enable 'Stereo Mix' → Set as Default Device")
print("\nAlternative: Install VB-Cable (free) from vb-audio.com")
print(" Then use: --device 'CABLE Output'")
list_audio_devices()
return
# Initialize transcriber
try:
transcriber = WhisperStreamTranscriber(
model_name=args.model,
language=args.language,
force_cpu=args.force_cpu
)
except Exception as e:
print(f"\n❌ Model Error: {e}")
print("Make sure you installed Whisper correctly")
return
# Initialize LLM analyzer (optional)
llm_analyzer = None
if args.enable_llm:
try:
llm_analyzer = LocalLLMAnalyzer(model=args.llm_model, debug=args.llm_debug)
except RuntimeError as e:
print(f"\n❌ LLM Error: {e}")
print("Continuing without LLM analysis...")
llm_analyzer = None
# Main processing loop
print(f"\n✅ Started transcription. Press Ctrl+C to stop.\n{'=' * 50}")
last_process_time = time.time()
total_duration = 0
segment_count = 0
try:
while True:
# Collect audio
chunk = capturer.read_chunk()
if chunk is not None:
transcriber.add_audio(chunk)
total_duration += len(chunk) / 16000
# Process at intervals
current_time = time.time()
if current_time - last_process_time >= args.interval:
text = transcriber.transcribe_chunk()
if text:
segment_count += 1
timestamp = datetime.now().strftime("%H:%M:%S")
# LLM Analysis
fact_check = None
questions = None
if llm_analyzer:
context = f"Segment {segment_count}"
fact_check = llm_analyzer.fact_check(text, context)
questions = llm_analyzer.generate_augmenting_questions(text, context)
# Display output
if llm_analyzer:
display_enriched_output(text, timestamp, fact_check, questions)
else:
print(f"[{timestamp}] {text}")
# Save output
if args.output:
if llm_analyzer:
data = {
'timestamp': timestamp,
'text': text,
'fact_check': fact_check,
'questions': questions
}
save_enriched_transcript(data, args.output)
else:
save_transcript(text, timestamp, args.output)
last_process_time = current_time
except KeyboardInterrupt:
print(f"\n{'=' * 50}\n🛑 Stopping transcription...")
# Cleanup
capturer.close()
# Process remaining audio
print("\nProcessing remaining audio...")
final_text = transcriber.transcribe_chunk(min_duration=0)
if final_text:
timestamp = datetime.now().strftime("%H:%M:%S")
# LLM Analysis for final segment
fact_check = None
questions = None
if llm_analyzer:
fact_check = llm_analyzer.fact_check(final_text, "Final segment")
questions = llm_analyzer.generate_augmenting_questions(final_text)
# Display output
if llm_analyzer:
display_enriched_output(final_text, timestamp, fact_check, questions)
else:
print(f"[{timestamp}] {final_text}")
# Save output
if args.output:
if llm_analyzer:
data = {
'timestamp': timestamp,
'text': final_text,
'fact_check': fact_check,
'questions': questions
}
save_enriched_transcript(data, args.output)
else:
save_transcript(final_text, timestamp, args.output)
# Summary
print(f"\n✅ Complete! Processed {total_duration:.1f}s of audio")
print(f" Generated {segment_count} transcript segments")
if args.output and os.path.exists(args.output):
abs_path = os.path.abspath(args.output)
print(f"💾 Transcript saved to: {abs_path}")
if __name__ == "__main__":
main()

40
voice/UWV.md Normal file
View File

@@ -0,0 +1,40 @@
5jaar geleden operatie, complicaties, PTSS
ik heb gewerkt in de tussentijd, operatie uigesteld
lang verhaal...
Na verwijdering galblaas leek verbetering, oxycodon gebruikt.
Ik voelde me de oude, kon van alles, kleine klusjes.
Maar oxycodon raakte op en peesontsteking.
Zware benen, veel rust.
Daarna gelijkelijk opbouwen, maar na korte periode inspanning zijn de benen overbelast.
Dat had ik in de gaten, veel benen tot "rust" brengen, maar ze zijn continu te zwaar. Trekken me naar beneden.
Ik ben momenteel aan het kijken naar een apparaat voor bovenlichaam
Ik kan prima een uur gaan werken, met de fysieke problemen.
Er zijn wel mentale problemen, gesprek met mijn tandarts laatst, ben daar een dag mee bezig om te verwerken.
-- En deze gesprekken het zweet komt uit me voorhoofd enkel bij de gedachten van het gesprek.
**UWV gesprek korte reminder**
* **Achtergrond:** 5 jaar geleden operatie → complicaties + PTSS. In de jaren erna wél gewerkt; operatie lang uitgesteld.
* **Tijdelijke verbetering:** na galblaasverwijdering voelde ik me duidelijk beter. Met oxycodon kon ik weer “mijn oude zelf” zijn en kleine klusjes doen.
* **Terugval:** oxycodon stopte + peesontsteking → zware benen, veel rust nodig.
* **Huidig patroon:** rustig opbouwen lukt even, maar na korte inspanning raken mijn benen snel overbelast.
* **Onderzoek:** 1 dec fotos/beeldvorming van het been. Met huisarts besproken: geen extra complicaties/geen nieuwe scheurtjes.
* **Specialist:** verwijzing orthopeed; afspraak gehad op 10 dec.
* **Besluit:** gekozen voor **kunstheup** → ik sta nu **op de wachtlijst**.
* **Wat ik nu doe:** zoeken naar trainingsoptie die benen spaart (apparaat voor **bovenlichaam**) om conditie te behouden.
**Afspraken (op datum/volgorde)**
* **1 dec:** Fotos/beeldvorming van het been gemaakt.
* **(na 1 dec) Huisarts:** Besproken; geen aanwijzingen voor extra complicaties/nieuwe scheurtjes → **verwijzing orthopeed**.
* **10 dec:** Afspraak bij **orthopeed**.
* **Nu:** Besluit **kunstheup** → je staat **op de wachtlijst**.
**Overig (geen afspraak, wel relevant voor UWV)**
* Je kunt fysiek vaak nog ± **1 uur** werken, maar benen worden snel zwaar/overbelast.
* Mentale belasting/angst rond gesprekken (bv. tandarts, UWV) met sterke stressreactie.