arxiv-title
Renames arXiv PDFs from opaque identifiers to descriptive, title-based filenames.
2101.05536v1.pdf
-> 2101.05536v1_scaling_equilibrium_propagation_to_deep_convnets_by_drastically_reducing_its_gradient_estimator_bias.pdf
Install
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
Use
arxiv-title artifacts/ # rename every arXiv-named PDF in a directory
arxiv-title -n artifacts/ # --dry-run: preview, touch nothing
arxiv-title --offline artifacts/ # no network; use the PDFs alone
arxiv-title 2101.05536v1.pdf # a single file
| Flag | Effect |
|---|---|
-n, --dry-run |
Print the rename table and exit without changing anything |
--offline |
Skip the arXiv API layer |
-r, --recursive |
Descend into subdirectories |
--force |
Accept titles that were only guessed from page layout |
--no-id-prefix |
Emit how_frogs_croak.pdf instead of 2101.05536v1_how_frogs_croak.pdf |
-v, --verbose |
Show which source supplied each title |
Only files whose name starts with an arXiv identifier are considered when scanning a directory; anything else is left alone. Because the ID is kept as a prefix by default, a second run over an already-renamed directory is a clean no-op.
How the title is found
Three sources, tried in order and reported by --verbose:
api— the arXiv Atom API, looked up by the identifier in the filename. Exact canonical titles, one batched HTTP request for the whole directory.metadata— the PDF's own/Titleentry.layout— typography on page one: the first contiguous block of larger-than-body-text lines near the top of the page.
Layout results are treated as low-confidence and held back from renaming unless you pass
--force.
Why the layout heuristic is not just "the largest text"
The obvious rule — take the biggest text on page one — gets 4 of the 10 sample papers right. Two problems dominate, both artifacts of how LaTeX sets titles:
- Small caps and enlarged leading capitals.
\textsc{}titles set the full capitals several points larger than the small capitals. A largest-glyph rule selects that one letter and reports a title of"M". Fixed by taking each line's character-count weighted dominant size, so twenty body letters outvote one oversized capital. - Split lines. Those same enlarged capitals sit higher on the page than the rest of
the line, so grouping words by their
topcoordinate tears one visual line in two — yieldingT D H : T M Lfollowed byHE RAGON ATCHLING …. Fixed by clustering on the baseline, which the two sizes share to within a point.
Two smaller details matter as well: extract_words splits a run wherever the font size
changes, so adjacent fragments must be rejoined without a space (otherwise The becomes
T he), and titles routinely wrap across two or three lines that need rejoining.
With those in place the layout heuristic alone gets all 10 of the sample papers right.
Tests
.venv/bin/python -m pytest
The suite is self-contained: extraction runs against three small PDFs committed under
tests/pdf/, and the API tests use a mocked transport. No network and no local corpus
required.
Releasing
CI runs the suite on Python 3.10–3.13 for every push and PR.
Publishing to PyPI happens only when a GitHub Release is published — a tag push
alone does nothing. The release job builds an sdist and wheel, runs twine check --strict, verifies the tag matches the version in pyproject.toml, and uploads via
PyPI Trusted Publishing (OIDC), so no API token is stored anywhere.
One-time setup on PyPI, at https://pypi.org/manage/account/publishing/, add a pending publisher:
| Field | Value |
|---|---|
| PyPI project | arxiv-title |
| Owner | cms-pm |
| Repository | arxiv-title |
| Workflow | release.yml |
| Environment | pypi |
Then create a matching GitHub environment named pypi under Settings → Environments.
Repeat with testpypi on test.pypi.org if you want the dry-run path.
To cut a release:
# bump version in pyproject.toml, commit, then
git tag v0.1.0 && git push origin v0.1.0
gh release create v0.1.0 --generate-notes
To rehearse without touching PyPI, run the Release workflow manually with the testpypi
target.
Metadata
Release files for arxiv-title 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| arxiv_title-0.1.0.tar.gz | 19.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| arxiv_title-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.0 kB
Release files / arxiv_title-0.1.0.tar.gz
| Download URL | arxiv_title-0.1.0.tar.gz |
|---|---|
| Size | 19.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5b2e869536e532586ab1a6bd520d8ddb235cfad9410a195a3c014b306dd8a70b
|
|
BLAKE2b-256 checksum How to use checksums |
87ca2718fc64e9afccb6e9c79254e744ba2be3bf2087bb0b84337eb571410cda
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.
Transparency logRelease files / arxiv_title-0.1.0-py3-none-any.whl
| Download URL | arxiv_title-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2a5948bdbba15f11fc5124d5cebc25aaec0a0cf080008675cfacf219240a2d5a
|
|
BLAKE2b-256 checksum How to use checksums |
7f922de6f07cc9f4cd4f62618d2c35fbd996a51f1716027edc94092620f1ac85
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.
Transparency log