redeflacue 0.1.0__tar.gz

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.
@@ -0,0 +1,19 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.{yml,yaml,toml,json}]
12
+ indent_size = 2
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
16
+
17
+ # Encoding fixtures must keep their legacy byte encoding.
18
+ [tests/fixtures/*windows1251*]
19
+ charset = unset
@@ -0,0 +1,53 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+ pull_request:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+ name: py${{ matrix.python-version }} / ${{ matrix.os }}
15
+ runs-on: ${{ matrix.os }}
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ os: [ubuntu-latest, macos-latest]
20
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
21
+
22
+ steps:
23
+ - uses: actions/checkout@v7
24
+ with:
25
+ fetch-depth: 0 # hatch-vcs derives the version from git tags
26
+
27
+ - uses: astral-sh/setup-uv@v10.3.0
28
+ with:
29
+ enable-cache: true
30
+ python-version: ${{ matrix.python-version }} # exported as UV_PYTHON
31
+
32
+ - name: Install sox (Linux)
33
+ if: runner.os == 'Linux'
34
+ run: sudo apt-get update && sudo apt-get install -y sox libsox-fmt-all
35
+
36
+ - name: Install sox (macOS)
37
+ if: runner.os == 'macOS'
38
+ run: brew install sox
39
+
40
+ - run: uv sync --frozen
41
+ - run: uv run ruff check
42
+ - run: uv run ruff format --check
43
+ - run: uv run mypy src
44
+ - run: uv run pytest --cov --cov-report=xml
45
+
46
+ - name: Upload coverage
47
+ uses: codecov/codecov-action@v7
48
+ with:
49
+ files: coverage.xml
50
+ flags: py${{ matrix.python-version }}-${{ matrix.os }}
51
+ fail_ci_if_error: false
52
+ # Required for private repositories.
53
+ token: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,31 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ pypi:
9
+ name: Build and publish to PyPI
10
+ runs-on: ubuntu-latest
11
+ # PyPI trusted publisher: r8/redeflacue, release.yml, environment pypi.
12
+ environment:
13
+ name: pypi
14
+ url: https://pypi.org/p/redeflacue
15
+ permissions:
16
+ id-token: write
17
+ contents: read
18
+
19
+ steps:
20
+ - uses: actions/checkout@v7
21
+ with:
22
+ fetch-depth: 0 # hatch-vcs derives the version from the pushed tag
23
+
24
+ - uses: astral-sh/setup-uv@v10.3.0
25
+
26
+ - run: uv build
27
+
28
+ - name: Smoke-test the wheel
29
+ run: uv run --isolated --no-project --with dist/*.whl redeflacue --version
30
+
31
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,27 @@
1
+ # Byte-compiled / cache
2
+ __pycache__/
3
+ *.py[cod]
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+
8
+ # Environments
9
+ .venv/
10
+ venv/
11
+ .env
12
+
13
+ # Build / dist
14
+ build/
15
+ dist/
16
+ *.egg-info/
17
+
18
+ # Coverage
19
+ .coverage
20
+ .coverage.*
21
+ coverage.xml
22
+ htmlcov/
23
+
24
+ # Editors / OS
25
+ .idea/
26
+ .vscode/
27
+ .DS_Store
@@ -0,0 +1,16 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.17.0
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+ - repo: local
9
+ hooks:
10
+ # Use the project environment because deflacue has no type stubs.
11
+ - id: mypy
12
+ name: mypy
13
+ entry: uv run mypy src
14
+ language: system
15
+ types: [python]
16
+ pass_filenames: false
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.1.0] - 2026-10-10
6
+
7
+ ### Added
8
+
9
+ - deflacue-compatible CLI (`redeflacue`, `python -m redeflacue`) with these flags:
10
+ `-d/--dest`, `-e/--encoding`, `-r/--recursive`, `--dry-run`, `-v/--verbose`; plus
11
+ `--version`.
12
+ - Library API: `plan_extractions()`, `build_sox_command()`, `Extraction`.
13
+
14
+ ### Fixed
15
+
16
+ - Track boundaries on non-44.1 kHz FLAC images. Cut points are passed to SoX as times instead of
17
+ 44.1 kHz sample counts.
18
+
19
+ [Unreleased]: https://github.com/r8/redeflacue/compare/v0.1.0...HEAD
20
+ [0.1.0]: https://github.com/r8/redeflacue/releases/tag/v0.1.0
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Sergey Storchay
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.5
2
+ Name: redeflacue
3
+ Version: 0.1.0
4
+ Summary: CUE + FLAC splitter that handles non-44.1 kHz audio
5
+ Project-URL: Homepage, https://github.com/r8/redeflacue
6
+ Project-URL: Issues, https://github.com/r8/redeflacue/issues
7
+ Project-URL: Changelog, https://github.com/r8/redeflacue/blob/main/CHANGELOG.md
8
+ Author-email: Sergey Storchay <r8@r8.com.ua>
9
+ License-Expression: BSD-3-Clause
10
+ License-File: LICENSE
11
+ Keywords: audio,cue,deflacue,flac,hi-res,sox,split
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Requires-Dist: deflacue>=2.0.1
28
+ Description-Content-Type: text/markdown
29
+
30
+ # redeflacue
31
+
32
+ `redeflacue` splits CUE + FLAC album images without assuming a 44.1 kHz sample rate.
33
+
34
+ It uses [deflacue](https://github.com/idlesign/deflacue) to parse cue sheets and keeps its
35
+ command-line interface, output layout, and tag mapping. The difference is in the arguments sent
36
+ to SoX: cue positions are passed as times instead of sample counts. This avoids misplaced track
37
+ boundaries on 96 kHz, 192 kHz, and other non-44.1 kHz files.
38
+
39
+ ## Install
40
+
41
+ Install the package with `uv` or `pipx`:
42
+
43
+ ```sh
44
+ uv tool install redeflacue
45
+ # or
46
+ pipx install redeflacue
47
+ ```
48
+
49
+ SoX is also required:
50
+
51
+ ```sh
52
+ sudo apt install sox libsox-fmt-all # Debian / Ubuntu
53
+ brew install sox # macOS
54
+ ```
55
+
56
+ ## Usage
57
+
58
+ ```sh
59
+ # Split .cue files in a directory.
60
+ redeflacue /home/me/cues/
61
+
62
+ # Read Windows-1251 cue sheets recursively and choose an output directory.
63
+ redeflacue -e windows-1251 -r -d /mnt/library/ /home/me/cues/
64
+
65
+ # Print the SoX commands without running them.
66
+ redeflacue --dry-run -v /home/me/cues/
67
+ ```
68
+
69
+ | Flag | Description |
70
+ | --- | --- |
71
+ | `-d, --dest DIR` | Output root; defaults to `deflacue/` beside each cue sheet |
72
+ | `-e, --encoding ENC` | Cue-sheet encoding, such as `windows-1251` |
73
+ | `-r, --recursive` | Search subdirectories |
74
+ | `--dry-run` | Print planned work without running SoX |
75
+ | `-v, --verbose` | Print each SoX command |
76
+ | `--version` | Print the version |
77
+
78
+ Tracks are written to:
79
+
80
+ ```text
81
+ <destination>/<performer>/<date - album>/<track number - title>.flac
82
+ ```
83
+
84
+ The cue sheet supplies the track number, title, artist, album, genre, date, ISRC, and
85
+ description tags.
86
+
87
+ ## Sample-rate fix
88
+
89
+ deflacue stores cue positions as 44.1 kHz sample offsets and passes them to `sox trim` as sample
90
+ counts. SoX interprets those counts using the input file's actual sample rate, so boundaries are
91
+ wrong when that rate is not 44.1 kHz.
92
+
93
+ A cue position is based on 75 frames per second. Because deflacue uses 588 samples per cue frame
94
+ (`44100 / 75`), dividing its stored offset by 44100 recovers the cue time. redeflacue sends that
95
+ time to SoX as `HH:MM:SS.ssssss`, which works independently of the input rate.
96
+
97
+ ## Python API
98
+
99
+ ```python
100
+ from pathlib import Path
101
+
102
+ from deflacue.parser import CueParser
103
+ from redeflacue import build_sox_command, plan_extractions
104
+
105
+ cue = CueParser.from_file(fpath=Path("album.cue")).run()
106
+ for extraction in plan_extractions(cue, Path("."), Path("out")):
107
+ print(
108
+ build_sox_command(
109
+ extraction.src,
110
+ extraction.dst,
111
+ extraction.start_s,
112
+ extraction.end_s,
113
+ extraction.tags,
114
+ )
115
+ )
116
+ ```
117
+
118
+ ## Development
119
+
120
+ ```sh
121
+ git clone https://github.com/r8/redeflacue.git
122
+ cd redeflacue
123
+ uv sync
124
+ uv run pytest
125
+ uv run ruff check
126
+ uv run ruff format --check
127
+ uv run mypy src
128
+ ```
129
+
130
+ Integration tests are skipped when SoX is not installed.
131
+
132
+ ## License
133
+
134
+ BSD-3-Clause. Cue-sheet parsing is provided by Igor Starikov's BSD-licensed
135
+ [deflacue](https://github.com/idlesign/deflacue).
@@ -0,0 +1,106 @@
1
+ # redeflacue
2
+
3
+ `redeflacue` splits CUE + FLAC album images without assuming a 44.1 kHz sample rate.
4
+
5
+ It uses [deflacue](https://github.com/idlesign/deflacue) to parse cue sheets and keeps its
6
+ command-line interface, output layout, and tag mapping. The difference is in the arguments sent
7
+ to SoX: cue positions are passed as times instead of sample counts. This avoids misplaced track
8
+ boundaries on 96 kHz, 192 kHz, and other non-44.1 kHz files.
9
+
10
+ ## Install
11
+
12
+ Install the package with `uv` or `pipx`:
13
+
14
+ ```sh
15
+ uv tool install redeflacue
16
+ # or
17
+ pipx install redeflacue
18
+ ```
19
+
20
+ SoX is also required:
21
+
22
+ ```sh
23
+ sudo apt install sox libsox-fmt-all # Debian / Ubuntu
24
+ brew install sox # macOS
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```sh
30
+ # Split .cue files in a directory.
31
+ redeflacue /home/me/cues/
32
+
33
+ # Read Windows-1251 cue sheets recursively and choose an output directory.
34
+ redeflacue -e windows-1251 -r -d /mnt/library/ /home/me/cues/
35
+
36
+ # Print the SoX commands without running them.
37
+ redeflacue --dry-run -v /home/me/cues/
38
+ ```
39
+
40
+ | Flag | Description |
41
+ | --- | --- |
42
+ | `-d, --dest DIR` | Output root; defaults to `deflacue/` beside each cue sheet |
43
+ | `-e, --encoding ENC` | Cue-sheet encoding, such as `windows-1251` |
44
+ | `-r, --recursive` | Search subdirectories |
45
+ | `--dry-run` | Print planned work without running SoX |
46
+ | `-v, --verbose` | Print each SoX command |
47
+ | `--version` | Print the version |
48
+
49
+ Tracks are written to:
50
+
51
+ ```text
52
+ <destination>/<performer>/<date - album>/<track number - title>.flac
53
+ ```
54
+
55
+ The cue sheet supplies the track number, title, artist, album, genre, date, ISRC, and
56
+ description tags.
57
+
58
+ ## Sample-rate fix
59
+
60
+ deflacue stores cue positions as 44.1 kHz sample offsets and passes them to `sox trim` as sample
61
+ counts. SoX interprets those counts using the input file's actual sample rate, so boundaries are
62
+ wrong when that rate is not 44.1 kHz.
63
+
64
+ A cue position is based on 75 frames per second. Because deflacue uses 588 samples per cue frame
65
+ (`44100 / 75`), dividing its stored offset by 44100 recovers the cue time. redeflacue sends that
66
+ time to SoX as `HH:MM:SS.ssssss`, which works independently of the input rate.
67
+
68
+ ## Python API
69
+
70
+ ```python
71
+ from pathlib import Path
72
+
73
+ from deflacue.parser import CueParser
74
+ from redeflacue import build_sox_command, plan_extractions
75
+
76
+ cue = CueParser.from_file(fpath=Path("album.cue")).run()
77
+ for extraction in plan_extractions(cue, Path("."), Path("out")):
78
+ print(
79
+ build_sox_command(
80
+ extraction.src,
81
+ extraction.dst,
82
+ extraction.start_s,
83
+ extraction.end_s,
84
+ extraction.tags,
85
+ )
86
+ )
87
+ ```
88
+
89
+ ## Development
90
+
91
+ ```sh
92
+ git clone https://github.com/r8/redeflacue.git
93
+ cd redeflacue
94
+ uv sync
95
+ uv run pytest
96
+ uv run ruff check
97
+ uv run ruff format --check
98
+ uv run mypy src
99
+ ```
100
+
101
+ Integration tests are skipped when SoX is not installed.
102
+
103
+ ## License
104
+
105
+ BSD-3-Clause. Cue-sheet parsing is provided by Igor Starikov's BSD-licensed
106
+ [deflacue](https://github.com/idlesign/deflacue).
@@ -0,0 +1,88 @@
1
+ [build-system]
2
+ requires = ["hatchling", "hatch-vcs"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "redeflacue"
7
+ dynamic = ["version"]
8
+ description = "CUE + FLAC splitter that handles non-44.1 kHz audio"
9
+ readme = "README.md"
10
+ license = "BSD-3-Clause"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Sergey Storchay", email = "r8@r8.com.ua" }]
13
+ requires-python = ">=3.9"
14
+ dependencies = ["deflacue>=2.0.1"]
15
+ keywords = ["cue", "flac", "sox", "split", "hi-res", "audio", "deflacue"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: End Users/Desktop",
20
+ "Operating System :: MacOS",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Topic :: Multimedia :: Sound/Audio :: Conversion",
30
+ "Typing :: Typed",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/r8/redeflacue"
35
+ Issues = "https://github.com/r8/redeflacue/issues"
36
+ Changelog = "https://github.com/r8/redeflacue/blob/main/CHANGELOG.md"
37
+
38
+ [project.scripts]
39
+ redeflacue = "redeflacue.cli:main"
40
+
41
+ [dependency-groups]
42
+ dev = [
43
+ "pytest>=8",
44
+ "pytest-cov>=5",
45
+ "ruff>=0.6",
46
+ "mypy>=1.10",
47
+ ]
48
+
49
+ [tool.hatch.version]
50
+ source = "vcs"
51
+
52
+ [tool.hatch.build.targets.wheel]
53
+ packages = ["src/redeflacue"]
54
+
55
+ [tool.ruff]
56
+ line-length = 100
57
+ src = ["src", "tests"]
58
+
59
+ [tool.ruff.lint]
60
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
61
+
62
+ [tool.mypy]
63
+ files = ["src"]
64
+ disallow_untyped_defs = true
65
+ disallow_incomplete_defs = true
66
+ check_untyped_defs = true
67
+ no_implicit_optional = true
68
+ warn_unused_ignores = true
69
+ warn_redundant_casts = true
70
+ warn_return_any = true
71
+ strict_equality = true
72
+
73
+ [[tool.mypy.overrides]]
74
+ module = ["deflacue", "deflacue.*"]
75
+ ignore_missing_imports = true
76
+
77
+ [tool.pytest.ini_options]
78
+ testpaths = ["tests"]
79
+ addopts = ["-ra", "--strict-markers"]
80
+ markers = ["integration: requires sox binary"]
81
+
82
+ [tool.coverage.run]
83
+ source = ["redeflacue"]
84
+ branch = true
85
+
86
+ [tool.coverage.report]
87
+ show_missing = true
88
+ skip_covered = true
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("redeflacue")
7
+ except PackageNotFoundError: # pragma: no cover
8
+ __version__ = "0.0.0+unknown"
9
+
10
+ from redeflacue.cli import main
11
+ from redeflacue.processor import Extraction, plan_extractions
12
+ from redeflacue.splitter import build_sox_command
13
+
14
+ __all__ = ["Extraction", "__version__", "build_sox_command", "main", "plan_extractions"]
@@ -0,0 +1,3 @@
1
+ from redeflacue.cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import logging
5
+ import shutil
6
+ from collections.abc import Sequence
7
+ from pathlib import Path
8
+
9
+ from redeflacue import __version__
10
+ from redeflacue.processor import iter_cue_files, process_cue
11
+
12
+ LOGGER = logging.getLogger("redeflacue")
13
+
14
+
15
+ def build_parser() -> argparse.ArgumentParser:
16
+ ap = argparse.ArgumentParser(
17
+ prog="redeflacue",
18
+ description="Split CUE + FLAC images without assuming a 44.1 kHz sample rate.",
19
+ )
20
+ ap.add_argument("source", type=Path, help="Directory containing .cue files")
21
+ ap.add_argument(
22
+ "-d",
23
+ "--dest",
24
+ type=Path,
25
+ default=None,
26
+ help="Output root (default: ./deflacue next to each cue)",
27
+ )
28
+ ap.add_argument(
29
+ "-e", "--encoding", default=None, help="Encoding of the .cue file (e.g. windows-1251)"
30
+ )
31
+ ap.add_argument("-r", "--recursive", action="store_true", help="Recurse into subdirectories")
32
+ ap.add_argument("--dry-run", action="store_true", help="Plan tracks without running SoX")
33
+ ap.add_argument("-v", "--verbose", action="store_true", help="Show each SoX command")
34
+ ap.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
35
+ return ap
36
+
37
+
38
+ def main(argv: Sequence[str] | None = None) -> int:
39
+ args = build_parser().parse_args(argv)
40
+
41
+ logging.basicConfig(
42
+ level=logging.DEBUG if args.verbose else logging.INFO,
43
+ format="%(levelname)s: %(message)s",
44
+ )
45
+
46
+ if shutil.which("sox") is None:
47
+ LOGGER.error("sox not found. Install: sudo apt install sox libsox-fmt-all")
48
+ return 1
49
+
50
+ src_root = args.source.resolve()
51
+ if not src_root.exists():
52
+ LOGGER.error("Path %s does not exist", src_root)
53
+ return 1
54
+
55
+ for cue in iter_cue_files(src_root, args.recursive):
56
+ target_root = (
57
+ args.dest.resolve() / cue.parent.name if args.dest else cue.parent / "deflacue"
58
+ )
59
+ if not args.dry_run:
60
+ target_root.mkdir(parents=True, exist_ok=True)
61
+ process_cue(cue, target_root, args.encoding, args.dry_run)
62
+
63
+ LOGGER.info("Done.")
64
+ return 0