synapse-sdk 1.0.0a35__py3-none-any.whl → 2025.11.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of synapse-sdk might be problematic. Click here for more details.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +308 -5
- synapse_sdk/cli/alias/utils.py +1 -1
- synapse_sdk/cli/code_server.py +687 -0
- synapse_sdk/cli/config.py +440 -0
- synapse_sdk/cli/devtools.py +90 -0
- synapse_sdk/cli/plugin/publish.py +23 -15
- synapse_sdk/clients/agent/__init__.py +9 -3
- synapse_sdk/clients/agent/container.py +133 -0
- synapse_sdk/clients/agent/core.py +19 -0
- synapse_sdk/clients/agent/ray.py +298 -9
- synapse_sdk/clients/backend/__init__.py +28 -12
- synapse_sdk/clients/backend/annotation.py +9 -1
- synapse_sdk/clients/backend/core.py +31 -4
- synapse_sdk/clients/backend/data_collection.py +186 -0
- synapse_sdk/clients/backend/hitl.py +1 -1
- synapse_sdk/clients/backend/integration.py +4 -3
- synapse_sdk/clients/backend/ml.py +1 -1
- synapse_sdk/clients/backend/models.py +35 -1
- synapse_sdk/clients/base.py +309 -36
- synapse_sdk/clients/ray/serve.py +2 -0
- synapse_sdk/devtools/__init__.py +0 -0
- synapse_sdk/devtools/config.py +94 -0
- synapse_sdk/devtools/docs/.gitignore +20 -0
- synapse_sdk/devtools/docs/README.md +41 -0
- synapse_sdk/devtools/docs/blog/2019-05-28-first-blog-post.md +12 -0
- synapse_sdk/devtools/docs/blog/2019-05-29-long-blog-post.md +44 -0
- synapse_sdk/devtools/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- synapse_sdk/devtools/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- synapse_sdk/devtools/docs/blog/2021-08-26-welcome/index.md +29 -0
- synapse_sdk/devtools/docs/blog/authors.yml +25 -0
- synapse_sdk/devtools/docs/blog/tags.yml +19 -0
- synapse_sdk/devtools/docs/docs/api/clients/agent.md +43 -0
- synapse_sdk/devtools/docs/docs/api/clients/annotation-mixin.md +378 -0
- synapse_sdk/devtools/docs/docs/api/clients/backend.md +420 -0
- synapse_sdk/devtools/docs/docs/api/clients/base.md +257 -0
- synapse_sdk/devtools/docs/docs/api/clients/core-mixin.md +477 -0
- synapse_sdk/devtools/docs/docs/api/clients/data-collection-mixin.md +422 -0
- synapse_sdk/devtools/docs/docs/api/clients/hitl-mixin.md +554 -0
- synapse_sdk/devtools/docs/docs/api/clients/index.md +391 -0
- synapse_sdk/devtools/docs/docs/api/clients/integration-mixin.md +571 -0
- synapse_sdk/devtools/docs/docs/api/clients/ml-mixin.md +578 -0
- synapse_sdk/devtools/docs/docs/api/clients/ray.md +342 -0
- synapse_sdk/devtools/docs/docs/api/index.md +52 -0
- synapse_sdk/devtools/docs/docs/api/plugins/categories.md +43 -0
- synapse_sdk/devtools/docs/docs/api/plugins/models.md +114 -0
- synapse_sdk/devtools/docs/docs/api/plugins/utils.md +328 -0
- synapse_sdk/devtools/docs/docs/categories.md +0 -0
- synapse_sdk/devtools/docs/docs/cli-usage.md +280 -0
- synapse_sdk/devtools/docs/docs/concepts/index.md +38 -0
- synapse_sdk/devtools/docs/docs/configuration.md +83 -0
- synapse_sdk/devtools/docs/docs/contributing.md +306 -0
- synapse_sdk/devtools/docs/docs/examples/index.md +29 -0
- synapse_sdk/devtools/docs/docs/faq.md +179 -0
- synapse_sdk/devtools/docs/docs/features/converters/index.md +455 -0
- synapse_sdk/devtools/docs/docs/features/index.md +24 -0
- synapse_sdk/devtools/docs/docs/features/utils/file.md +415 -0
- synapse_sdk/devtools/docs/docs/features/utils/network.md +378 -0
- synapse_sdk/devtools/docs/docs/features/utils/storage.md +57 -0
- synapse_sdk/devtools/docs/docs/features/utils/types.md +51 -0
- synapse_sdk/devtools/docs/docs/installation.md +94 -0
- synapse_sdk/devtools/docs/docs/introduction.md +47 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/neural-net-plugins/train-action-overview.md +814 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-action.md +948 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-overview.md +544 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-template.md +766 -0
- synapse_sdk/devtools/docs/docs/plugins/export-plugins.md +1092 -0
- synapse_sdk/devtools/docs/docs/plugins/plugins.md +852 -0
- synapse_sdk/devtools/docs/docs/quickstart.md +78 -0
- synapse_sdk/devtools/docs/docs/troubleshooting.md +519 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/_category_.json +8 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/congratulations.md +23 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-document.md +57 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-page.md +43 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
- synapse_sdk/devtools/docs/docs/tutorial-extras/_category_.json +7 -0
- synapse_sdk/devtools/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
- synapse_sdk/devtools/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
- synapse_sdk/devtools/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
- synapse_sdk/devtools/docs/docs/tutorial-extras/translate-your-site.md +88 -0
- synapse_sdk/devtools/docs/docusaurus.config.ts +148 -0
- synapse_sdk/devtools/docs/i18n/ko/code.json +325 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/agent.md +43 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/annotation-mixin.md +289 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/backend.md +420 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/base.md +257 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/core-mixin.md +417 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/data-collection-mixin.md +356 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/hitl-mixin.md +192 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/index.md +391 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/integration-mixin.md +479 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/ml-mixin.md +284 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/ray.md +342 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/index.md +52 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/plugins/models.md +114 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/categories.md +0 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/cli-usage.md +280 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/concepts/index.md +38 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/configuration.md +83 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/contributing.md +306 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/examples/index.md +29 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/faq.md +179 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/converters/index.md +30 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/index.md +24 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/file.md +415 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/network.md +378 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/storage.md +60 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/types.md +51 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/installation.md +94 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/introduction.md +47 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/neural-net-plugins/train-action-overview.md +815 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-action.md +948 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-overview.md +544 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-template.md +766 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/export-plugins.md +1092 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/plugins.md +117 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/quickstart.md +78 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/troubleshooting.md +519 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current.json +34 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-theme-classic/footer.json +42 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-theme-classic/navbar.json +18 -0
- synapse_sdk/devtools/docs/package-lock.json +18784 -0
- synapse_sdk/devtools/docs/package.json +48 -0
- synapse_sdk/devtools/docs/sidebars.ts +122 -0
- synapse_sdk/devtools/docs/src/components/HomepageFeatures/index.tsx +71 -0
- synapse_sdk/devtools/docs/src/components/HomepageFeatures/styles.module.css +11 -0
- synapse_sdk/devtools/docs/src/css/custom.css +30 -0
- synapse_sdk/devtools/docs/src/pages/index.module.css +23 -0
- synapse_sdk/devtools/docs/src/pages/index.tsx +21 -0
- synapse_sdk/devtools/docs/src/pages/markdown-page.md +7 -0
- synapse_sdk/devtools/docs/static/.nojekyll +0 -0
- synapse_sdk/devtools/docs/static/img/docusaurus-social-card.jpg +0 -0
- synapse_sdk/devtools/docs/static/img/docusaurus.png +0 -0
- synapse_sdk/devtools/docs/static/img/favicon.ico +0 -0
- synapse_sdk/devtools/docs/static/img/logo.png +0 -0
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_react.svg +170 -0
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- synapse_sdk/devtools/docs/tsconfig.json +8 -0
- synapse_sdk/devtools/server.py +41 -0
- synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
- synapse_sdk/devtools/streamlit_app/app.py +128 -0
- synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
- synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
- synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
- synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
- synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
- synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
- synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
- synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
- synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
- synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
- synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
- synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
- synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
- synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
- synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
- synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
- synapse_sdk/devtools/streamlit_app.py +10 -0
- synapse_sdk/loggers.py +65 -7
- synapse_sdk/plugins/README.md +1340 -0
- synapse_sdk/plugins/categories/base.py +73 -11
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
- synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
- synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
- synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
- synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
- synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
- synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
- synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
- synapse_sdk/plugins/categories/export/actions/{export.py → export/utils.py} +47 -82
- synapse_sdk/plugins/categories/export/templates/config.yaml +19 -1
- synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
- synapse_sdk/plugins/categories/export/templates/plugin/export.py +153 -129
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +9 -62
- synapse_sdk/plugins/categories/neural_net/actions/train.py +1062 -32
- synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +27 -5
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +145 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +97 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +250 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +287 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +87 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +127 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +966 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +232 -0
- synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
- synapse_sdk/plugins/categories/upload/actions/upload/enums.py +471 -0
- synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
- synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +203 -0
- synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
- synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
- synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +84 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +203 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +97 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +258 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +281 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
- synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
- synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
- synapse_sdk/plugins/categories/upload/templates/config.yaml +29 -2
- synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +294 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +88 -30
- synapse_sdk/plugins/models.py +122 -16
- synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
- synapse_sdk/plugins/templates/schema.json +491 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
- synapse_sdk/plugins/utils/__init__.py +46 -0
- synapse_sdk/plugins/utils/actions.py +119 -0
- synapse_sdk/plugins/utils/config.py +203 -0
- synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
- synapse_sdk/plugins/utils/ray_gcs.py +66 -0
- synapse_sdk/plugins/utils/registry.py +58 -0
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/shared/enums.py +93 -0
- synapse_sdk/utils/converters/__init__.py +240 -0
- synapse_sdk/utils/converters/coco/__init__.py +0 -0
- synapse_sdk/utils/converters/coco/from_dm.py +322 -0
- synapse_sdk/utils/converters/coco/to_dm.py +215 -0
- synapse_sdk/utils/converters/dm/__init__.py +56 -0
- synapse_sdk/utils/converters/dm/from_v1.py +627 -0
- synapse_sdk/utils/converters/dm/to_v1.py +367 -0
- synapse_sdk/utils/converters/pascal/__init__.py +0 -0
- synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
- synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
- synapse_sdk/utils/converters/yolo/__init__.py +0 -0
- synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
- synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
- synapse_sdk/utils/dataset.py +46 -0
- synapse_sdk/utils/encryption.py +158 -0
- synapse_sdk/utils/file/__init__.py +39 -0
- synapse_sdk/utils/file/archive.py +32 -0
- synapse_sdk/utils/file/checksum.py +56 -0
- synapse_sdk/utils/file/chunking.py +31 -0
- synapse_sdk/utils/file/download.py +385 -0
- synapse_sdk/utils/file/encoding.py +40 -0
- synapse_sdk/utils/file/io.py +22 -0
- synapse_sdk/utils/file/video/__init__.py +29 -0
- synapse_sdk/utils/file/video/transcode.py +307 -0
- synapse_sdk/utils/{file.py → file.py.backup} +84 -2
- synapse_sdk/utils/http.py +138 -0
- synapse_sdk/utils/network.py +293 -0
- synapse_sdk/utils/storage/__init__.py +36 -2
- synapse_sdk/utils/storage/providers/__init__.py +141 -0
- synapse_sdk/utils/storage/providers/file_system.py +134 -0
- synapse_sdk/utils/storage/providers/http.py +190 -0
- synapse_sdk/utils/storage/providers/s3.py +54 -6
- synapse_sdk/utils/storage/providers/sftp.py +31 -0
- synapse_sdk/utils/storage/registry.py +6 -0
- synapse_sdk-2025.11.7.dist-info/METADATA +122 -0
- synapse_sdk-2025.11.7.dist-info/RECORD +386 -0
- {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/WHEEL +1 -1
- synapse_sdk/clients/backend/dataset.py +0 -102
- synapse_sdk/plugins/categories/upload/actions/upload.py +0 -293
- synapse_sdk-1.0.0a35.dist-info/METADATA +0 -47
- synapse_sdk-1.0.0a35.dist-info/RECORD +0 -137
- {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/licenses/LICENSE +0 -0
- {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import shutil
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Callable, Optional
|
|
6
|
+
|
|
7
|
+
import ffmpeg
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Exception classes
|
|
11
|
+
class VideoTranscodeError(Exception):
|
|
12
|
+
"""Base exception for video transcoding errors."""
|
|
13
|
+
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UnsupportedFormatError(VideoTranscodeError):
|
|
18
|
+
"""Raised when input format is not supported."""
|
|
19
|
+
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FFmpegNotFoundError(VideoTranscodeError):
|
|
24
|
+
"""Raised when FFmpeg is not installed or not in PATH."""
|
|
25
|
+
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TranscodingFailedError(VideoTranscodeError):
|
|
30
|
+
"""Raised when FFmpeg transcoding process fails."""
|
|
31
|
+
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class TranscodeConfig:
|
|
37
|
+
"""Video transcoding configuration."""
|
|
38
|
+
|
|
39
|
+
vcodec: str = 'libx264' # Video codec
|
|
40
|
+
preset: str = 'medium' # Encoding preset (ultrafast to veryslow)
|
|
41
|
+
crf: int = 28 # Constant Rate Factor (0-51, lower=better quality)
|
|
42
|
+
acodec: str = 'aac' # Audio codec
|
|
43
|
+
audio_bitrate: str = '128k' # Audio bitrate
|
|
44
|
+
movflags: str = '+faststart' # MP4 optimization flags
|
|
45
|
+
resolution: Optional[str] = None # Target resolution (e.g., '1920x1080')
|
|
46
|
+
fps: Optional[int] = None # Target frame rate
|
|
47
|
+
start_time: Optional[float] = None # Trim start time in seconds
|
|
48
|
+
duration: Optional[float] = None # Trim duration in seconds
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Supported input formats
|
|
52
|
+
SUPPORTED_FORMATS = {'.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv', '.mpeg', '.mpg', '.m4v', '.3gp', '.ogv'}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _check_ffmpeg_available():
|
|
56
|
+
"""Check if FFmpeg is available in PATH."""
|
|
57
|
+
if not shutil.which('ffmpeg'):
|
|
58
|
+
raise FFmpegNotFoundError(
|
|
59
|
+
'FFmpeg is not installed or not found in PATH. Please install FFmpeg to use video transcoding features.'
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def validate_video_format(video_path: str | Path) -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Check if video format is supported for transcoding.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
video_path (str | Path): Path to the video file
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
bool: True if format is supported, False otherwise
|
|
72
|
+
"""
|
|
73
|
+
path = Path(video_path)
|
|
74
|
+
return path.suffix.lower() in SUPPORTED_FORMATS
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_video_info(video_path: str | Path) -> dict:
|
|
78
|
+
"""
|
|
79
|
+
Extract video metadata (resolution, duration, codecs, etc.).
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
video_path (str | Path): Path to the video file
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
dict: Video metadata information
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
VideoTranscodeError: If unable to probe video file
|
|
89
|
+
"""
|
|
90
|
+
_check_ffmpeg_available()
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
probe = ffmpeg.probe(str(video_path))
|
|
94
|
+
|
|
95
|
+
video_info = {}
|
|
96
|
+
|
|
97
|
+
# Get format information
|
|
98
|
+
if 'format' in probe:
|
|
99
|
+
format_info = probe['format']
|
|
100
|
+
video_info['duration'] = float(format_info.get('duration', 0))
|
|
101
|
+
video_info['size'] = int(format_info.get('size', 0))
|
|
102
|
+
video_info['bitrate'] = int(format_info.get('bit_rate', 0))
|
|
103
|
+
|
|
104
|
+
# Get stream information
|
|
105
|
+
video_streams = [stream for stream in probe['streams'] if stream['codec_type'] == 'video']
|
|
106
|
+
audio_streams = [stream for stream in probe['streams'] if stream['codec_type'] == 'audio']
|
|
107
|
+
|
|
108
|
+
if video_streams:
|
|
109
|
+
video_stream = video_streams[0]
|
|
110
|
+
video_info['width'] = int(video_stream.get('width', 0))
|
|
111
|
+
video_info['height'] = int(video_stream.get('height', 0))
|
|
112
|
+
video_info['video_codec'] = video_stream.get('codec_name', '')
|
|
113
|
+
video_info['fps'] = eval(video_stream.get('r_frame_rate', '0/1'))
|
|
114
|
+
|
|
115
|
+
if audio_streams:
|
|
116
|
+
audio_stream = audio_streams[0]
|
|
117
|
+
video_info['audio_codec'] = audio_stream.get('codec_name', '')
|
|
118
|
+
video_info['channels'] = int(audio_stream.get('channels', 0))
|
|
119
|
+
video_info['sample_rate'] = int(audio_stream.get('sample_rate', 0))
|
|
120
|
+
|
|
121
|
+
return video_info
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
raise VideoTranscodeError(f'Failed to probe video file: {str(e)}')
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _build_ffmpeg_stream(input_path: str | Path, output_path: str | Path, config: TranscodeConfig):
|
|
128
|
+
"""Build FFmpeg stream with configuration."""
|
|
129
|
+
stream = ffmpeg.input(str(input_path))
|
|
130
|
+
|
|
131
|
+
# Apply start time and duration trimming
|
|
132
|
+
if config.start_time is not None or config.duration is not None:
|
|
133
|
+
kwargs = {}
|
|
134
|
+
if config.start_time is not None:
|
|
135
|
+
kwargs['ss'] = config.start_time
|
|
136
|
+
if config.duration is not None:
|
|
137
|
+
kwargs['t'] = config.duration
|
|
138
|
+
stream = ffmpeg.input(str(input_path), **kwargs)
|
|
139
|
+
|
|
140
|
+
# Apply video filters
|
|
141
|
+
if config.resolution or config.fps:
|
|
142
|
+
if config.resolution:
|
|
143
|
+
width, height = config.resolution.split('x')
|
|
144
|
+
stream = ffmpeg.filter(stream, 'scale', width, height)
|
|
145
|
+
if config.fps:
|
|
146
|
+
stream = ffmpeg.filter(stream, 'fps', fps=config.fps)
|
|
147
|
+
|
|
148
|
+
# Build output with encoding parameters
|
|
149
|
+
output_kwargs = {
|
|
150
|
+
'vcodec': config.vcodec,
|
|
151
|
+
'preset': config.preset,
|
|
152
|
+
'crf': config.crf,
|
|
153
|
+
'acodec': config.acodec,
|
|
154
|
+
'audio_bitrate': config.audio_bitrate,
|
|
155
|
+
'movflags': config.movflags,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return ffmpeg.output(stream, str(output_path), **output_kwargs)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def transcode_video(
|
|
162
|
+
input_path: str | Path,
|
|
163
|
+
output_path: str | Path,
|
|
164
|
+
config: Optional[TranscodeConfig] = None,
|
|
165
|
+
progress_callback: Optional[Callable[[float], None]] = None,
|
|
166
|
+
) -> Path:
|
|
167
|
+
"""
|
|
168
|
+
Transcode video with specified configuration.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
input_path (str | Path): Path to input video file
|
|
172
|
+
output_path (str | Path): Path to output video file
|
|
173
|
+
config (Optional[TranscodeConfig]): Transcoding configuration
|
|
174
|
+
progress_callback (Optional[Callable[[float], None]]): Progress callback function
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Path: Path to the transcoded video file
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
UnsupportedFormatError: If input format is not supported
|
|
181
|
+
FFmpegNotFoundError: If FFmpeg is not available
|
|
182
|
+
TranscodingFailedError: If transcoding fails
|
|
183
|
+
"""
|
|
184
|
+
_check_ffmpeg_available()
|
|
185
|
+
|
|
186
|
+
input_path = Path(input_path)
|
|
187
|
+
output_path = Path(output_path)
|
|
188
|
+
|
|
189
|
+
if not validate_video_format(input_path):
|
|
190
|
+
raise UnsupportedFormatError(f'Unsupported video format: {input_path.suffix}')
|
|
191
|
+
|
|
192
|
+
if config is None:
|
|
193
|
+
config = TranscodeConfig()
|
|
194
|
+
|
|
195
|
+
# Ensure output directory exists
|
|
196
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
# Build FFmpeg command
|
|
200
|
+
stream = _build_ffmpeg_stream(input_path, output_path, config)
|
|
201
|
+
|
|
202
|
+
# Run FFmpeg
|
|
203
|
+
if progress_callback:
|
|
204
|
+
# Get video duration for progress calculation
|
|
205
|
+
video_info = get_video_info(input_path)
|
|
206
|
+
total_duration = video_info.get('duration', 0)
|
|
207
|
+
|
|
208
|
+
# Run with progress monitoring
|
|
209
|
+
process = ffmpeg.run_async(stream, pipe_stderr=True, overwrite_output=True)
|
|
210
|
+
|
|
211
|
+
while True:
|
|
212
|
+
output = process.stderr.readline()
|
|
213
|
+
if output == b'' and process.poll() is not None:
|
|
214
|
+
break
|
|
215
|
+
if output:
|
|
216
|
+
line = output.decode('utf-8')
|
|
217
|
+
# Parse progress from FFmpeg output
|
|
218
|
+
if 'time=' in line and total_duration > 0:
|
|
219
|
+
try:
|
|
220
|
+
time_str = line.split('time=')[1].split()[0]
|
|
221
|
+
hours, minutes, seconds = time_str.split(':')
|
|
222
|
+
current_time = int(hours) * 3600 + int(minutes) * 60 + float(seconds)
|
|
223
|
+
progress = min(current_time / total_duration, 1.0)
|
|
224
|
+
progress_callback(progress)
|
|
225
|
+
except (ValueError, IndexError):
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
if process.returncode != 0:
|
|
229
|
+
raise TranscodingFailedError('FFmpeg process failed')
|
|
230
|
+
else:
|
|
231
|
+
# Run without progress monitoring
|
|
232
|
+
ffmpeg.run(stream, overwrite_output=True, quiet=True)
|
|
233
|
+
|
|
234
|
+
return output_path
|
|
235
|
+
|
|
236
|
+
except ffmpeg.Error as e:
|
|
237
|
+
error_message = e.stderr.decode('utf-8') if e.stderr else str(e)
|
|
238
|
+
raise TranscodingFailedError(f'Transcoding failed: {error_message}')
|
|
239
|
+
except Exception as e:
|
|
240
|
+
raise VideoTranscodeError(f'Unexpected error during transcoding: {str(e)}')
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def optimize_for_web(video_path: str | Path, output_path: str | Path) -> Path:
|
|
244
|
+
"""
|
|
245
|
+
Quick optimization for web streaming with default settings.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
video_path (str | Path): Path to input video file
|
|
249
|
+
output_path (str | Path): Path to output video file
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Path: Path to the optimized video file
|
|
253
|
+
"""
|
|
254
|
+
config = TranscodeConfig(
|
|
255
|
+
preset='fast', # Faster encoding for web optimization
|
|
256
|
+
crf=23, # Better quality for web
|
|
257
|
+
movflags='+faststart+frag_keyframe+empty_moov', # Advanced web optimization
|
|
258
|
+
)
|
|
259
|
+
return transcode_video(video_path, output_path, config)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
async def atranscode_video(
|
|
263
|
+
input_path: str | Path, output_path: str | Path, config: Optional[TranscodeConfig] = None
|
|
264
|
+
) -> Path:
|
|
265
|
+
"""
|
|
266
|
+
Async version of transcode_video.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
input_path (str | Path): Path to input video file
|
|
270
|
+
output_path (str | Path): Path to output video file
|
|
271
|
+
config (Optional[TranscodeConfig]): Transcoding configuration
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Path: Path to the transcoded video file
|
|
275
|
+
"""
|
|
276
|
+
loop = asyncio.get_event_loop()
|
|
277
|
+
return await loop.run_in_executor(None, transcode_video, input_path, output_path, config)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def transcode_batch(
|
|
281
|
+
video_paths: list[Path], output_dir: Path, config: Optional[TranscodeConfig] = None, max_workers: int = 4
|
|
282
|
+
) -> list[Path]:
|
|
283
|
+
"""
|
|
284
|
+
Process multiple videos concurrently.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
video_paths (list[Path]): List of input video file paths
|
|
288
|
+
output_dir (Path): Directory for output files
|
|
289
|
+
config (Optional[TranscodeConfig]): Transcoding configuration
|
|
290
|
+
max_workers (int): Maximum number of concurrent workers
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
list[Path]: List of paths to transcoded video files
|
|
294
|
+
"""
|
|
295
|
+
import concurrent.futures
|
|
296
|
+
|
|
297
|
+
output_dir = Path(output_dir)
|
|
298
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
299
|
+
|
|
300
|
+
def process_video(video_path):
|
|
301
|
+
output_path = output_dir / f'{video_path.stem}_transcoded.mp4'
|
|
302
|
+
return transcode_video(video_path, output_path, config)
|
|
303
|
+
|
|
304
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
305
|
+
results = list(executor.map(process_video, video_paths))
|
|
306
|
+
|
|
307
|
+
return results
|
|
@@ -7,6 +7,7 @@ import operator
|
|
|
7
7
|
import zipfile
|
|
8
8
|
from functools import reduce
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
from typing import IO, Any, Callable
|
|
10
11
|
|
|
11
12
|
import aiohttp
|
|
12
13
|
import requests
|
|
@@ -16,6 +17,39 @@ from synapse_sdk.utils.network import clean_url
|
|
|
16
17
|
from synapse_sdk.utils.string import hash_text
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def read_file_in_chunks(file_path, chunk_size=1024 * 1024 * 50):
|
|
21
|
+
"""
|
|
22
|
+
Read a file in chunks for efficient memory usage during file processing.
|
|
23
|
+
|
|
24
|
+
This function is particularly useful for large files or when you need to process
|
|
25
|
+
files in chunks, such as for uploading or hashing.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
file_path (str | Path): Path to the file to read
|
|
29
|
+
chunk_size (int, optional): Size of each chunk in bytes. Defaults to 50MB (1024 * 1024 * 50)
|
|
30
|
+
|
|
31
|
+
Yields:
|
|
32
|
+
bytes: File content chunks
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
FileNotFoundError: If the file doesn't exist
|
|
36
|
+
PermissionError: If the file can't be read due to permissions
|
|
37
|
+
OSError: If there's an OS-level error reading the file
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
```python
|
|
41
|
+
from synapse_sdk.utils.file import read_file_in_chunks
|
|
42
|
+
|
|
43
|
+
# Read a file in 10MB chunks
|
|
44
|
+
for chunk in read_file_in_chunks('large_file.bin', chunk_size=1024*1024*10):
|
|
45
|
+
process_chunk(chunk)
|
|
46
|
+
```
|
|
47
|
+
"""
|
|
48
|
+
with open(file_path, 'rb') as file:
|
|
49
|
+
while chunk := file.read(chunk_size):
|
|
50
|
+
yield chunk
|
|
51
|
+
|
|
52
|
+
|
|
19
53
|
def download_file(url, path_download, name=None, coerce=None, use_cached=True):
|
|
20
54
|
chunk_size = 1024 * 1024 * 50
|
|
21
55
|
cleaned_url = clean_url(url) # remove query params and fragment
|
|
@@ -150,11 +184,55 @@ def calculate_checksum(file_path, prefix=''):
|
|
|
150
184
|
return checksum
|
|
151
185
|
|
|
152
186
|
|
|
153
|
-
def
|
|
187
|
+
def get_checksum_from_file(file: IO[Any], digest_mod: Callable[[], Any] = hashlib.sha1) -> str:
|
|
188
|
+
"""
|
|
189
|
+
Calculate checksum for a file-like object.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
file (IO[Any]): File-like object with read() method that supports reading in chunks
|
|
193
|
+
digest_mod (Callable[[], Any]): Hash algorithm from hashlib (defaults to hashlib.sha1)
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
str: Hexadecimal digest of the file contents
|
|
197
|
+
|
|
198
|
+
Example:
|
|
199
|
+
```python
|
|
200
|
+
import hashlib
|
|
201
|
+
from io import BytesIO
|
|
202
|
+
from synapse_sdk.utils.file import get_checksum_from_file
|
|
203
|
+
|
|
204
|
+
# With BytesIO
|
|
205
|
+
data = BytesIO(b'Hello, world!')
|
|
206
|
+
checksum = get_checksum_from_file(data)
|
|
207
|
+
|
|
208
|
+
# With different hash algorithm
|
|
209
|
+
checksum = get_checksum_from_file(data, digest_mod=hashlib.sha256)
|
|
210
|
+
```
|
|
211
|
+
"""
|
|
212
|
+
digest = digest_mod()
|
|
213
|
+
chunk_size = 4096
|
|
214
|
+
|
|
215
|
+
# Reset file pointer to beginning if possible
|
|
216
|
+
if hasattr(file, 'seek'):
|
|
217
|
+
file.seek(0)
|
|
218
|
+
|
|
219
|
+
while True:
|
|
220
|
+
chunk = file.read(chunk_size)
|
|
221
|
+
if not chunk:
|
|
222
|
+
break
|
|
223
|
+
if isinstance(chunk, str):
|
|
224
|
+
chunk = chunk.encode('utf-8')
|
|
225
|
+
digest.update(chunk)
|
|
226
|
+
|
|
227
|
+
return digest.hexdigest()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def archive(input_path, output_path, append=False):
|
|
154
231
|
input_path = Path(input_path)
|
|
155
232
|
output_path = Path(output_path)
|
|
156
233
|
|
|
157
|
-
|
|
234
|
+
mode = 'a' if append and output_path.exists() else 'w'
|
|
235
|
+
with zipfile.ZipFile(output_path, mode=mode, compression=zipfile.ZIP_DEFLATED) as zipf:
|
|
158
236
|
if input_path.is_file():
|
|
159
237
|
zipf.write(input_path, input_path.name)
|
|
160
238
|
else:
|
|
@@ -196,6 +274,10 @@ def convert_file_to_base64(file_path):
|
|
|
196
274
|
Returns:
|
|
197
275
|
str: Base64 encoded string of the file contents
|
|
198
276
|
"""
|
|
277
|
+
# FIXME base64 is sent sometimes.
|
|
278
|
+
if file_path.startswith('data:'):
|
|
279
|
+
return file_path
|
|
280
|
+
|
|
199
281
|
# Convert string path to Path object
|
|
200
282
|
path = Path(file_path)
|
|
201
283
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from synapse_sdk.utils.network import get_available_ports_host
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SingleFileHttpServer(SimpleHTTPRequestHandler):
|
|
16
|
+
"""
|
|
17
|
+
Custom HTTP request handler that serves a single specified file
|
|
18
|
+
regardless of the request path.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, *args, file_path=None, content_type=None, random_path=None, **kwargs):
|
|
22
|
+
self.file_path = file_path
|
|
23
|
+
self.content_type = content_type
|
|
24
|
+
self.random_path = random_path
|
|
25
|
+
super().__init__(*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
def do_GET(self):
|
|
28
|
+
"""Handle GET requests by serving the single file."""
|
|
29
|
+
try:
|
|
30
|
+
# Check if the path matches our random path
|
|
31
|
+
if self.random_path and self.path == f'/{self.random_path}':
|
|
32
|
+
self.send_response(200)
|
|
33
|
+
if self.content_type:
|
|
34
|
+
self.send_header('Content-type', self.content_type)
|
|
35
|
+
self.send_header('Content-Length', str(os.path.getsize(self.file_path)))
|
|
36
|
+
self.end_headers()
|
|
37
|
+
|
|
38
|
+
with open(self.file_path, 'rb') as file:
|
|
39
|
+
self.wfile.write(file.read())
|
|
40
|
+
elif self.path == '/':
|
|
41
|
+
# Redirect root to the random path
|
|
42
|
+
self.send_response(302)
|
|
43
|
+
self.send_header('Location', f'/{self.random_path}')
|
|
44
|
+
self.end_headers()
|
|
45
|
+
else:
|
|
46
|
+
self.send_error(404, 'File not found')
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
self.send_error(500, str(e))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@contextmanager
|
|
53
|
+
def temp_file_server(image=None, file_path=None, format='JPEG', host='0.0.0.0', port=None, content_type=None):
|
|
54
|
+
"""
|
|
55
|
+
Context manager that serves a file temporarily via HTTP.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
image: A PIL Image object to serve (optional)
|
|
59
|
+
file_path: Path to an existing file to serve (optional - used if image not provided)
|
|
60
|
+
format: Image format when saving a PIL Image (default: "JPEG")
|
|
61
|
+
host: Host to serve on (default: "0.0.0.0")
|
|
62
|
+
port: Port to serve on (default: auto-selected free port)
|
|
63
|
+
content_type: Content type header (default: auto-detected based on format)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
URL where the file is being served
|
|
67
|
+
|
|
68
|
+
Usage:
|
|
69
|
+
with temp_file_serve(image=my_pillow_img) as url:
|
|
70
|
+
# use url to access the image
|
|
71
|
+
print(f"Image available at: {url}")
|
|
72
|
+
"""
|
|
73
|
+
if image is None and file_path is None:
|
|
74
|
+
raise ValueError('Either image or file_path must be provided')
|
|
75
|
+
|
|
76
|
+
# Use a free port if none specified
|
|
77
|
+
if port is None:
|
|
78
|
+
port = get_available_ports_host(start_port=8991, end_port=8999)
|
|
79
|
+
|
|
80
|
+
temp_dir = None
|
|
81
|
+
temp_file_path = None
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
random_filename = f'{uuid.uuid4().hex}'
|
|
85
|
+
|
|
86
|
+
if image is not None:
|
|
87
|
+
temp_dir = tempfile.mkdtemp()
|
|
88
|
+
ext_map = {'JPEG': '.jpg', 'PNG': '.png', 'GIF': '.gif', 'WEBP': '.webp'}
|
|
89
|
+
content_type_map = {'JPEG': 'image/jpeg', 'PNG': 'image/png', 'GIF': 'image/gif', 'WEBP': 'image/webp'}
|
|
90
|
+
|
|
91
|
+
ext = ext_map.get(format, '.jpg')
|
|
92
|
+
if content_type is None:
|
|
93
|
+
content_type = content_type_map.get(format, 'image/jpeg')
|
|
94
|
+
|
|
95
|
+
temp_file_path = os.path.join(temp_dir, f'temp_image{ext}')
|
|
96
|
+
image.save(temp_file_path, format=format)
|
|
97
|
+
file_path = temp_file_path
|
|
98
|
+
random_filename += ext
|
|
99
|
+
else:
|
|
100
|
+
_, ext = os.path.splitext(file_path)
|
|
101
|
+
random_filename += ext
|
|
102
|
+
|
|
103
|
+
def handler(*args, **kwargs):
|
|
104
|
+
return SingleFileHttpServer(
|
|
105
|
+
*args, file_path=file_path, content_type=content_type, random_path=random_filename, **kwargs
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
server = HTTPServer((host, port), handler)
|
|
109
|
+
|
|
110
|
+
server_thread = threading.Thread(target=server.serve_forever)
|
|
111
|
+
server_thread.daemon = True
|
|
112
|
+
server_thread.start()
|
|
113
|
+
|
|
114
|
+
url = f'http://localhost:{port}/{random_filename}'
|
|
115
|
+
|
|
116
|
+
while True:
|
|
117
|
+
try:
|
|
118
|
+
response = requests.get(url)
|
|
119
|
+
if response.status_code == 200:
|
|
120
|
+
break
|
|
121
|
+
except requests.exceptions.ConnectionError:
|
|
122
|
+
pass
|
|
123
|
+
time.sleep(0.01)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
yield url
|
|
127
|
+
finally:
|
|
128
|
+
server.shutdown()
|
|
129
|
+
server.server_close()
|
|
130
|
+
|
|
131
|
+
finally:
|
|
132
|
+
if temp_dir:
|
|
133
|
+
try:
|
|
134
|
+
if temp_file_path and os.path.exists(temp_file_path):
|
|
135
|
+
os.unlink(temp_file_path)
|
|
136
|
+
os.rmdir(temp_dir)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
logging.warning(f'Error cleaning up temporary files: {e}')
|