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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file arxiv_title-0.1.0.tar.gz.
File metadata
- Download URL: arxiv_title-0.1.0.tar.gz
- Upload date:
- Size: 19.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5b2e869536e532586ab1a6bd520d8ddb235cfad9410a195a3c014b306dd8a70b
|
|
| MD5 |
4ecd0ce34b905a68ce03591bc6133095
|
|
| BLAKE2b-256 |
87ca2718fc64e9afccb6e9c79254e744ba2be3bf2087bb0b84337eb571410cda
|
Provenance
The following attestation bundles were made for arxiv_title-0.1.0.tar.gz:
Publisher:
release.yml on cms-pm/arxiv-title
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
arxiv_title-0.1.0.tar.gz -
Subject digest:
5b2e869536e532586ab1a6bd520d8ddb235cfad9410a195a3c014b306dd8a70b - Sigstore transparency entry: 2337762416
- Sigstore integration time:
-
Permalink:
cms-pm/arxiv-title@6348367ab2c45acd28e424d7f1edc1d0562b2962 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/cms-pm
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6348367ab2c45acd28e424d7f1edc1d0562b2962 -
Trigger Event:
release
-
Statement type:
File details
Details for the file arxiv_title-0.1.0-py3-none-any.whl.
File metadata
- Download URL: arxiv_title-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a5948bdbba15f11fc5124d5cebc25aaec0a0cf080008675cfacf219240a2d5a
|
|
| MD5 |
a4cac74c1aa31dd46bc09f95f07b3784
|
|
| BLAKE2b-256 |
7f922de6f07cc9f4cd4f62618d2c35fbd996a51f1716027edc94092620f1ac85
|
Provenance
The following attestation bundles were made for arxiv_title-0.1.0-py3-none-any.whl:
Publisher:
release.yml on cms-pm/arxiv-title
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
arxiv_title-0.1.0-py3-none-any.whl -
Subject digest:
2a5948bdbba15f11fc5124d5cebc25aaec0a0cf080008675cfacf219240a2d5a - Sigstore transparency entry: 2337762455
- Sigstore integration time:
-
Permalink:
cms-pm/arxiv-title@6348367ab2c45acd28e424d7f1edc1d0562b2962 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/cms-pm
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6348367ab2c45acd28e424d7f1edc1d0562b2962 -
Trigger Event:
release
-
Statement type: