rekordbox-edit 0.7.0.dev69__tar.gz → 0.7.0.dev72__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.
- rekordbox_edit-0.7.0.dev72/.github/actions/build-release-notes/action.yml +25 -0
- rekordbox_edit-0.7.0.dev72/.github/actions/build-release-notes/build_release_notes.py +169 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.gitignore +1 -3
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/AGENTS.md +4 -1
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/CHANGELOG.md +4 -1
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/PKG-INFO +1 -1
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/docs/commands/convert.md +2 -1
- rekordbox_edit-0.7.0.dev72/docs/faqs.md +50 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/docs/filtering.md +1 -1
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/mkdocs.yml +1 -3
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/pyproject.toml +3 -3
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/uv.lock +1 -1
- rekordbox_edit-0.7.0.dev69/.github/actions/build-release-notes/action.yml +0 -88
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.agent-style/RULES.md +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.agent-style/claude-code.md +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/actions/commit-check/action.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/actions/commitizen-bump/action.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/actions/commitizen-bump/commitizen-bump.sh +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/actions/e2e/action.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/actions/install/action.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/actions/lint/action.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/actions/test/action.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/workflows/cd.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/workflows/ci.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/workflows/publish.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/workflows/release.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.pre-commit-config.yaml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.python-version +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.readthedocs.yaml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/CLAUDE.md +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/CONTRIBUTING.md +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/LICENSE +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/Makefile +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/README.md +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/codecov.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/docker-compose.yml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/docs/api.md +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/docs/commands/edit.md +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/docs/commands/search.md +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/docs/index.md +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/docs/stylesheets/extra.css +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/__init__.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/_click.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/api/__init__.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/api/_utils.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/api/convert.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/api/edit.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/api/search.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/cli/__init__.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/cli/_utils.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/cli/convert.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/cli/edit.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/cli/main.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/cli/search.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/display.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/logger.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/models.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/query.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/rekordbox_edit/utils.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/renovate.json5 +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/ruff.toml +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/__init__.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/api/__init__.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/api/test_convert.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/api/test_edit.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/api/test_search.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/api/test_utils.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/cli/__init__.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/cli/test_convert.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/cli/test_edit.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/cli/test_main.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/cli/test_search.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/cli/test_utils.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/conftest.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/Dockerfile +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/__init__.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/__snapshots__/test_journey/test_search_full_json_snapshot[macos].json +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/__snapshots__/test_journey/test_search_full_json_snapshot[windows].json +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/__snapshots__/test_journey.ambr +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/conftest.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/audio/01-flac-44_1k-16b.flac +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/audio/02-flac-96k-24b.flac +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/audio/03-alac-44_1k-16b.m4a +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/audio/04-alac-48k-24b.m4a +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/audio/05-aiff-44_1k-16b.aiff +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/audio/06-wav-96k-24b.wav +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/audio/07-mp3-44_1k-320cbr.mp3 +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/audio/08-mp3-44_1k-v0vbr.mp3 +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/audio/09-aac-44_1k-256kbps.m4a +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/audio/10-/303/274/303/261/303/256c/303/266d/303/251-flac-44_1k-16b.flac" +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/macos/master.6.8.6.db +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/windows/master.6.8.6.db +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/test_journey.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/test_display.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/test_logger.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/test_models.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/test_query.py +0 -0
- {rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: "Build Release Notes"
|
|
2
|
+
description: "Generate categorized, per-commit release notes with each commit's PR resolved and its author credited"
|
|
3
|
+
inputs:
|
|
4
|
+
from_tag:
|
|
5
|
+
description: "Tag to start the changelog range from (exclusive). Leave empty for full history."
|
|
6
|
+
required: false
|
|
7
|
+
default: ""
|
|
8
|
+
to_tag:
|
|
9
|
+
description: "Tag to end the changelog range at (inclusive)"
|
|
10
|
+
required: true
|
|
11
|
+
github_token:
|
|
12
|
+
description: "GitHub token for API access"
|
|
13
|
+
required: true
|
|
14
|
+
runs:
|
|
15
|
+
using: "composite"
|
|
16
|
+
steps:
|
|
17
|
+
- name: Generate release notes
|
|
18
|
+
shell: bash
|
|
19
|
+
run: python3 "${GITHUB_ACTION_PATH}/build_release_notes.py"
|
|
20
|
+
env:
|
|
21
|
+
FROM_TAG: ${{ inputs.from_tag }}
|
|
22
|
+
TO_TAG: ${{ inputs.to_tag }}
|
|
23
|
+
OUTPUT_FILE: release-notes.md
|
|
24
|
+
REPO: ${{ github.repository }}
|
|
25
|
+
GH_TOKEN: ${{ inputs.github_token }}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Build categorized release notes from commits, linking each commit's PR.
|
|
3
|
+
|
|
4
|
+
Lists one entry per commit (not per PR) between ``FROM_TAG`` (exclusive) and
|
|
5
|
+
``TO_TAG`` (inclusive). Each entry links the pull request that introduced the
|
|
6
|
+
commit and credits its author. The PR (number and author login) is resolved via
|
|
7
|
+
``GET /repos/{repo}/commits/{sha}/pulls``, which works for both squash and
|
|
8
|
+
rebase merges. Commits with no associated PR fall back to a short-SHA reference
|
|
9
|
+
and the git author name.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
|
|
21
|
+
# Category matchers, evaluated in order; first match wins. Mirrors the
|
|
22
|
+
# conventional-commit types accepted by the project's commitizen schema.
|
|
23
|
+
_BREAKING_TITLE = re.compile(r"^(BREAKING(\(.*\))?:|(\w+)?(\(.*\))?!:)")
|
|
24
|
+
_BREAKING_BODY = re.compile(r"BREAKING[\- ]CHANGE")
|
|
25
|
+
_CATEGORY_TITLE_RULES: list[tuple[str, re.Pattern[str]]] = [
|
|
26
|
+
("## Features", re.compile(r"^feat(\(.*\))?:")),
|
|
27
|
+
("## Fixes", re.compile(r"^fix(?!\(deps\))(\(.*\))?:")),
|
|
28
|
+
(
|
|
29
|
+
"## Chores",
|
|
30
|
+
re.compile(
|
|
31
|
+
r"^(chore(?!\(deps\))|refactor|ci|docs|style|perf|test|build)(\(.*\))?:"
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
("## Dependency Updates", re.compile(r"^(chore|fix)\(deps\):")),
|
|
35
|
+
]
|
|
36
|
+
_BREAKING_TITLE_HEADER = "## Breaking Changes"
|
|
37
|
+
_UNCATEGORIZED_HEADER = "## Uncategorized"
|
|
38
|
+
_CATEGORY_ORDER = [
|
|
39
|
+
_BREAKING_TITLE_HEADER,
|
|
40
|
+
"## Features",
|
|
41
|
+
"## Fixes",
|
|
42
|
+
"## Chores",
|
|
43
|
+
"## Dependency Updates",
|
|
44
|
+
_UNCATEGORIZED_HEADER,
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Commits that are noise in a changelog: merge commits are dropped at the
|
|
48
|
+
# ``git log`` level (--no-merges); version-bump commits are filtered here.
|
|
49
|
+
_SKIP_SUBJECT = re.compile(r"^bump:")
|
|
50
|
+
# A trailing "(#NN)" left by squash merges, stripped so it is not duplicated
|
|
51
|
+
# alongside the resolved reference.
|
|
52
|
+
_TRAILING_PR = re.compile(r"\s*\(#\d+\)\s*$")
|
|
53
|
+
|
|
54
|
+
_RECORD_SEP = "\x1e"
|
|
55
|
+
_FIELD_SEP = "\x1f"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class Commit:
|
|
60
|
+
sha: str
|
|
61
|
+
subject: str
|
|
62
|
+
body: str
|
|
63
|
+
author: str
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Category:
|
|
68
|
+
header: str
|
|
69
|
+
lines: list[str] = field(default_factory=list)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _run(args: list[str]) -> subprocess.CompletedProcess[str]:
|
|
73
|
+
return subprocess.run(args, capture_output=True, text=True, encoding="utf-8")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def read_commits(from_tag: str, to_tag: str) -> list[Commit]:
|
|
77
|
+
rng = f"{from_tag}..{to_tag}" if from_tag else to_tag
|
|
78
|
+
fmt = _FIELD_SEP.join(["%H", "%s", "%b", "%an"]) + _RECORD_SEP
|
|
79
|
+
result = _run(["git", "log", "--no-merges", f"--format={fmt}", rng])
|
|
80
|
+
if result.returncode != 0:
|
|
81
|
+
raise SystemExit(f"git log failed for range '{rng}': {result.stderr.strip()}")
|
|
82
|
+
|
|
83
|
+
commits: list[Commit] = []
|
|
84
|
+
for record in result.stdout.split(_RECORD_SEP):
|
|
85
|
+
record = record.strip("\n")
|
|
86
|
+
if not record:
|
|
87
|
+
continue
|
|
88
|
+
sha, subject, body, author = (record.split(_FIELD_SEP) + ["", "", ""])[:4]
|
|
89
|
+
commits.append(
|
|
90
|
+
Commit(
|
|
91
|
+
sha=sha,
|
|
92
|
+
subject=subject.strip(),
|
|
93
|
+
body=body.strip(),
|
|
94
|
+
author=author.strip(),
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
return commits
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def resolve_pr(repo: str, sha: str) -> tuple[int, str] | None:
|
|
101
|
+
"""Return the (number, author_login) of a commit's PR, or None if unresolved."""
|
|
102
|
+
result = _run(["gh", "api", f"repos/{repo}/commits/{sha}/pulls"])
|
|
103
|
+
if result.returncode != 0 or not result.stdout.strip():
|
|
104
|
+
return None
|
|
105
|
+
try:
|
|
106
|
+
prs = json.loads(result.stdout)
|
|
107
|
+
except json.JSONDecodeError:
|
|
108
|
+
return None
|
|
109
|
+
merged = [pr for pr in prs if pr.get("merged_at")]
|
|
110
|
+
chosen = merged[0] if merged else (prs[0] if prs else None)
|
|
111
|
+
if not chosen:
|
|
112
|
+
return None
|
|
113
|
+
login = (chosen.get("user") or {}).get("login", "")
|
|
114
|
+
return chosen["number"], login
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def categorize(commit: Commit) -> str:
|
|
118
|
+
if _BREAKING_TITLE.match(commit.subject) or _BREAKING_BODY.search(commit.body):
|
|
119
|
+
return _BREAKING_TITLE_HEADER
|
|
120
|
+
for header, pattern in _CATEGORY_TITLE_RULES:
|
|
121
|
+
if pattern.match(commit.subject):
|
|
122
|
+
return header
|
|
123
|
+
return _UNCATEGORIZED_HEADER
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def render_line(commit: Commit, repo: str) -> str:
|
|
127
|
+
subject = _TRAILING_PR.sub("", commit.subject)
|
|
128
|
+
pr = resolve_pr(repo, commit.sha)
|
|
129
|
+
if pr is not None:
|
|
130
|
+
number, login = pr
|
|
131
|
+
credit = f"#{number} by @{login}" if login else f"#{number}"
|
|
132
|
+
return f"- {subject} ({credit})"
|
|
133
|
+
return f"- {subject} ({commit.sha[:7]} by {commit.author})"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def build_notes(commits: list[Commit], repo: str) -> str:
|
|
137
|
+
categories = {header: Category(header) for header in _CATEGORY_ORDER}
|
|
138
|
+
for commit in commits:
|
|
139
|
+
if _SKIP_SUBJECT.match(commit.subject):
|
|
140
|
+
continue
|
|
141
|
+
categories[categorize(commit)].lines.append(render_line(commit, repo))
|
|
142
|
+
|
|
143
|
+
sections = [
|
|
144
|
+
f"{cat.header}\n" + "\n".join(cat.lines)
|
|
145
|
+
for header in _CATEGORY_ORDER
|
|
146
|
+
if (cat := categories[header]).lines
|
|
147
|
+
]
|
|
148
|
+
return "\n\n".join(sections) if sections else "_No changes._"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main() -> None:
|
|
152
|
+
to_tag = os.environ.get("TO_TAG", "").strip()
|
|
153
|
+
if not to_tag:
|
|
154
|
+
raise SystemExit("TO_TAG is required")
|
|
155
|
+
from_tag = os.environ.get("FROM_TAG", "").strip()
|
|
156
|
+
repo = os.environ.get("REPO") or os.environ.get("GITHUB_REPOSITORY", "")
|
|
157
|
+
if not repo:
|
|
158
|
+
raise SystemExit("REPO (or GITHUB_REPOSITORY) is required")
|
|
159
|
+
output_file = os.environ.get("OUTPUT_FILE", "release-notes.md")
|
|
160
|
+
|
|
161
|
+
commits = read_commits(from_tag, to_tag)
|
|
162
|
+
notes = build_notes(commits, repo)
|
|
163
|
+
with open(output_file, "w", encoding="utf-8") as handle:
|
|
164
|
+
handle.write(notes + "\n")
|
|
165
|
+
print(notes, file=sys.stderr)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
main()
|
|
@@ -91,7 +91,10 @@ Every module should get and use its own `logger` for all logging purposes and fo
|
|
|
91
91
|
- Commit descriptions should be terse and use active tenses (e.g. "add feature")
|
|
92
92
|
- Commit bodies are completely optional, and should be used to describe changes when they aren't easily inferred by the main message, they should not include agent conversation context, decisions, or plan notes.
|
|
93
93
|
- The agent may commit. But the user always handles pushes, PR creation, and rebases after merges.
|
|
94
|
-
- Do not commit spec, brainstorming, or design documents (e.g. anything under `docs
|
|
94
|
+
- Do not commit spec, brainstorming, or design documents (e.g. anything under `ai-docs/`).
|
|
95
|
+
- **Save plans to:** `ai-docs/YYYYMMDD-HHmm-{session-slug}/plan.md` instead of the plugin or skill presets.
|
|
96
|
+
- **Save specs to:** `ai-docs/YYYYMMDD-HHmm-{session-slug}/specs.md` instead of the plugin or skill presets.
|
|
97
|
+
- **Save decision records to:** `ai-docs/decisions/{topic}.md` (not session-scoped).
|
|
95
98
|
|
|
96
99
|
## Tooling
|
|
97
100
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
## v0.7.0.
|
|
1
|
+
## v0.7.0.dev72 (2026-07-31)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
- ci: more precise changelogs on releases
|
|
5
|
+
- chore: update AGENTS.md
|
|
6
|
+
- docs: pave FAQ page based on convert + analysis research
|
|
4
7
|
- feat(convert): verify source codec against FileType before converting
|
|
5
8
|
- test(cli): mock rekordbox pid check in edit CLI tests
|
|
6
9
|
- feat: add probe/file-type matcher and codec_mismatch skip reason
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rekordbox-edit
|
|
3
|
-
Version: 0.7.0.
|
|
3
|
+
Version: 0.7.0.dev72
|
|
4
4
|
Summary: Tools for managing and modifying a RekordBox library en-masse
|
|
5
5
|
Project-URL: Homepage, https://github.com/jviall/rekordbox-edit
|
|
6
6
|
Project-URL: Repository, https://github.com/jviall/rekordbox-edit
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# convert
|
|
2
2
|
|
|
3
|
-
Convert audio files between formats and update the Rekordbox database to point at the new files.
|
|
3
|
+
Convert audio files between formats and update the Rekordbox database to point at the new files. Cues, analysis, beatgrids, and all other metadata are preserved — see [Frequently Asked Questions](../faqs.md) for more info.
|
|
4
4
|
|
|
5
5
|
## Supported Formats
|
|
6
6
|
|
|
@@ -50,6 +50,7 @@ rbe search --artist "Lauryn Hill" --print ids | rbe convert --yes
|
|
|
50
50
|
### Guardrails
|
|
51
51
|
- Without flags, `convert` shows every planned change and asks once before applying. `--interactive` confirms each track individually; `--dry-run` previews without writing; `--yes` confirms the default choice for all prompts without asking.
|
|
52
52
|
- Editing while Rekordbox is open risks corrupting your database. By default `convert` warns and asks for confirmation (defaulting to no, so a `--yes` would exit); in a non-interactive mode (e.g. `--print ids`) it throws an error.
|
|
53
|
+
- Before a large run, walk through the checklist in [What Should I Do Before Converting?](../faqs.md#what-should-i-do-before-converting)
|
|
53
54
|
|
|
54
55
|
## Reference
|
|
55
56
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Frequently Asked Questions
|
|
2
|
+
|
|
3
|
+
Messing with your Rekordbox library is bound to raise some questions and concerns, particularly around preserving all the manual work you've done to organize, catalog, and prepare your library for gigs. This page is dedicated to answering those questions around what the impact and risks are of editing your rekordbox library or the underlying audio files using rekordbox-edit. If your question isn't answered here, please ask it in [a discussion](https://github.com/jviall/rekordbox-edit/discussions/new/choose).
|
|
4
|
+
|
|
5
|
+
## How Does Rekordbox's Analysis Work?
|
|
6
|
+
|
|
7
|
+
Analysis touches two places in your library. The database gets the summary values for things like tempo, key, and the flags Rekordbox uses to track analysis state. The heavier artifacts live in a per-track folder of analysis files (.ANLZ) containing the beat grid, waveforms, and phrase data. Your cues and loops live in neither; they are separate database records that only your own edits change.
|
|
8
|
+
|
|
9
|
+
What analysis writes is a deterministic estimate. Deterministic, because the same file analyzed twice produces byte-identical results. An estimate, because tempo, grid, and key are inferred from the audio, and Rekordbox's inference is sometimes wrong. Both aspects are worth considering; when the analysis of a converted file produces a different result, it is simply a different estimate from different data.
|
|
10
|
+
|
|
11
|
+
## How Does Converting a Track Affect the Analysis?
|
|
12
|
+
|
|
13
|
+
The reason conversion is safe comes down to how Rekordbox stores analysis: positions are timestamps. The beat grid is a list of times in milliseconds, cues are stored in milliseconds, and phrases are anchored to beats. None of it references sample counts or byte positions in the audio file. Change the codec, the sample rate, or the bit depth, and every stored position still names the same musical moment.
|
|
14
|
+
|
|
15
|
+
`rbe convert` therefore only has to update what identifies the file: the track's database row (the file path, sample rate, bit depth, and similar columns) and the path recorded inside the analysis files. It rewrites nothing else.
|
|
16
|
+
|
|
17
|
+
There are a few minor things left stale by a conversion (without any consequence that I have observed yet):
|
|
18
|
+
|
|
19
|
+
- The waveform still draws the old audio: identical unless you converted to MP3, and then only imperceptibly off.
|
|
20
|
+
- A FLAC-specific seek index goes stale when you convert away from FLAC, which would be removed on any later analysis.
|
|
21
|
+
|
|
22
|
+
Converting *to* FLAC does not create that seek index automatically either; only an analysis can. I have *not* tested (yet) whether that matters in practice, but it makes a stronger case for re-analyzing after a conversion to FLAC than for other targets. FLAC also is not the most ideal target format in general for Rekordbox (even though it's preferred for streaming and music hoarders), because only the more recent Pioneer devices support it. AIFF is what I would pick if you insist on lossless.
|
|
23
|
+
|
|
24
|
+
## Does Converting to MP3 Shift the Audio?
|
|
25
|
+
|
|
26
|
+
No. `rbe` uses LAME's gapless-playback encoding to avoid any start-of-file offset, and across many tests the MP3 320 output never differed in timing, sample for sample, from its lossless source. Cues and the beat grid stay exactly in register.
|
|
27
|
+
|
|
28
|
+
The only artifact is visual: the drawn waveform still reflects the lossless original rather than the MP3. A re-analysis would redraw it, but you'd be hard-pressed to notice the difference.
|
|
29
|
+
|
|
30
|
+
## Will My Cues and Beatgrid Survive?
|
|
31
|
+
|
|
32
|
+
Yes. Cues, loops, and memory points are records in your library database, positioned in milliseconds and attached to the track's database entry, and `rbe convert` does not touch them. The beat grid lives in the analysis files, which convert also leaves alone. In all my testing, every cue and beat grid has been completely unchanged by a conversion, and works just as it did prior.
|
|
33
|
+
|
|
34
|
+
This includes hand-tuned grids. Because convert never rewrites the grid a manually corrected beat grid survives conversion unchanged. The one operation that can replace it is a re-analysis you run yourself; see below.
|
|
35
|
+
|
|
36
|
+
## Should I Re-Analyze After Converting?
|
|
37
|
+
|
|
38
|
+
You do not need to. The existing analysis remains correct for the converted file, and Rekordbox never re-analyzes on its own; it waits for you to ask. Skipping re-analysis keeps your grid and cues exactly as they are.
|
|
39
|
+
|
|
40
|
+
If you do re-analyze, the outcome depends on whether the conversion changed the audio samples. A pure container swap (WAV to FLAC or AIFF at the same depth and rate) reproduces the previous analysis exactly. A conversion that resamples, reduces bit depth, or lossily compresses to MP3 produces a slightly different estimate: about half a BPM of tempo difference, a first beat shifted by tens of milliseconds, cues sitting up to roughly 20 ms off the new grid. The new grid is not necessarily more or less correct than the previous; your ears and eyes will be the best judges of that. Ultimately it is the same song being analyzed, but Rekordbox made a fresh estimate on different underlying data. I would argue, though, that an analysis of a high-res file is more likely to be accurate than one of a lower-fidelity file.
|
|
41
|
+
|
|
42
|
+
One real hazard: re-analysis replaces manually edited beat grids on any track that is not locked. If you plan to re-analyze after a bulk conversion, turn on Analysis Lock first for tracks whose grids you have tuned by hand.
|
|
43
|
+
|
|
44
|
+
## What Should I Do Before Converting?
|
|
45
|
+
|
|
46
|
+
- **Preview with `--dry-run`.** See exactly which tracks will convert and where the files will go before anything is written.
|
|
47
|
+
- **Close Rekordbox.** Writing to the database while Rekordbox has it open risks corruption.
|
|
48
|
+
- **Make fresh backups of your music and your Rekordbox database.** Conversion rewrites files and database rows in place; a current backup makes any surprise reversible.
|
|
49
|
+
- **Keep your originals until you are satisfied.** The default `--delete-originals lossless` only deletes a source when no audio information was lost. Be deliberate before choosing `all`.
|
|
50
|
+
- **Lock hand-tuned beat grids if you plan to re-analyze.** Conversion won't mess with your grids, but a later bulk re-analysis replaces grids on unlocked tracks. Analysis Lock exists for exactly this.
|
|
@@ -104,4 +104,4 @@ rbe search --artist "Daft Punk" --artist "Justice" --print ids \
|
|
|
104
104
|
| rbe convert --format-out mp3 --dry-run
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
For richer pipelines, `--print json` emits the same selection as structured data for `jq` and other shell tools. Use the [API functions](
|
|
107
|
+
For richer pipelines, `--print json` emits the same selection as structured data for `jq` and other shell tools. Use the [API functions](api.md#functions) if you want to build a more complex python script.
|
|
@@ -4,9 +4,6 @@ site_url: https://rekordbox-edit.readthedocs.io/
|
|
|
4
4
|
repo_url: https://github.com/jviall/rekordbox-edit
|
|
5
5
|
repo_name: jviall/rekordbox-edit
|
|
6
6
|
|
|
7
|
-
exclude_docs: |
|
|
8
|
-
superpowers/
|
|
9
|
-
|
|
10
7
|
theme:
|
|
11
8
|
name: material
|
|
12
9
|
palette:
|
|
@@ -60,4 +57,5 @@ nav:
|
|
|
60
57
|
- search: commands/search.md
|
|
61
58
|
- edit: commands/edit.md
|
|
62
59
|
- convert: commands/convert.md
|
|
60
|
+
- FAQs: faqs.md
|
|
63
61
|
- API Reference: api.md
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rekordbox-edit"
|
|
7
|
-
version = "0.7.0.
|
|
7
|
+
version = "0.7.0.dev72"
|
|
8
8
|
description = "Tools for managing and modifying a RekordBox library en-masse"
|
|
9
9
|
authors = [{ name = "James Viall", email= "jamesviall@pm.me"}]
|
|
10
10
|
license = "MIT"
|
|
@@ -91,8 +91,8 @@ message_template = "{{change_type}}{{scope}}: {{message}}"
|
|
|
91
91
|
example = "feat(convert): add new conversion feature"
|
|
92
92
|
schema = "<type>(<scope>): <body>"
|
|
93
93
|
schema_pattern = "^(BREAKING|feat|fix|refactor|ci|test|docs|chore)(\\([^)]*\\))?!?: .+"
|
|
94
|
-
bump_pattern = "^(BREAKING|feat|fix|perf|hotfix|refactor)"
|
|
95
|
-
bump_map = { "BREAKING" = "MAJOR", "feat" = "MINOR", "fix" = "PATCH", "refactor" = "PATCH" }
|
|
94
|
+
bump_pattern = "^((BREAKING[\\-\\ ]CHANGE|BREAKING|feat|fix|perf|hotfix|refactor)(\\(.+\\))?!?):"
|
|
95
|
+
bump_map = { "^.+!$" = "MAJOR", "^BREAKING" = "MAJOR", "^feat" = "MINOR", "^fix" = "PATCH", "^refactor" = "PATCH" }
|
|
96
96
|
|
|
97
97
|
[[tool.commitizen.customize.questions]]
|
|
98
98
|
type = "list"
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
name: "Build Release Notes"
|
|
2
|
-
description: "Generate categorized release notes from conventional commits between two tags"
|
|
3
|
-
inputs:
|
|
4
|
-
from_tag:
|
|
5
|
-
description: "Tag to start the changelog range from (exclusive)"
|
|
6
|
-
required: true
|
|
7
|
-
to_tag:
|
|
8
|
-
description: "Tag to end the changelog range at (inclusive)"
|
|
9
|
-
required: true
|
|
10
|
-
github_token:
|
|
11
|
-
description: "GitHub token for API access"
|
|
12
|
-
required: true
|
|
13
|
-
runs:
|
|
14
|
-
using: "composite"
|
|
15
|
-
steps:
|
|
16
|
-
- uses: mikepenz/release-changelog-builder-action@c9bcd8238b6f41e05561348339429d360b1c0247 # v6.2.3
|
|
17
|
-
with:
|
|
18
|
-
outputFile: release-notes.md
|
|
19
|
-
configurationJson: |
|
|
20
|
-
{
|
|
21
|
-
"categories": [
|
|
22
|
-
{
|
|
23
|
-
"title": "## Breaking Changes",
|
|
24
|
-
"labels": [],
|
|
25
|
-
"rules": [
|
|
26
|
-
{
|
|
27
|
-
"pattern": "^(\\w+)?(\\(.*\\))?!:",
|
|
28
|
-
"on_property": "title"
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"title": "## Features",
|
|
34
|
-
"labels": [],
|
|
35
|
-
"rules": [
|
|
36
|
-
{
|
|
37
|
-
"pattern": "^feat(\\(.*\\))?:",
|
|
38
|
-
"on_property": "title"
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"title": "## Fixes",
|
|
44
|
-
"labels": [],
|
|
45
|
-
"rules": [
|
|
46
|
-
{
|
|
47
|
-
"pattern": "^fix(?!\\(deps\\))(\\(.*\\))?:",
|
|
48
|
-
"on_property": "title"
|
|
49
|
-
}
|
|
50
|
-
]
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"title": "## Chores",
|
|
54
|
-
"labels": [],
|
|
55
|
-
"rules": [
|
|
56
|
-
{
|
|
57
|
-
"pattern": "^(chore(?!\\(deps\\))|refactor|ci|docs|style|perf|test|build)(\\(.*\\))?:",
|
|
58
|
-
"on_property": "title"
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"title": "## Dependency Updates",
|
|
64
|
-
"labels": [],
|
|
65
|
-
"rules": [
|
|
66
|
-
{
|
|
67
|
-
"pattern": "^(chore|fix)\\(deps\\):",
|
|
68
|
-
"on_property": "title"
|
|
69
|
-
}
|
|
70
|
-
]
|
|
71
|
-
}
|
|
72
|
-
],
|
|
73
|
-
"exhaustive": true,
|
|
74
|
-
"sort": {
|
|
75
|
-
"order": "ASC",
|
|
76
|
-
"on_property": "title"
|
|
77
|
-
},
|
|
78
|
-
"template": "#{{CHANGELOG}}\n\n## Uncategorized\n#{{UNCATEGORIZED}}",
|
|
79
|
-
"pr_template": "- #{{TITLE}} (##{{NUMBER}} by @#{{AUTHOR}})",
|
|
80
|
-
"empty_template": "- No changes",
|
|
81
|
-
"tag_resolver": {
|
|
82
|
-
"method": "sort"
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
fromTag: ${{ inputs.from_tag }}
|
|
86
|
-
toTag: ${{ inputs.to_tag }}
|
|
87
|
-
env:
|
|
88
|
-
GITHUB_TOKEN: ${{ inputs.github_token }}
|
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/actions/commit-check/action.yml
RENAMED
|
File without changes
|
{rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/actions/commitizen-bump/action.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/.github/actions/install/action.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/__snapshots__/test_journey.ambr
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/macos/master.6.8.6.db
RENAMED
|
File without changes
|
{rekordbox_edit-0.7.0.dev69 → rekordbox_edit-0.7.0.dev72}/tests/e2e/fixtures/windows/master.6.8.6.db
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|