{"id":44040730,"url":"https://github.com/prokopto-dev/m4b-splitter","last_synced_at":"2026-02-07T20:38:09.996Z","repository":{"id":330216893,"uuid":"1121964742","full_name":"prokopto-dev/m4b-splitter","owner":"prokopto-dev","description":"Tool to split m4b audiobook files using ffmpeg as the backend, specifically with iPod Classics as targets.","archived":false,"fork":false,"pushed_at":"2025-12-23T23:15:14.000Z","size":84,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-25T12:48:05.211Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/prokopto-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-23T21:37:37.000Z","updated_at":"2025-12-23T23:13:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/prokopto-dev/m4b-splitter","commit_stats":null,"previous_names":["prokopto-dev/m4b-splitter"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/prokopto-dev/m4b-splitter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prokopto-dev%2Fm4b-splitter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prokopto-dev%2Fm4b-splitter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prokopto-dev%2Fm4b-splitter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prokopto-dev%2Fm4b-splitter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/prokopto-dev","download_url":"https://codeload.github.com/prokopto-dev/m4b-splitter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prokopto-dev%2Fm4b-splitter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29208163,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T20:33:12.493Z","status":"ssl_error","status_checked_at":"2026-02-07T20:30:47.381Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-07T20:38:09.189Z","updated_at":"2026-02-07T20:38:09.987Z","avatar_url":"https://github.com/prokopto-dev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# M4B Splitter\n\nA Python package for splitting M4B audiobook files into smaller parts at chapter boundaries while preserving metadata.\n\n## Features\n\n- **Chapter-aware splitting**: Splits only at chapter boundaries, never in the middle of a chapter\n- **Metadata preservation**: Maintains all original metadata (title, artist, album, etc.)\n- **Chapter information**: Preserves chapter titles with adjusted timestamps in each part\n- **Cover art**: Extracts and embeds cover art in all output files\n- **Progress tracking**: Real-time progress display with detailed step information\n- **No re-encoding**: Uses stream copying for fast, lossless splitting (by default)\n- **iPod Classic support**: Optional re-encoding for iPod Classic compatibility\n- **Dependency checking**: Detects OS and provides installation instructions for ffmpeg\n- **Flexible naming**: Customizable output filename patterns\n- **Python 3.12+**: Uses modern Python features for clean, type-safe code\n\n## Requirements\n\n- Python 3.12 or higher\n- `ffmpeg` and `ffprobe` installed and available in PATH\n\n### Checking Dependencies\n\nRun the built-in dependency check to verify ffmpeg is installed:\n\n```bash\nm4b-splitter check\n```\n\nThis will show whether ffmpeg/ffprobe are installed, and if not, provide OS-specific installation instructions.\n\n### Installing ffmpeg\n\n**Ubuntu/Debian:**\n```bash\nsudo apt update\nsudo apt install ffmpeg\n```\n\n**macOS (Homebrew):**\n```bash\nbrew install ffmpeg\n```\n\n**Windows (Chocolatey):**\n```bash\nchoco install ffmpeg\n```\n\n**Fedora:**\n```bash\nsudo dnf install ffmpeg\n```\n\n**Arch Linux:**\n```bash\nsudo pacman -S ffmpeg\n```\n\n## Installation\n\n### From source\n\n```bash\ngit clone https://github.com/example/m4b-splitter.git\ncd m4b-splitter\npip install -e .\n```\n\n### Using pip (when published)\n\n```bash\npip install m4b-splitter\n```\n\n## Usage\n\n### Command Line Interface\n\nBasic usage - split an audiobook with default 8-hour maximum per part:\n\n```bash\nm4b-splitter audiobook.m4b\n```\n\nSpecify maximum duration and output directory:\n\n```bash\nm4b-splitter audiobook.m4b -d 4h -o ./split_output\n```\n\nDuration formats supported:\n- Hours: `8h`, `4.5h`, `2`\n- Minutes: `90m`, `120m`\n- Combined: `2h30m`\n\nCustom output filename pattern:\n\n```bash\nm4b-splitter audiobook.m4b -p \"{title} - Part {part}/{total}.m4b\"\n```\n\nAvailable placeholders:\n- `{title}` - Book title from metadata\n- `{artist}` - Author/artist from metadata\n- `{part}` - Part number\n- `{total}` - Total number of parts\n\nQuiet mode (suppress progress output):\n\n```bash\nm4b-splitter audiobook.m4b -q\n```\n\n### iPod Classic Mode\n\nFor compatibility with iPod Classic devices, use the `--ipod` flag to re-encode audio:\n\n```bash\nm4b-splitter audiobook.m4b --ipod\n```\n\nThe iPod Classic has a 32-bit sample counter limitation that restricts playback duration. The `--ipod` flag re-encodes audio to work around this.\n\n**iPod Presets:**\n\n| Preset | Sample Rate | Bitrate | Channels | Max Duration |\n|--------|-------------|---------|----------|--------------|\n| standard | 22050 Hz | 64 kbps | Mono | ~55 hours |\n| high | 44100 Hz | 128 kbps | Stereo | ~27 hours |\n| extended | 16000 Hz | 48 kbps | Mono | ~74 hours |\n\nUse a specific preset:\n\n```bash\nm4b-splitter audiobook.m4b --ipod --ipod-preset high\n```\n\n**Note:** iPod mode requires re-encoding, which is slower than the default stream copy mode but produces smaller files optimized for the device.\n\n### Python API\n\n#### Simple usage\n\n```python\nfrom m4b_splitter import split_m4b\n\n# Split with default settings (8-hour max per part)\nresult = split_m4b(\"audiobook.m4b\", \"./output\")\n\n# Check result\nif result.success:\n    print(f\"Created {len(result.parts)} parts:\")\n    for path in result.output_files:\n        print(f\"  {path}\")\nelse:\n    print(f\"Failed: {result.error_message}\")\n```\n\n#### iPod mode\n\n```python\nfrom m4b_splitter import split_m4b\n\n# Split with iPod-compatible encoding\nresult = split_m4b(\n    \"audiobook.m4b\",\n    \"./output\",\n    max_duration_hours=4,\n    ipod_mode=True,\n    ipod_preset=\"standard\"  # or \"high\" or \"extended\"\n)\n```\n\n#### Advanced usage with custom progress\n\n```python\nfrom pathlib import Path\nfrom m4b_splitter import (\n    M4BSplitter,\n    ConsoleProgress,\n    ProgressCallback,\n    ProgressUpdate,\n)\n\n# Use built-in console progress\nsplitter = M4BSplitter(progress_callback=ConsoleProgress())\n\nresult = splitter.split(\n    input_file=Path(\"audiobook.m4b\"),\n    output_dir=Path(\"./output\"),\n    max_duration_hours=4.0,\n    output_pattern=\"{title} - Part {part} of {total}.m4b\",\n    ipod_mode=True\n)\n\n# Access detailed result information\nprint(f\"Source: {result.source_file}\")\nprint(f\"Original title: {result.original_metadata.title}\")\nprint(f\"Original artist: {result.original_metadata.artist}\")\n\nfor part in result.parts:\n    print(f\"\\nPart {part.part_number}/{part.total_parts}:\")\n    print(f\"  Duration: {part.duration:.1f}s\")\n    print(f\"  Chapters: {len(part.chapters)}\")\n    for ch in part.chapters:\n        print(f\"    - {ch.title}\")\n```\n\n#### Custom progress callback\n\n```python\nfrom m4b_splitter import M4BSplitter, ProgressCallback, ProgressUpdate\n\nclass MyProgress(ProgressCallback):\n    def on_progress(self, update: ProgressUpdate) -\u003e None:\n        if update.total \u003e 0:\n            percent = (update.current / update.total) * 100\n            print(f\"[{percent:.0f}%] {update.message}\")\n        else:\n            print(update.message)\n    \n    def on_complete(self, success: bool, message: str) -\u003e None:\n        status = \"SUCCESS\" if success else \"FAILED\"\n        print(f\"[{status}] {message}\")\n    \n    def on_error(self, error: str) -\u003e None:\n        print(f\"[ERROR] {error}\")\n\nsplitter = M4BSplitter(progress_callback=MyProgress())\nresult = splitter.split(\"audiobook.m4b\", \"./output\")\n```\n\n#### Checking dependencies programmatically\n\n```python\nfrom m4b_splitter import check_dependencies, format_dependency_check\n\n# Check if ffmpeg is available\nresult = check_dependencies()\n\nif result.all_found:\n    print(\"All dependencies satisfied!\")\n    print(f\"ffmpeg: {result.ffmpeg.path}\")\n    print(f\"ffprobe: {result.ffprobe.path}\")\nelse:\n    # Print detailed instructions\n    print(format_dependency_check(result))\n```\n\n#### Probing files without splitting\n\n```python\nfrom m4b_splitter import extract_chapters, extract_metadata, validate_m4b_file\nfrom pathlib import Path\n\nfile_path = Path(\"audiobook.m4b\")\n\n# Validate file\nis_valid, message = validate_m4b_file(file_path)\nprint(f\"Valid: {is_valid} - {message}\")\n\n# Get metadata\nmetadata = extract_metadata(file_path)\nprint(f\"Title: {metadata.title}\")\nprint(f\"Artist: {metadata.artist}\")\nprint(f\"Duration: {metadata.duration / 3600:.1f} hours\")\n\n# Get chapters\nchapters = extract_chapters(file_path)\nprint(f\"\\nChapters ({len(chapters)}):\")\nfor ch in chapters:\n    print(f\"  {ch.id + 1}. {ch.title} ({ch.duration:.0f}s)\")\n```\n\n## Output\n\nEach output file will:\n\n1. Contain complete chapters (never split mid-chapter)\n2. Have a title like \"Original Title - Part 1/3\"\n3. Include all original metadata with track number set to part/total\n4. Have chapter markers with timestamps adjusted to start from 0\n5. Include embedded cover art (if present in source)\n\nWith `--ipod` mode, files are also:\n- Re-encoded with optimized settings for iPod Classic\n- Smaller in file size (especially with mono/lower bitrate)\n- Compatible with the iPod's 32-bit sample counter limitation\n\n## Project Structure\n\n```\nm4b_splitter/\n├── src/\n│   └── m4b_splitter/\n│       ├── __init__.py      # Package exports\n│       ├── __main__.py      # Entry point for python -m\n│       ├── cli.py           # Command-line interface\n│       ├── dependencies.py  # FFmpeg dependency checking\n│       ├── models.py        # Data models (Chapter, Metadata, etc.)\n│       ├── probe.py         # ffprobe wrapper for metadata extraction\n│       ├── progress.py      # Progress tracking and display\n│       └── splitter.py      # Core splitting logic\n├── tests/                   # Test files\n├── pyproject.toml          # Package configuration\n└── README.md               # This file\n```\n\n## How It Works\n\n1. **Dependency Check**: Verify ffmpeg/ffprobe are available\n2. **Validation**: Verify the input file exists and contains chapters\n3. **Metadata Extraction**: Use ffprobe to extract all metadata and chapter information\n4. **Planning**: Calculate split points based on chapter boundaries and max duration\n5. **Cover Art**: Extract any embedded cover art for reuse\n6. **Splitting**: For each part:\n   - Generate an ffmpeg metadata file with adjusted chapter times\n   - Use ffmpeg to extract the audio segment (stream copy or re-encode for iPod)\n   - Embed metadata and cover art\n7. **Output**: Return structured result with all file paths and information\n\n## Limitations\n\n- Requires chapters to be present in the M4B file\n- Single chapters longer than max duration will be placed in their own part\n- Cover art must be in a format ffmpeg can extract (usually JPEG)\n- iPod mode requires re-encoding, which is slower than stream copy\n\n## License\n\nMIT License - see LICENSE file for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprokopto-dev%2Fm4b-splitter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprokopto-dev%2Fm4b-splitter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprokopto-dev%2Fm4b-splitter/lists"}