Keep the article in {title} and {series}
The default template is now {title_sort} - {authors}. It gives the same name as Calibre's default.
This commit is contained in:
@@ -448,23 +448,27 @@ downloaded before.
|
|||||||
## File names of downloaded books
|
## File names of downloaded books
|
||||||
|
|
||||||
"Download ePub" on a story page sends the book to your browser with a file name. A template gives
|
"Download ePub" on a story page sends the book to your browser with a file name. A template gives
|
||||||
the name. The default template is `{title} - {authors}`, as in Calibre. For "The Book" by Ann
|
the name. The default template is `{title_sort} - {authors}`. For "The Book" by Ann Writer, it gives
|
||||||
Writer, it gives `Book, The - Ann Writer.epub`.
|
`Book, The - Ann Writer.epub`. That is the name that Calibre's default template gives.
|
||||||
|
|
||||||
An admin can change the template under "Book Downloads" in Settings. To go back to the default,
|
An admin can change the template under "Book Downloads" in Settings. To go back to the default,
|
||||||
clear the field and save. The template follows
|
clear the field and save. The template follows
|
||||||
the rules of [Calibre's templates](https://manual.calibre-ebook.com/template_lang.html) for file
|
the rules of [Calibre's templates](https://manual.calibre-ebook.com/template_lang.html) for file
|
||||||
names, so a template from Calibre gives the same name in Bindery. Bindery adds `.epub`. For example,
|
names. Bindery adds `.epub`. For example, `{series:|| - }{series_index:0>2s|| - }{title} - {authors}`
|
||||||
`{series:|| - }{series_index:0>2s|| - }{title} - {authors}` gives
|
gives `The Saga - 02 - The Book - Ann Writer.epub`.
|
||||||
`Saga, The - 02 - Book, The - Ann Writer.epub`.
|
|
||||||
|
In Calibre, `{title}` and `{series}` give the title sort in a file name, for example `Book, The`.
|
||||||
|
In Bindery, they keep the article. This is the same as Calibre with the tweak
|
||||||
|
`save_template_title_series_sorting` set to `strictly_alphabetic`. Use `{title_sort}` for the title
|
||||||
|
with the article at the end.
|
||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `{title}` | The title. A leading "The", "A" or "An" moves to the end, as in Calibre: `Book, The`. |
|
| `{title}` | The title: `The Book`. |
|
||||||
| `{title_sort}` | The same value as `{title}`. |
|
| `{title_sort}` | The title with a leading "The", "A" or "An" at the end: `Book, The`. |
|
||||||
| `{authors}`, `{author}` | The authors, with ` & ` between them. |
|
| `{authors}`, `{author}` | The authors, with ` & ` between them. |
|
||||||
| `{author_sort}` | The authors with the last name first: `Writer, Ann`. |
|
| `{author_sort}` | The authors with the last name first: `Writer, Ann`. |
|
||||||
| `{series}` | The series. A leading article moves to the end. |
|
| `{series}` | The series. |
|
||||||
| `{series_index}` | The series number: `2`, or `2.5`. |
|
| `{series_index}` | The series number: `2`, or `2.5`. |
|
||||||
| `{tags}` | The tags in sorted order, with `, ` between them. |
|
| `{tags}` | The tags in sorted order, with `, ` between them. |
|
||||||
| `{languages}` | The language, as Bindery stores it. |
|
| `{languages}` | The language, as Bindery stores it. |
|
||||||
@@ -495,7 +499,7 @@ gives the day, `%b` the month name, `%m` the month number and `%Y` the year. The
|
|||||||
`%b, %Y`, as in Calibre. It gives `May, 2020`. To go back to the default, clear the field and save.
|
`%b, %Y`, as in Calibre. It gives `May, 2020`. To go back to the default, clear the field and save.
|
||||||
A `/` in a date becomes `_`, as in Calibre.
|
A `/` in a date becomes `_`, as in Calibre.
|
||||||
|
|
||||||
Bindery follows Calibre 9.2.1 with its default tweaks, for English. The order of tags with letters
|
Bindery follows Calibre 9.2.1 for English, with the tweak above. The order of tags with letters
|
||||||
outside ASCII can differ from Calibre. The template applies only to downloads in the browser. It
|
outside ASCII can differ from Calibre. The template applies only to downloads in the browser. It
|
||||||
does not change the names of the files in `INGEST_DIR`. The browser can ignore the name.
|
does not change the names of the files in `INGEST_DIR`. The browser can ignore the name.
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"""Calibre's templates for file names, for the name of a downloaded book.
|
"""Calibre's templates for file names, for the name of a downloaded book.
|
||||||
|
|
||||||
Bindery follows Calibre 9.2.1 with its default tweaks: titles and series in library order with the English
|
Bindery follows Calibre 9.2.1 with its default tweaks and the English articles. prepare() takes the tweak for
|
||||||
articles. Dates take a strftime format, with Calibre's "%b, %Y" as the default. Bindery takes {field},
|
the order of titles and series as an argument. Dates take a strftime format, with Calibre's "%b, %Y" as the
|
||||||
{field[0]}, {field:format|prefix|suffix} and {{ }}. It refuses template functions and the program modes. It never
|
default. Bindery takes {field}, {field[0]}, {field:format|prefix|suffix} and {{ }}. It refuses template
|
||||||
runs template text as code.
|
functions and the program modes. It never runs template text as code.
|
||||||
|
|
||||||
The tags sort close to Calibre's ICU rules, but their order can differ for characters outside ASCII.
|
The tags sort close to Calibre's ICU rules, but their order can differ for characters outside ASCII.
|
||||||
"""
|
"""
|
||||||
@@ -16,8 +16,8 @@ from typing import Any
|
|||||||
|
|
||||||
from bindery.ingest import sanitize_filename
|
from bindery.ingest import sanitize_filename
|
||||||
|
|
||||||
# Calibre's default template for a file that it sends to a device.
|
# The name that Calibre's default template, {title} - {authors}, gives with its default tweaks.
|
||||||
DEFAULT_TEMPLATE = "{title} - {authors}"
|
DEFAULT_TEMPLATE = "{title_sort} - {authors}"
|
||||||
DEFAULT_DATE_FORMAT = "%b, %Y"
|
DEFAULT_DATE_FORMAT = "%b, %Y"
|
||||||
MAX_TEMPLATE_LENGTH = 1024
|
MAX_TEMPLATE_LENGTH = 1024
|
||||||
MAX_DATE_FORMAT_LENGTH = 100
|
MAX_DATE_FORMAT_LENGTH = 100
|
||||||
@@ -29,7 +29,7 @@ MAX_NAME_BYTES = 200
|
|||||||
# a book without that value.
|
# a book without that value.
|
||||||
FIELDS = {
|
FIELDS = {
|
||||||
"title": "The title",
|
"title": "The title",
|
||||||
"title_sort": "The title in sort order",
|
"title_sort": 'The title with a leading "The", "A" or "An" at the end, such as "Book, The"',
|
||||||
"authors": "The authors",
|
"authors": "The authors",
|
||||||
"author": "The authors",
|
"author": "The authors",
|
||||||
"author_sort": "The author sort string. To use only the first letter of the name, use {author_sort[0]}",
|
"author_sort": "The author sort string. To use only the first letter of the name, use {author_sort[0]}",
|
||||||
|
|||||||
@@ -675,7 +675,10 @@ def _download_name(story: dict[str, Any]) -> str:
|
|||||||
book_id = story["calibre_id"] if config.CWA_ENABLED and story["calibre_id"] is not None else story["id"]
|
book_id = story["calibre_id"] if config.CWA_ENABLED and story["calibre_id"] is not None else story["id"]
|
||||||
name = ""
|
name = ""
|
||||||
try:
|
try:
|
||||||
values = filename_template.prepare(meta, book_id, date_format=_download_setting("download_date_format"))
|
# {title} and {series} keep a leading article, as in Calibre with this value of its tweak
|
||||||
|
# save_template_title_series_sorting. {title_sort} moves it to the end.
|
||||||
|
date_format = _download_setting("download_date_format")
|
||||||
|
values = filename_template.prepare(meta, book_id, order="strictly_alphabetic", date_format=date_format)
|
||||||
name = filename_template.file_name(_download_setting("download_template"), values)
|
name = filename_template.file_name(_download_setting("download_template"), values)
|
||||||
except filename_template.TemplateError as e:
|
except filename_template.TemplateError as e:
|
||||||
logger.warning(f"The download template fails for {story['url']}: {e}")
|
logger.warning(f"The download template fails for {story['url']}: {e}")
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export function settingsPage(main, _params, config) {
|
|||||||
{ class: 'hint' },
|
{ class: 'hint' },
|
||||||
'This template gives the file name of a book that you download from a story page. To go back to the ' +
|
'This template gives the file name of a book that you download from a story page. To go back to the ' +
|
||||||
'default, ',
|
'default, ',
|
||||||
code('{title} - {authors}'),
|
code('{title_sort} - {authors}'),
|
||||||
', clear the field and save.',
|
', clear the field and save.',
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
@@ -183,7 +183,7 @@ export function settingsPage(main, _params, config) {
|
|||||||
' for file names. Bindery replaces each field in braces with the value of the book, and adds .epub. ' +
|
' for file names. Bindery replaces each field in braces with the value of the book, and adds .epub. ' +
|
||||||
'A field with no value becomes empty text. For example, ',
|
'A field with no value becomes empty text. For example, ',
|
||||||
code('{series:|| - }{series_index:0>2s|| - }{title} - {authors}'),
|
code('{series:|| - }{series_index:0>2s|| - }{title} - {authors}'),
|
||||||
' gives "Saga, The - 02 - Book, The - Ann Writer.epub".',
|
' gives "The Saga - 02 - The Book - Ann Writer.epub".',
|
||||||
),
|
),
|
||||||
h('p', { class: 'hint' }, 'You can use these fields:'),
|
h('p', { class: 'hint' }, 'You can use these fields:'),
|
||||||
h(
|
h(
|
||||||
@@ -208,8 +208,9 @@ export function settingsPage(main, _params, config) {
|
|||||||
h(
|
h(
|
||||||
'li',
|
'li',
|
||||||
{},
|
{},
|
||||||
'The title and the series move a leading "The", "A" or "An" to the end, as Calibre does. ' +
|
'{title} and {series} keep a leading "The", "A" or "An". {title_sort} moves it to the end, so ' +
|
||||||
'"The Book" becomes "Book, The".',
|
'"The Book" becomes "Book, The". Calibre puts the title sort in {title} as well, unless you set its ' +
|
||||||
|
'tweak save_template_title_series_sorting to strictly_alphabetic.',
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
'li',
|
'li',
|
||||||
|
|||||||
+1
-1
@@ -472,7 +472,7 @@ def test_settings(client, monkeypatch, tmp_path):
|
|||||||
"pause_uploads": False,
|
"pause_uploads": False,
|
||||||
"ingest_available": True,
|
"ingest_available": True,
|
||||||
"personal_ini": "[defaults]\nis_adult:true\n",
|
"personal_ini": "[defaults]\nis_adult:true\n",
|
||||||
"download_template": "{title} - {authors}",
|
"download_template": "{title_sort} - {authors}",
|
||||||
"download_date_format": "%b, %Y",
|
"download_date_format": "%b, %Y",
|
||||||
"story_jobs_hours": 36,
|
"story_jobs_hours": 36,
|
||||||
"story_jobs_count": 10,
|
"story_jobs_count": 10,
|
||||||
|
|||||||
@@ -160,9 +160,13 @@ def test_download_template_names_the_file(client, story):
|
|||||||
assert client.get("/api/config").json["download_fields"]["id"].startswith("The CWA book ID")
|
assert client.get("/api/config").json["download_fields"]["id"].startswith("The CWA book ID")
|
||||||
saved = client.put("/api/settings", json={"download_template": " {series:|| - }{title} - {id} "}, headers=H)
|
saved = client.put("/api/settings", json={"download_template": " {series:|| - }{title} - {id} "}, headers=H)
|
||||||
assert saved.json["download_template"] == "{series:|| - }{title} - {id}"
|
assert saved.json["download_template"] == "{series:|| - }{title} - {id}"
|
||||||
assert f'filename="Saga, The - Book, The - {sid}.epub"' in download_name(client, sid)
|
assert f'filename="The Saga - The Book - {sid}.epub"' in download_name(client, sid)
|
||||||
|
# The default moves the article to the end, as Calibre's default does.
|
||||||
|
client.put("/api/settings", json={"download_template": ""}, headers=H)
|
||||||
|
assert 'filename="Book, The - Ann Writer.epub"' in download_name(client, sid)
|
||||||
# The ID is that of the CWA book, when the story has one.
|
# The ID is that of the CWA book, when the story has one.
|
||||||
o.store.update_story(sid, calibre_id=77, pinned={"title": "Caf\u00e9", "series": ["", None]})
|
o.store.update_story(sid, calibre_id=77, pinned={"title": "Caf\u00e9", "series": ["", None]})
|
||||||
|
client.put("/api/settings", json={"download_template": "{series:|| - }{title} - {id}"}, headers=H)
|
||||||
name = download_name(client, sid)
|
name = download_name(client, sid)
|
||||||
assert 'filename="Cafe - 77.epub"' in name and "filename*=UTF-8''Caf%C3%A9%20-%2077.epub" in name
|
assert 'filename="Cafe - 77.epub"' in name and "filename*=UTF-8''Caf%C3%A9%20-%2077.epub" in name
|
||||||
# A template with folders gives the name of the file alone.
|
# A template with folders gives the name of the file alone.
|
||||||
@@ -170,7 +174,7 @@ def test_download_template_names_the_file(client, story):
|
|||||||
assert "filename*=UTF-8''Caf%C3%A9.epub" in download_name(client, sid)
|
assert "filename*=UTF-8''Caf%C3%A9.epub" in download_name(client, sid)
|
||||||
# An empty template restores the default.
|
# An empty template restores the default.
|
||||||
saved = client.put("/api/settings", json={"download_template": " "}, headers=H).json
|
saved = client.put("/api/settings", json={"download_template": " "}, headers=H).json
|
||||||
assert saved["download_template"] == "{title} - {authors}"
|
assert saved["download_template"] == "{title_sort} - {authors}"
|
||||||
assert "filename*=UTF-8''Caf%C3%A9%20-%20Ann%20Writer.epub" in download_name(client, sid)
|
assert "filename*=UTF-8''Caf%C3%A9%20-%20Ann%20Writer.epub" in download_name(client, sid)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user