instrumentaipdfsplitter


Nameinstrumentaipdfsplitter JSON
Version 0.2.0 PyPI version JSON
download
home_pageNone
SummaryAI-assisted instrument part detector and PDF splitter
upload_time2025-10-06 18:46:01
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseProprietary - No modification allowed
keywords instrument pdf split splitting splitting pdf music openai
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Instrument AI PDF Splitter

A lightweight Python tool that uses OpenAI to analyze multi-page sheet-music PDFs, detect instrument parts (including voice/desk numbers), determine their page ranges, and split the source PDF into one file per instrument/voice.

- AI-assisted part detection: extracts instruments, voice numbers, and 1-indexed start/end pages as strict JSON.
- Smart uploads: avoids re-uploading identical files via SHA-256 hashing.
- Reliable splitting: clamps page ranges, sanitizes filenames, and writes outputs using pypdf.
- Flexible input: use AI analysis or provide your own instrument list (InstrumentPart or JSON).
- Configurable model: via constructor or OPENAI_MODEL env var; requires an OpenAI API key.

## Installation

Replace PACKAGE_NAME with your actual PyPI name once published (e.g., instrument-ai-pdf-splitter).

```bash
pip install PACKAGE_NAME
```

Requirements:
- Python 3.10+
- openai (>= 1.0.0)
- pypdf
- dataclasses (builtin)
- typing, pathlib, etc. (builtin)

## Quickstart

```python
import os
import json
from instrument_ai_pdf_splitter import InstrumentAiPdfSplitter

# Set your OpenAI API key via env or pass directly
api_key = os.getenv("OPENAI_API_KEY")

splitter = InstrumentAiPdfSplitter(api_key=api_key)

# 1) Analyze the PDF to get instrument parts and page ranges
data = splitter.analyse("scores/book.pdf")
print(json.dumps(data, indent=2))

# Example output (JSON):
# {
#   "instruments": [
#     {"name": "Trumpet in Bb", "voice": "1", "start_page": 3, "end_page": 5},
#     {"name": "Alto Sax", "voice": null, "start_page": 6, "end_page": 9}
#   ]
# }

# 2) Split the PDF into one file per instrument/voice
results = splitter.split_pdf("scores/book.pdf")
for r in results:
    print(f"{r['name']} {r['voice']} -> {r['output_path']} [{r['start_page']}-{r['end_page']}]")
```

Output files are saved into a sibling directory named "<stem>_parts" by default (e.g., book_parts).

## Manual instrument data (no AI call)

You can skip analysis and provide parts manually, either as InstrumentPart instances or JSON-like dicts.

```python
from instrument_ai_pdf_splitter import InstrumentAiPdfSplitter, InstrumentPart

splitter = InstrumentAiPdfSplitter(api_key="YOUR_OPENAI_API_KEY")

parts = [
    InstrumentPart(name="Trumpet in Bb", voice="1", start_page=3, end_page=5),
    {"name": "Alto Sax", "voice": None, "start_page": 6, "end_page": 9},  # JSON-like dict also works
]

results = splitter.split_pdf(
    pdf_path="scores/book.pdf",
    instruments_data=parts,
    out_dir="output/parts"  # optional custom directory
)

for r in results:
    print(r)
```

## Configuration

- API key: Provide via constructor or set OPENAI_API_KEY in your environment.
- Model: Pass `model` to the constructor or set `OPENAI_MODEL`; defaults to "gpt-4.1".

```python
splitter = InstrumentAiPdfSplitter(api_key="...", model="gpt-4.1")
```

Note: Model availability depends on your OpenAI account. Use a model that supports the Responses API with file inputs. You will get the best results with gpt-5.

## How it works

- Content-hash uploads: Files are uploaded once per SHA-256; duplicates are skipped.
- AI analysis: The PDF and a strict prompt are sent to OpenAI; output is parsed as JSON.
- Splitting:
  - Ensures pages are 1-indexed and within document bounds.
  - Swaps start/end if reversed.
  - Sanitizes output filenames (removes unsafe characters).
  - Writes per-part PDFs using pypdf.

## Public API

| Item | Signature | Description |
|------|-----------|-------------|
| InstrumentPart | name: str; voice: Optional[str]; start_page: int; end_page: int | Dataclass representing a single instrument part with optional voice and 1-indexed inclusive page range. |
| InstrumentAiPdfSplitter.__init__ | (api_key: str, *, model: str | None = None) -> None | Initialize the splitter with OpenAI credentials and default prompt. |
| InstrumentAiPdfSplitter.analyse | (pdf_path: str) -> dict | Analyze a PDF and return instrument data as JSON {instruments: [...]}. |
| InstrumentAiPdfSplitter.is_file_already_uploaded | (pdf_path: str) -> Tuple[bool, str] | Check if a file (by SHA-256) is already uploaded; returns (True, file_id) or (False,). |
| InstrumentAiPdfSplitter.split_pdf | (pdf_path: str, instruments_data: List[InstrumentPart] | Dict[str, Any] | None = None, out_dir: Optional[str] = None) -> List[Dict[str, Any]] | Split the PDF per instrument/voice. Returns metadata with output_path. |
| InstrumentAiPdfSplitter.file_hash | (path: str) -> str | Compute SHA-256 hex digest of a file’s contents. |

## Error handling

- FileNotFoundError: Path doesn’t exist.
- ValueError: Not a file or not a .pdf.
- json.JSONDecodeError: If AI output isn’t valid JSON (rare; retry or adjust model).
- OpenAI errors: Network/auth/model issues are propagated from the OpenAI SDK.

## Tips for best results

- Use clear, well-structured PDFs with visible instrument headers or page titles.
- If AI is uncertain, manually provide `instruments_data` for precise splitting.
- Verify the model supports file inputs in your region/account.
- Handle sensitive material carefully; PDFs are uploaded to OpenAI for analysis.

## Example project structure

```text
scores/
├── book.pdf
output/
└── parts/
    ├── 01 - Trumpet in Bb 1.pdf
    ├── 02 - Alto Sax.pdf
    └── ...
```

## Development

```bash
# Clone and install locally
git clone REPO_URL
cd REPO_DIR
pip install -e .

# Run a quick test (adjust paths)
python -c "from instrument_ai_pdf_splitter import InstrumentAiPdfSplitter; import os; s=InstrumentAiPdfSplitter(api_key=os.getenv('OPENAI_API_KEY')); print(s.file_hash('scores/book.pdf'))"
```

## Versioning and compatibility

- Tested with Python 3.10+.
- Requires openai>=1.0.0 and pypdf. Keep dependencies updated.

## FAQ

- Does it require internet?
  - Yes, for AI analysis. Splitting runs locally.
- Can I prevent re-uploads?
  - Yes. The tool checks a SHA-256 content hash against your uploaded files.
- Is the output deterministic?
  - The JSON structure is deterministic; the content depends on model interpretation.

## License

Copyright (c) 2025 Flinn

Permission is hereby granted to use, copy, and distribute this software in unmodified form,
provided that proper attribution is given to the author. Modification, merging, or creation
of derivative works based on this software is strictly prohibited.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

## Acknowledgments

- Built with pypdf and the OpenAI Python SDK.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "instrumentaipdfsplitter",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "instrument, pdf, split, splitting, splitting pdf, music, openai",
    "author": null,
    "author_email": "Flinn Fuchs <flinn.handymail@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/e4/74/22755088dda70e1abc97ac2d6cd1d82d1772ba7d189a3fd2a43007e407bf/instrumentaipdfsplitter-0.2.0.tar.gz",
    "platform": null,
    "description": "# Instrument AI PDF Splitter\r\n\r\nA lightweight Python tool that uses OpenAI to analyze multi-page sheet-music PDFs, detect instrument parts (including voice/desk numbers), determine their page ranges, and split the source PDF into one file per instrument/voice.\r\n\r\n- AI-assisted part detection: extracts instruments, voice numbers, and 1-indexed start/end pages as strict JSON.\r\n- Smart uploads: avoids re-uploading identical files via SHA-256 hashing.\r\n- Reliable splitting: clamps page ranges, sanitizes filenames, and writes outputs using pypdf.\r\n- Flexible input: use AI analysis or provide your own instrument list (InstrumentPart or JSON).\r\n- Configurable model: via constructor or OPENAI_MODEL env var; requires an OpenAI API key.\r\n\r\n## Installation\r\n\r\nReplace PACKAGE_NAME with your actual PyPI name once published (e.g., instrument-ai-pdf-splitter).\r\n\r\n```bash\r\npip install PACKAGE_NAME\r\n```\r\n\r\nRequirements:\r\n- Python 3.10+\r\n- openai (>= 1.0.0)\r\n- pypdf\r\n- dataclasses (builtin)\r\n- typing, pathlib, etc. (builtin)\r\n\r\n## Quickstart\r\n\r\n```python\r\nimport os\r\nimport json\r\nfrom instrument_ai_pdf_splitter import InstrumentAiPdfSplitter\r\n\r\n# Set your OpenAI API key via env or pass directly\r\napi_key = os.getenv(\"OPENAI_API_KEY\")\r\n\r\nsplitter = InstrumentAiPdfSplitter(api_key=api_key)\r\n\r\n# 1) Analyze the PDF to get instrument parts and page ranges\r\ndata = splitter.analyse(\"scores/book.pdf\")\r\nprint(json.dumps(data, indent=2))\r\n\r\n# Example output (JSON):\r\n# {\r\n#   \"instruments\": [\r\n#     {\"name\": \"Trumpet in Bb\", \"voice\": \"1\", \"start_page\": 3, \"end_page\": 5},\r\n#     {\"name\": \"Alto Sax\", \"voice\": null, \"start_page\": 6, \"end_page\": 9}\r\n#   ]\r\n# }\r\n\r\n# 2) Split the PDF into one file per instrument/voice\r\nresults = splitter.split_pdf(\"scores/book.pdf\")\r\nfor r in results:\r\n    print(f\"{r['name']} {r['voice']} -> {r['output_path']} [{r['start_page']}-{r['end_page']}]\")\r\n```\r\n\r\nOutput files are saved into a sibling directory named \"<stem>_parts\" by default (e.g., book_parts).\r\n\r\n## Manual instrument data (no AI call)\r\n\r\nYou can skip analysis and provide parts manually, either as InstrumentPart instances or JSON-like dicts.\r\n\r\n```python\r\nfrom instrument_ai_pdf_splitter import InstrumentAiPdfSplitter, InstrumentPart\r\n\r\nsplitter = InstrumentAiPdfSplitter(api_key=\"YOUR_OPENAI_API_KEY\")\r\n\r\nparts = [\r\n    InstrumentPart(name=\"Trumpet in Bb\", voice=\"1\", start_page=3, end_page=5),\r\n    {\"name\": \"Alto Sax\", \"voice\": None, \"start_page\": 6, \"end_page\": 9},  # JSON-like dict also works\r\n]\r\n\r\nresults = splitter.split_pdf(\r\n    pdf_path=\"scores/book.pdf\",\r\n    instruments_data=parts,\r\n    out_dir=\"output/parts\"  # optional custom directory\r\n)\r\n\r\nfor r in results:\r\n    print(r)\r\n```\r\n\r\n## Configuration\r\n\r\n- API key: Provide via constructor or set OPENAI_API_KEY in your environment.\r\n- Model: Pass `model` to the constructor or set `OPENAI_MODEL`; defaults to \"gpt-4.1\".\r\n\r\n```python\r\nsplitter = InstrumentAiPdfSplitter(api_key=\"...\", model=\"gpt-4.1\")\r\n```\r\n\r\nNote: Model availability depends on your OpenAI account. Use a model that supports the Responses API with file inputs. You will get the best results with gpt-5.\r\n\r\n## How it works\r\n\r\n- Content-hash uploads: Files are uploaded once per SHA-256; duplicates are skipped.\r\n- AI analysis: The PDF and a strict prompt are sent to OpenAI; output is parsed as JSON.\r\n- Splitting:\r\n  - Ensures pages are 1-indexed and within document bounds.\r\n  - Swaps start/end if reversed.\r\n  - Sanitizes output filenames (removes unsafe characters).\r\n  - Writes per-part PDFs using pypdf.\r\n\r\n## Public API\r\n\r\n| Item | Signature | Description |\r\n|------|-----------|-------------|\r\n| InstrumentPart | name: str; voice: Optional[str]; start_page: int; end_page: int | Dataclass representing a single instrument part with optional voice and 1-indexed inclusive page range. |\r\n| InstrumentAiPdfSplitter.__init__ | (api_key: str, *, model: str | None = None) -> None | Initialize the splitter with OpenAI credentials and default prompt. |\r\n| InstrumentAiPdfSplitter.analyse | (pdf_path: str) -> dict | Analyze a PDF and return instrument data as JSON {instruments: [...]}. |\r\n| InstrumentAiPdfSplitter.is_file_already_uploaded | (pdf_path: str) -> Tuple[bool, str] | Check if a file (by SHA-256) is already uploaded; returns (True, file_id) or (False,). |\r\n| InstrumentAiPdfSplitter.split_pdf | (pdf_path: str, instruments_data: List[InstrumentPart] | Dict[str, Any] | None = None, out_dir: Optional[str] = None) -> List[Dict[str, Any]] | Split the PDF per instrument/voice. Returns metadata with output_path. |\r\n| InstrumentAiPdfSplitter.file_hash | (path: str) -> str | Compute SHA-256 hex digest of a file\u2019s contents. |\r\n\r\n## Error handling\r\n\r\n- FileNotFoundError: Path doesn\u2019t exist.\r\n- ValueError: Not a file or not a .pdf.\r\n- json.JSONDecodeError: If AI output isn\u2019t valid JSON (rare; retry or adjust model).\r\n- OpenAI errors: Network/auth/model issues are propagated from the OpenAI SDK.\r\n\r\n## Tips for best results\r\n\r\n- Use clear, well-structured PDFs with visible instrument headers or page titles.\r\n- If AI is uncertain, manually provide `instruments_data` for precise splitting.\r\n- Verify the model supports file inputs in your region/account.\r\n- Handle sensitive material carefully; PDFs are uploaded to OpenAI for analysis.\r\n\r\n## Example project structure\r\n\r\n```text\r\nscores/\r\n\u251c\u2500\u2500 book.pdf\r\noutput/\r\n\u2514\u2500\u2500 parts/\r\n    \u251c\u2500\u2500 01 - Trumpet in Bb 1.pdf\r\n    \u251c\u2500\u2500 02 - Alto Sax.pdf\r\n    \u2514\u2500\u2500 ...\r\n```\r\n\r\n## Development\r\n\r\n```bash\r\n# Clone and install locally\r\ngit clone REPO_URL\r\ncd REPO_DIR\r\npip install -e .\r\n\r\n# Run a quick test (adjust paths)\r\npython -c \"from instrument_ai_pdf_splitter import InstrumentAiPdfSplitter; import os; s=InstrumentAiPdfSplitter(api_key=os.getenv('OPENAI_API_KEY')); print(s.file_hash('scores/book.pdf'))\"\r\n```\r\n\r\n## Versioning and compatibility\r\n\r\n- Tested with Python 3.10+.\r\n- Requires openai>=1.0.0 and pypdf. Keep dependencies updated.\r\n\r\n## FAQ\r\n\r\n- Does it require internet?\r\n  - Yes, for AI analysis. Splitting runs locally.\r\n- Can I prevent re-uploads?\r\n  - Yes. The tool checks a SHA-256 content hash against your uploaded files.\r\n- Is the output deterministic?\r\n  - The JSON structure is deterministic; the content depends on model interpretation.\r\n\r\n## License\r\n\r\nCopyright (c) 2025 Flinn\r\n\r\nPermission is hereby granted to use, copy, and distribute this software in unmodified form,\r\nprovided that proper attribution is given to the author. Modification, merging, or creation\r\nof derivative works based on this software is strictly prohibited.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\r\nBUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN\r\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n\r\n## Acknowledgments\r\n\r\n- Built with pypdf and the OpenAI Python SDK.\r\n",
    "bugtrack_url": null,
    "license": "Proprietary - No modification allowed",
    "summary": "AI-assisted instrument part detector and PDF splitter",
    "version": "0.2.0",
    "project_urls": null,
    "split_keywords": [
        "instrument",
        " pdf",
        " split",
        " splitting",
        " splitting pdf",
        " music",
        " openai"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "8c06c6d0043881d283227a78275ee78d7d3cf26688b230fc4fb4b1b5e09a8b2f",
                "md5": "dfeb64b836cc88ab182bd3f289cf67a9",
                "sha256": "a24c069877b1a22e756404df2053198d66ede33aa0e76f7a9a183dbb62fe80b9"
            },
            "downloads": -1,
            "filename": "instrumentaipdfsplitter-0.2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "dfeb64b836cc88ab182bd3f289cf67a9",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 5456,
            "upload_time": "2025-10-06T18:46:01",
            "upload_time_iso_8601": "2025-10-06T18:46:01.097462Z",
            "url": "https://files.pythonhosted.org/packages/8c/06/c6d0043881d283227a78275ee78d7d3cf26688b230fc4fb4b1b5e09a8b2f/instrumentaipdfsplitter-0.2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "e47422755088dda70e1abc97ac2d6cd1d82d1772ba7d189a3fd2a43007e407bf",
                "md5": "365f33d8b19dcb1cf5e8733d9b5a8c2c",
                "sha256": "afab1227f311c22c992db826531da020ba7292d72ed2a46d0ad7cbb664b6cbfb"
            },
            "downloads": -1,
            "filename": "instrumentaipdfsplitter-0.2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "365f33d8b19dcb1cf5e8733d9b5a8c2c",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 4759,
            "upload_time": "2025-10-06T18:46:01",
            "upload_time_iso_8601": "2025-10-06T18:46:01.892249Z",
            "url": "https://files.pythonhosted.org/packages/e4/74/22755088dda70e1abc97ac2d6cd1d82d1772ba7d189a3fd2a43007e407bf/instrumentaipdfsplitter-0.2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-06 18:46:01",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "instrumentaipdfsplitter"
}
        
Elapsed time: 1.31152s