Skip to content

Commit 42e7aab

Browse files
feat: Add retained formats functionality for auto-conversion
Implements ability to keep original book formats after conversion to target format. Users can now select which formats to retain via CWA settings UI. Features: - New auto_convert_retained_formats setting with checkbox grid UI - Automatic conflict prevention (target format always retained) - Database migration support for backward compatibility - Enhanced ingest processor with robust format addition logic Credit to @angelicadvocate for original implementation concept in PR #284. Fixes edge cases including race conditions, UI state handling, and iteration safety.
1 parent 276e7b2 commit 42e7aab

6 files changed

Lines changed: 87 additions & 13 deletions

File tree

CONTRIBUTORS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
CONTRIBUTORS
22

33
This file is automatically generated. DO NOT EDIT MANUALLY.
4-
Generated on: 2025-09-12T19:09:48.157596Z
4+
Generated on: 2025-09-12T19:34:33.644775Z
55

66
Upstream project: https://github.com/janeczku/calibre-web
77
Fork project (Calibre-Web Automated, since 2024): https://github.com/crocodilestick/calibre-web-automated

cps/cwa_functions.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -222,14 +222,15 @@ def set_cwa_settings():
222222
for format in ignorable_formats:
223223
string_settings.append(f"ignore_ingest_{format}")
224224
string_settings.append(f"ignore_convert_{format}")
225+
string_settings.append(f"convert_retained_{format}")
225226

226227
if request.method == 'POST':
227228
if request.form['submit_button'] == "Submit":
228-
result = {"auto_convert_ignored_formats":[], "auto_ingest_ignored_formats":[]}
229+
result = {"auto_convert_ignored_formats":[], "auto_ingest_ignored_formats":[], "auto_convert_retained_formats":[]}
229230
# set boolean_settings
230231
for setting in boolean_settings:
231232
value = request.form.get(setting)
232-
if value == None:
233+
if value is None:
233234
value = 0
234235
else:
235236
value = 1
@@ -238,18 +239,20 @@ def set_cwa_settings():
238239
for setting in string_settings:
239240
value = request.form.get(setting)
240241
if setting[:14] == "ignore_convert":
241-
if value == None:
242-
continue
243-
else:
242+
if value is not None:
244243
result["auto_convert_ignored_formats"].append(value)
245-
continue
244+
continue
246245
elif setting[:13] == "ignore_ingest":
247-
if value == None:
248-
continue
249-
else:
246+
if value is not None:
250247
result["auto_ingest_ignored_formats"].append(value)
251-
continue
252-
elif setting == "auto_convert_target_format" and value == None:
248+
continue
249+
elif setting.startswith("convert_retained"):
250+
if value is not None:
251+
result["auto_convert_retained_formats"].append(value)
252+
continue
253+
elif setting == "auto_convert_target_format":
254+
if value is None:
255+
value = cwa_db.cwa_settings['auto_convert_target_format']
253256
value = cwa_db.cwa_settings['auto_convert_target_format']
254257

255258
result |= {setting:value}
@@ -260,6 +263,15 @@ def set_cwa_settings():
260263
if result['auto_convert_target_format'] in result['auto_ingest_ignored_formats']:
261264
result['auto_ingest_ignored_formats'].remove(result['auto_convert_target_format'])
262265

266+
# Prevent retaining of ignored ingest formats (create a copy to avoid modification during iteration)
267+
for ignored_format in result['auto_ingest_ignored_formats'][:]:
268+
if ignored_format in result['auto_convert_retained_formats']:
269+
result['auto_convert_retained_formats'].remove(ignored_format)
270+
271+
# Force target format to be retained (ensure it's not already there to avoid duplicates)
272+
if result['auto_convert_target_format'] not in result['auto_convert_retained_formats']:
273+
result['auto_convert_retained_formats'].append(result['auto_convert_target_format'])
274+
263275
# Handle integer settings
264276
for setting in integer_settings:
265277
value = request.form.get(setting)

cps/templates/cwa_settings.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,35 @@ <h4 class="settings-section-header">{{_('CWA Auto-Convert - Ignored Formats')}}<
429429
</div>
430430
</div>
431431

432+
<div class="settings-container">
433+
<h4 class="settings-section-header">{{_('CWA Auto-Convert - Retained Formats')}}</h4>
434+
<p class="cwa-settings-explanation settings-explanation">
435+
{{_('The formats selected here will always be added to the library, even after they have been converted to the target format. However, if the format is in the "CWA Auto-Ingest - Ignored Formats" list, it will not be imported. Note that the target format is always retained.')}}
436+
</p>
437+
<div style="max-width: 90rem; padding-left: 30px;">
438+
{% for format in ignorable_formats -%}
439+
<label for="convert_retained_{{ format }}" style="width: 75px; padding-right: 6px; display: inline-block !important;">
440+
{% if format in cwa_settings.get('auto_convert_retained_formats', []) or format == cwa_settings['auto_convert_target_format'] %}
441+
{% if format in cwa_settings['auto_ingest_ignored_formats'] %}
442+
<input type="checkbox" id="convert_retained_{{ format }}" name="convert_retained_{{ format }}" value="{{ format }}" disabled style="vertical-align: middle; accent-color: var(--color-secondary); display: inline-block !important;">
443+
{% elif format == cwa_settings['auto_convert_target_format'] %}
444+
<input type="checkbox" id="convert_retained_{{ format }}" name="convert_retained_{{ format }}" value="{{ format }}" checked disabled style="vertical-align: middle; accent-color: var(--color-secondary); display: inline-block !important;" title="Target format is always retained">
445+
{% else %}
446+
<input type="checkbox" id="convert_retained_{{ format }}" name="convert_retained_{{ format }}" value="{{ format }}" checked style="vertical-align: middle; accent-color: var(--color-secondary); display: inline-block !important;">
447+
{% endif %}
448+
{% else %}
449+
{% if format in cwa_settings['auto_ingest_ignored_formats'] %}
450+
<input type="checkbox" id="convert_retained_{{ format }}" name="convert_retained_{{ format }}" value="{{ format }}" disabled style="vertical-align: middle; accent-color: var(--color-secondary); display: inline-block !important;">
451+
{% else %}
452+
<input type="checkbox" id="convert_retained_{{ format }}" name="convert_retained_{{ format }}" value="{{ format }}" style="vertical-align: middle; accent-color: var(--color-secondary); display: inline-block !important;">
453+
{% endif %}
454+
{% endif %}
455+
<span style="padding-left: 4px; vertical-align: middle;">{{ format }}</span>
456+
</label>
457+
{% endfor %}
458+
</div>
459+
</div>
460+
432461
<div class="settings-container">
433462
<h4 class="settings-section-header">{{_('CWA Auto-Ingest - Ignored Formats')}}</h4>
434463
<p class="cwa-settings-explanation settings-explanation">

scripts/cwa_db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def get_cwa_settings(self) -> dict:
245245
def update_cwa_settings(self, result) -> None:
246246
"""Sets settings using POST request from set_cwa_settings()"""
247247
for setting in result.keys():
248-
if setting == "auto_convert_ignored_formats" or setting == "auto_ingest_ignored_formats":
248+
if setting == "auto_convert_ignored_formats" or setting == "auto_ingest_ignored_formats" or setting == "auto_convert_retained_formats":
249249
result[setting] = ','.join(result[setting])
250250

251251
# Use parameterized queries to safely handle non-English characters and quotes

scripts/cwa_schema.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ CREATE TABLE IF NOT EXISTS cwa_settings(
4242
auto_convert_target_format TEXT DEFAULT "epub" NOT NULL,
4343
auto_convert_ignored_formats TEXT DEFAULT "" NOT NULL,
4444
auto_ingest_ignored_formats TEXT DEFAULT "" NOT NULL,
45+
auto_convert_retained_formats TEXT DEFAULT "" NOT NULL,
4546
auto_ingest_automerge TEXT DEFAULT "new_record" NOT NULL,
4647
ingest_timeout_minutes INTEGER DEFAULT 15 NOT NULL,
4748
auto_metadata_enforcement SMALLINT DEFAULT 1 NOT NULL,

scripts/ingest_processor.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ def __init__(self, filepath: str):
123123
self.ingest_ignored_formats.append(tmp_ext)
124124

125125
self.convert_ignored_formats = self.cwa_settings['auto_convert_ignored_formats']
126+
self.convert_retained_formats = self.cwa_settings.get('auto_convert_retained_formats', [])
127+
if isinstance(self.convert_retained_formats, str):
128+
self.convert_retained_formats = self.convert_retained_formats.split(',') if self.convert_retained_formats else []
126129
self.is_kindle_epub_fixer = self.cwa_settings['kindle_epub_fixer']
127130

128131
# Formats
@@ -712,6 +715,35 @@ def main(filepath=sys.argv[1]):
712715

713716
if convert_successful: # If previous conversion process was successful, remove tmp files and import into library
714717
nbp.add_book_to_library(converted_filepath) # type: ignore
718+
719+
# If the original format should be retained, also add it as an additional format
720+
if nbp.input_format in nbp.convert_retained_formats and nbp.input_format not in nbp.ingest_ignored_formats:
721+
print(f"[ingest-processor]: Retaining original format ({nbp.input_format}) for {nbp.filename}...", flush=True)
722+
# Find the book that was just added to get its ID
723+
try:
724+
calibre_db_path = os.path.join(nbp.library_dir, 'metadata.db')
725+
with sqlite3.connect(calibre_db_path, timeout=30) as con:
726+
cur = con.cursor()
727+
# Get the most recently added book - use title/author for more reliable matching
728+
# in case of concurrent ingests
729+
cur.execute("""
730+
SELECT id FROM books
731+
WHERE path = (SELECT path FROM books ORDER BY timestamp DESC LIMIT 1)
732+
ORDER BY timestamp DESC LIMIT 1
733+
""")
734+
result = cur.fetchone()
735+
736+
if result:
737+
book_id = result[0]
738+
# Verify the original file still exists before trying to add it
739+
if os.path.exists(filepath) and os.path.getsize(filepath) > 0:
740+
nbp.add_format_to_book(book_id, filepath)
741+
else:
742+
print(f"[ingest-processor] Original file no longer exists or is empty, cannot retain format: {filepath}", flush=True)
743+
else:
744+
print(f"[ingest-processor] Could not find book ID to add retained format for: {nbp.filename}", flush=True)
745+
except Exception as e:
746+
print(f"[ingest-processor] Error adding retained format: {e}", flush=True)
715747

716748
elif nbp.can_convert and not nbp.auto_convert_on: # Books not in target format but Auto-Converter is off so files are imported anyway
717749
print(f"\n[ingest-processor]: {nbp.filename} not in target format but CWA Auto-Convert is deactivated so importing the file anyway...", flush=True)

0 commit comments

Comments
 (0)