diff --git a/README.md b/README.md index c3e63ab..0130072 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,149 @@

- FairScan icon + FSBC icon

-

FairScan

+

FAIRSCAN_SURPORT_BY_CYY

+

FSBC — 扫描 + 图传 + 文档处理一体化

- An Android app to scan your documents — -
simple and respectful. + 基于 FairScan 增强,
+ 新增局域网实时图传、PDF 上传、MinerU / OCRmyPDF 云端处理、手机端任务管理面板。

-

- - License -

-

- Get it on: - F-Droid · - Google Play · - GitHub -

+--- + +## 新增功能(CYY 增强版) + +### 实时网络图传 +- 手机通过 WebSocket 将 JPEG 帧发送到 PC +- PC 浏览器实时显示画面(`http://:2026`) +- 支持帧率控制(无限制 / 15fps / 10fps / 5fps) +- 三档压缩质量可选(低 / 均衡 / 高) + +### PDF 上传 + 云端处理 +- 导出页一键上传 PDF 到 PC 服务器 +- 三种模式:仅传输 / 上传并处理 OCR PDF / 上传并处理 Markdown +- 上传进度实时显示 + +### MinerU → Markdown 数字化 +- 真实接入 MinerU pipeline 后端(v3.0.9) +- 将扫描 PDF 转为结构化 Markdown + 图片 +- 自动打包 `{name}_result.zip`(.md + images/) +- GPU 加速:RTX 4060 8GB,约 3~8 秒/页 + +### OCRmyPDF → 可搜索双层 PDF +- 接入 OCRmyPDF v15.4.4,生成真正可搜索的双层 PDF +- Tesseract 5.5.2 OCR 引擎,支持中文/英语/日语/韩语 +- Ctrl+F 搜索、屏幕阅读器无障碍 + +### 手机端任务管理面板 +- 导出页底部显示所有上传处理任务 +- 四种状态:排队中 / 处理中(进度条) / 已完成 / 失败 +- 2 秒轮询自动更新状态,完成后可下载到指定目录 +- SAF 目录选择器,下载进度实时显示 + +### PC 管理面板 +- 浏览器访问 `http://:2026/dashboard` +- 文件列表 + 任务列表 + 统计卡片 +- 支持下载原始文件和处理产物 +- 自动刷新 --- -FairScan is an Android app to **scan documents quickly, easily and privately**. +## 原版功能(FairScan) -It's designed to be **simple**: users get a clean, shareable PDF in seconds, with no manual adjustments.
-And **respectful**: open source, minimal permissions, no tracking, no ads. - -- Website: https://fairscan.org -- Blog: https://fairscan.org/blog/ +- **清晰无干扰的界面**,一键扫描 +- **自动文档检测**:基于自定义分割模型 (LiteRT) +- **自动透视校正** + **图像增强** (OpenCV) +- **快速 PDF 生成**,无需手动调整 +- **完全离线** — 原版无网络权限 +- **最小权限**,无跟踪,无广告 +- **开源** GPLv3 --- -## Contributing +## PC 服务器部署 -Contributions are welcome, but please read the guidelines first: [CONTRIBUTING.md](CONTRIBUTING.md) +### 环境要求 ---- +| 组件 | 说明 | +|------|------| +| Conda 环境 | `MinerU`(Python 3.10, PyTorch 2.6, CUDA 12.4) | +| MinerU | v3.0.9(markdown 处理) | +| OCRmyPDF | v15.4.4(ocrpdf 处理) | +| Tesseract | 5.5.2 + chi_sim / eng 语言包 | +| Ghostscript | 10.07.1 | +| GPU | NVIDIA RTX 4060 8GB(推荐) | -## Screenshots - -| Scan | Preview | Save & Share | -|------|---------|--------------| -| ![](metadata/en-US/images/phoneScreenshots/1.jpg) | ![](metadata/en-US/images/phoneScreenshots/2.jpg) | ![](metadata/en-US/images/phoneScreenshots/3.jpg) | - ---- - -## Features - -- **Clear, distraction-free interface** -- **Easy flow**: scan, review if needed, save or share -- **Automatic document detection** using a custom segmentation model -- **Automatic perspective correction** -- **Automatic image enhancement** -- **Fast PDF generation** with no manual adjustments -- **Fully offline** – the app has *no* internet permission -- **Minimal permissions** -- **Open source**, GPLv3 - ---- - -## What FairScan is not - -FairScan is **not** intended to: -- provide fine-grained manual control over document processing -- replicate all features found in other scanning apps -- optimize for highly specific use cases at the expense of simplicity - ---- - -## Compatibility - -FairScan works on any device that: -- runs **Android 8.0+** -- has a camera - ---- - -## Experimental: Scan to PDF via intent - -FairScan can be invoked by other Android applications to perform a document scan and return a generated PDF. - -This feature is **experimental** and intended for developers who want to rely on FairScan as a -simple, privacy-respecting scanning tool. -The intent contract and behavior may change between versions, and backward compatibility -is not guaranteed at this stage. - -Intent action: `org.fairscan.app.action.SCAN_TO_PDF` - -This is an **implicit intent** that launches FairScan in a dedicated external mode. - -When started via this intent: - -- FairScan opens directly in scan mode -- the user scans one or more pages -- FairScan generates a single PDF -- the resulting PDF is returned to the calling application as a URI with a limited lifetime -- the calling application should immediately copy the content of the URI as FairScan deletes it later - -See an example app: [fairscan-intent-sample](https://github.com/pynicolas/fairscan-intent-sample) - ---- - -## Technical details - -FairScan uses: - -- [Jetpack Compose](https://developer.android.com/compose) for the UI -- [CameraX](https://developer.android.com/media/camera/camerax) for image capture -- [LiteRT](https://ai.google.dev/edge/litert) to run the custom segmentation model for automatic document detection -- [OpenCV](https://opencv.org/) for perspective correction and image enhancement -- [PDFBox-Android](https://github.com/TomRoush/PdfBox-Android) for PDF generation - ---- - -## The segmentation model - -FairScan uses a custom-trained image segmentation model to detect documents:
-https://github.com/pynicolas/fairscan-segmentation-model - -It's based on a fully public dataset that is available here:
-https://github.com/pynicolas/fairscan-dataset - -The build system automatically downloads the model using -[`download-tflite.gradle.kts`](app/download-tflite.gradle.kts). - -Related blog posts: -- [*Making document detection more reliable*](https://fairscan.org/blog/automatic-document-detection/) -- [*Building a public dataset for FairScan*](https://fairscan.org/blog/building_a_public_dataset/) - ---- - -## Build - -To build an APK: +### 启动 ```bash -./gradlew clean check assembleRelease +conda activate MinerU +cd pc-server +python main.py ``` -To build an Android App Bundle: + +服务启动在 `http://0.0.0.0:2026`。 + +### API 端点 + +| 端点 | 方法 | 功能 | +|------|------|------| +| `/health` | GET | 健康检查 | +| `/stream` | WS | 接收图传帧 | +| `/` | GET | 图传预览页面 | +| `/dashboard` | GET | 管理面板 | +| `/api/dashboard` | GET | 管理面板 JSON | +| `/upload/pdf` | POST | 上传 PDF | +| `/tasks/process` | POST | 创建处理任务 | +| `/tasks/{taskId}` | GET | 查询任务状态 | +| `/tasks/{taskId}/artifacts` | GET | 查询产物列表 | +| `/artifacts/{id}/download` | GET | 下载产物 | +| `/files/{fileId}/download` | GET | 下载原始文件 | + +--- + +## 手机端配置 + +1. 进入设置 → 网络协作 +2. 填写 PC 主机 IP 和端口(默认 2026) +3. 点击"测试连接"验证 +4. 扫描文档后在导出页选择上传/处理 + +--- + +## 技术栈 + +| 层 | 技术 | +|----|------| +| Android UI | Jetpack Compose | +| 相机 | CameraX | +| 文档检测 | LiteRT + 自定义分割模型 | +| 图像处理 | OpenCV | +| PDF 生成 | PDFBox-Android | +| 网络 | OkHttp 4.12 + WebSocket | +| PC 服务 | Python FastAPI + Uvicorn | +| Markdown 处理 | MinerU (pipeline backend) | +| OCR PDF | OCRmyPDF + Tesseract + Ghostscript | + +--- + +## 构建 + ```bash +# Android APK +./gradlew clean check assembleRelease + +# Android App Bundle ./gradlew clean check :app:bundleRelease ``` -## License -This project is licensed under the GNU GPLv3. See [LICENSE](LICENSE) for details. +## 许可 + +本项目基于 FairScan (GPLv3) 修改。详见 [LICENSE](LICENSE)。 + +## 致谢 + +- 原项目 [FairScan](https://github.com/pynicolas/FairScan) +- [MinerU](https://github.com/opendatalab/MinerU) +- [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF) diff --git a/app/src/main/java/org/fairscan/app/MainActivity.kt b/app/src/main/java/org/fairscan/app/MainActivity.kt index 087e293..6a42e41 100644 --- a/app/src/main/java/org/fairscan/app/MainActivity.kt +++ b/app/src/main/java/org/fairscan/app/MainActivity.kt @@ -328,7 +328,7 @@ class MainActivity : ComponentActivity() { when (event) { is AboutEvent.CopyLogs -> { clipboard.setClipEntry( - ClipData.newPlainText("FairScan logs", event.logs).toClipEntry() + ClipData.newPlainText("FSBC logs", event.logs).toClipEntry() ) showToast(msgCopiedLogs) } diff --git a/app/src/main/java/org/fairscan/app/platform/AndroidPdfWriter.kt b/app/src/main/java/org/fairscan/app/platform/AndroidPdfWriter.kt index 6cd2769..6c4af89 100644 --- a/app/src/main/java/org/fairscan/app/platform/AndroidPdfWriter.kt +++ b/app/src/main/java/org/fairscan/app/platform/AndroidPdfWriter.kt @@ -32,7 +32,7 @@ class AndroidPdfWriter : PdfWriter { override suspend fun writePdfFromJpegs(pages: List, outputStream: OutputStream): Int { val doc = PDDocument() doc.documentInformation.creationDate = Calendar.getInstance() - doc.documentInformation.creator = "FairScan ${BuildConfig.VERSION_NAME}" + doc.documentInformation.creator = "FSBC ${BuildConfig.VERSION_NAME}" doc.use { document -> for (page in pages) { val image = JPEGFactory.createFromByteArray(document, page.jpeg.get().bytes) diff --git a/app/src/main/java/org/fairscan/app/ui/screens/about/AboutViewModel.kt b/app/src/main/java/org/fairscan/app/ui/screens/about/AboutViewModel.kt index 50ed998..bab34df 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/about/AboutViewModel.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/about/AboutViewModel.kt @@ -52,7 +52,7 @@ class AboutViewModel(container: AppContainer, val imageRepository: ImageReposito private fun buildFullLogs(): String { val header = buildString { - appendLine("FairScan diagnostics report") + appendLine("FSBC diagnostics report") appendLine("App version: ${BuildConfig.VERSION_NAME}") appendLine("Android version: ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})") appendLine("Generated: ${LocalDateTime.now()}") diff --git a/app/src/main/java/org/fairscan/app/ui/screens/about/Emails.kt b/app/src/main/java/org/fairscan/app/ui/screens/about/Emails.kt index e4625da..9805455 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/about/Emails.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/about/Emails.kt @@ -34,7 +34,7 @@ fun createEmailWithImageIntent(context: Context, imageFile: File?): Intent { putExtra(Intent.EXTRA_EMAIL, arrayOf(EMAIL_ADDRESS)) putExtra( Intent.EXTRA_SUBJECT, - "FairScan ${BuildConfig.VERSION_NAME}" + "FSBC ${BuildConfig.VERSION_NAME}" ) if (imageFile != null) { val uri = uriForFile(context, imageFile) diff --git a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt index 3f4b4cf..7dc9d75 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt @@ -232,7 +232,7 @@ fun CameraScreen( isTorchEnabled), onCapture = { previewView?.bitmap?.let { - Log.i("FairScan", "Pressed ") + Log.i("FSBC", "Pressed ") cameraViewModel.onCapturePressed(it) captureController.takePicture( onImageCaptured = { imageProxy, opticalMeasures -> diff --git a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt index 38023ce..6d993df 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt @@ -184,7 +184,7 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit throw e } catch (e: Exception) { val message = "Failed to prepare $exportFormat export" - logger.e("FairScan", message, e) + logger.e("FSBC", message, e) _uiState.update { it.copy(error = ExportError.OnPrepareOrShare(message, e)) } @@ -256,7 +256,7 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit _uiState.update { it.copy(hasShared = true) } } catch (e: Exception) { val message = "Failed to prepare share" - logger.e("FairScan", message, e) + logger.e("FSBC", message, e) _uiState.update { it.copy(error = ExportError.OnPrepareOrShare(message, e)) } } } @@ -280,13 +280,13 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit save(context, saveDir, exportFormat) } } catch (e: MissingExportDirPermissionException) { - logger.e("FairScan", "Missing export dir permission", e) + logger.e("FSBC", "Missing export dir permission", e) _uiState.update { it.copy(error = ExportError.OnSave(R.string.error_export_dir_permission_lost, saveDir)) } } catch (e: Exception) { - logger.e("FairScan", "Failed to save PDF", e) + logger.e("FSBC", "Failed to save PDF", e) _uiState.update { it.copy(error = ExportError.OnSave(R.string.error_save, saveDir, e)) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1e0793a..822aa7b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ About Add page - FairScan + FSBC A simple and respectful application to scan your documents. Apply Back diff --git a/pc-server/main.py b/pc-server/main.py index 60ae0e2..89ae839 100644 --- a/pc-server/main.py +++ b/pc-server/main.py @@ -1,5 +1,5 @@ """ -FairScan PC Server — Streaming, PDF upload & real MinerU task processing. +FAIRSCAN_SURPORT_BY_CYY (FSBC) PC Server Endpoints: Streaming: @@ -41,7 +41,7 @@ from mineru.cli.common import aio_do_parse, read_fn import ocrmypdf from loguru import logger -app = FastAPI(title="FairScan PC Server") +app = FastAPI(title="FSBC PC Server") # ── Configuration ───────────────────────────────────────────────────────────── @@ -66,7 +66,7 @@ STREAM_PAGE = """\ - FairScan Stream + FSBC Stream