vomebook commited on
Commit
130ee5c
·
1 Parent(s): 0d0f8a0

fix(reader): remove the dead converted-PDF fallback for chapter books

Browse files

validFallback only accepted PDF objects in the deleted vomebook/Reader-Assets
dataset, while every current sidecar fallback is a CHM-native EPUB in the
reader-assets-v2 bucket, so the redirect to ext=pdf never ran. Remove
ReaderFallback from assetFields and from the backend record and reader-resolve
fields, the fallback reader parameter, validFallback and the chapter-load
redirect; a load failure keeps showing the classified error.

backend/record_metadata.py CHANGED
@@ -120,17 +120,14 @@ def reader_asset_fields(asset: dict, original_extension: str) -> dict:
120
  if asset.get("b") == "vomebook/pdf-pages-v2" or asset.get("ob") == "vomebook/pdf-pages-v2" else f"{READER_ASSETS_BASE}{ocr_path}"
121
  )
122
  fields["ocr_mode"] = str(asset.get("om") or "")
123
- for key, field in (("c", "chapter_manifest"), ("f", "fallback")):
124
- if asset.get(key):
125
- if key == "c" and asset.get("cb") in {"vomebook/pdf-pages-v2", "vomebook/reader-assets-v2"}:
126
- if asset.get("cb") == "vomebook/reader-assets-v2":
127
- fields[field] = ("https://voiceofml-search.hf.space/api/reader-bucket-resource?path="
128
- + quote(asset[key], safe="/"))
129
- else:
130
- fields[field] = ("/api/reader-bucket-resource?path="
131
- + quote(asset[key], safe="/"))
132
- else:
133
- fields[field] = f"{READER_ASSETS_BASE}{asset[key]}"
134
  return fields
135
 
136
 
@@ -149,7 +146,7 @@ def enrich_reader_record(record: dict, asset: dict) -> dict:
149
  "extension": direct_modes[direct_extension][0]}
150
  if fields:
151
  for source, target in (("url", "ReaderLink"), ("extension", "ReaderExtension"),
152
- ("chapter_manifest", "ReaderChapterManifest"), ("fallback", "ReaderFallback"),
153
  ("ocr_manifest", "ReaderOcrManifest"), ("ocr_mode", "ReaderOcrMode"),
154
  ("pdf_document", "ReaderPdfDocument")):
155
  if source in fields:
 
120
  if asset.get("b") == "vomebook/pdf-pages-v2" or asset.get("ob") == "vomebook/pdf-pages-v2" else f"{READER_ASSETS_BASE}{ocr_path}"
121
  )
122
  fields["ocr_mode"] = str(asset.get("om") or "")
123
+ if asset.get("c"):
124
+ if asset.get("cb") == "vomebook/reader-assets-v2":
125
+ fields["chapter_manifest"] = ("https://voiceofml-search.hf.space/api/reader-bucket-resource?path="
126
+ + quote(asset["c"], safe="/"))
127
+ elif asset.get("cb") == "vomebook/pdf-pages-v2":
128
+ fields["chapter_manifest"] = "/api/reader-bucket-resource?path=" + quote(asset["c"], safe="/")
129
+ else:
130
+ fields["chapter_manifest"] = f"{READER_ASSETS_BASE}{asset['c']}"
 
 
 
131
  return fields
132
 
133
 
 
146
  "extension": direct_modes[direct_extension][0]}
147
  if fields:
148
  for source, target in (("url", "ReaderLink"), ("extension", "ReaderExtension"),
149
+ ("chapter_manifest", "ReaderChapterManifest"),
150
  ("ocr_manifest", "ReaderOcrManifest"), ("ocr_mode", "ReaderOcrMode"),
151
  ("pdf_document", "ReaderPdfDocument")):
152
  if source in fields:
static/app.js CHANGED
@@ -2417,7 +2417,7 @@ function getReaderLink(rec, returnUrl = location.href, cacheMetadata = true) {
2417
  readerRecord.OcrUrl = `/txt/${encodeRecordPath(VoiceOfMLReader.txtRelativePath(relPath))}`;
2418
  }
2419
  const readerUrl = VoiceOfMLReader.readerUrl(readerRecord, "/static/reader.html");
2420
- if (cacheMetadata) try { const readerId = new URL(readerUrl, location.origin).searchParams.get("id"); if (readerId) { const sourceData = { url: readerRecord.ReaderLink || readerRecord.Link, download: readerRecord.Link, title: readerRecord.File, extension: readerRecord.ReaderExtension || readerRecord.Extension, original_extension: readerRecord.Extension, repo: String(readerRecord.Repo || "").split("/").pop(), folder: readerRecord.Folder, chapter_manifest: readerRecord.ReaderChapterManifest || "", fallback: readerRecord.ReaderFallback || "", pdf_document: readerRecord.ReaderPdfDocument || "" }; cacheReaderMetadata(readerId, sourceData, true); } } catch (_) {}
2421
  return readerUrl;
2422
  }
2423
 
 
2417
  readerRecord.OcrUrl = `/txt/${encodeRecordPath(VoiceOfMLReader.txtRelativePath(relPath))}`;
2418
  }
2419
  const readerUrl = VoiceOfMLReader.readerUrl(readerRecord, "/static/reader.html");
2420
+ if (cacheMetadata) try { const readerId = new URL(readerUrl, location.origin).searchParams.get("id"); if (readerId) { const sourceData = { url: readerRecord.ReaderLink || readerRecord.Link, download: readerRecord.Link, title: readerRecord.File, extension: readerRecord.ReaderExtension || readerRecord.Extension, original_extension: readerRecord.Extension, repo: String(readerRecord.Repo || "").split("/").pop(), folder: readerRecord.Folder, chapter_manifest: readerRecord.ReaderChapterManifest || "", pdf_document: readerRecord.ReaderPdfDocument || "" }; cacheReaderMetadata(readerId, sourceData, true); } } catch (_) {}
2421
  return readerUrl;
2422
  }
2423
 
static/reader.js CHANGED
@@ -154,7 +154,6 @@ const readerContext = {
154
  get epubSeekTarget() { return epubSeekTarget; }, set epubSeekTarget(value) { epubSeekTarget = value; },
155
  get extension() { return extension; }, set extension(value) { extension = value; },
156
  get fail() { return fail; }, set fail(value) { fail = value; },
157
- get fallbackUrl() { return fallbackUrl; }, set fallbackUrl(value) { fallbackUrl = value; },
158
  get fetchReaderResponse() { return fetchReaderResponse; }, set fetchReaderResponse(value) { fetchReaderResponse = value; },
159
  get fetchReaderUrl() { return fetchReaderUrl; }, set fetchReaderUrl(value) { fetchReaderUrl = value; },
160
  get filterPanel() { return filterPanel; }, set filterPanel(value) { filterPanel = value; },
@@ -428,7 +427,6 @@ const readerContext = {
428
  get updateSearchState() { return updateSearchState; }, set updateSearchState(value) { updateSearchState = value; },
429
  get updateTocCurrentMark() { return updateTocCurrentMark; }, set updateTocCurrentMark(value) { updateTocCurrentMark = value; },
430
  get v3ResourceUrl() { return v3ResourceUrl; }, set v3ResourceUrl(value) { v3ResourceUrl = value; },
431
- get validFallback() { return validFallback; }, set validFallback(value) { validFallback = value; },
432
  get validOcr() { return validOcr; }, set validOcr(value) { validOcr = value; },
433
  get validSource() { return validSource; }, set validSource(value) { validSource = value; },
434
  get validatePageMap() { return validatePageMap; },
@@ -572,7 +570,7 @@ let {
572
  let {
573
  readerContentUrl, readerDownloadApiUrl, readerDownloadUrl, startReaderDownload,
574
  normalizeSourceUrl, resolveReaderId, applyReaderMetadata, applyReaderPathMetadata,
575
- validSource, validOcr, validFallback, readerAssetObjectFamily,
576
  trustedReaderAssetManifestUrl, trustedChapterUrl, trustedChapterReferenceUrl, trustedChapterResourceUrl
577
  } = createReaderSource(readerContext);
578
 
@@ -795,7 +793,6 @@ const documentState = readerRuntime.state.document,
795
 
796
  readerBindings.initializeDocumentTitle();
797
 
798
- let fallbackUrl = params.get("fallback") || "";
799
  const ocrUrl = params.get("ocr") || "";
800
  let ocrManifestUrl = params.get("ocr_manifest") || "";
801
  let returnUrl = params.get("return") || "";
 
154
  get epubSeekTarget() { return epubSeekTarget; }, set epubSeekTarget(value) { epubSeekTarget = value; },
155
  get extension() { return extension; }, set extension(value) { extension = value; },
156
  get fail() { return fail; }, set fail(value) { fail = value; },
 
157
  get fetchReaderResponse() { return fetchReaderResponse; }, set fetchReaderResponse(value) { fetchReaderResponse = value; },
158
  get fetchReaderUrl() { return fetchReaderUrl; }, set fetchReaderUrl(value) { fetchReaderUrl = value; },
159
  get filterPanel() { return filterPanel; }, set filterPanel(value) { filterPanel = value; },
 
427
  get updateSearchState() { return updateSearchState; }, set updateSearchState(value) { updateSearchState = value; },
428
  get updateTocCurrentMark() { return updateTocCurrentMark; }, set updateTocCurrentMark(value) { updateTocCurrentMark = value; },
429
  get v3ResourceUrl() { return v3ResourceUrl; }, set v3ResourceUrl(value) { v3ResourceUrl = value; },
 
430
  get validOcr() { return validOcr; }, set validOcr(value) { validOcr = value; },
431
  get validSource() { return validSource; }, set validSource(value) { validSource = value; },
432
  get validatePageMap() { return validatePageMap; },
 
570
  let {
571
  readerContentUrl, readerDownloadApiUrl, readerDownloadUrl, startReaderDownload,
572
  normalizeSourceUrl, resolveReaderId, applyReaderMetadata, applyReaderPathMetadata,
573
+ validSource, validOcr, readerAssetObjectFamily,
574
  trustedReaderAssetManifestUrl, trustedChapterUrl, trustedChapterReferenceUrl, trustedChapterResourceUrl
575
  } = createReaderSource(readerContext);
576
 
 
793
 
794
  readerBindings.initializeDocumentTitle();
795
 
 
796
  const ocrUrl = params.get("ocr") || "";
797
  let ocrManifestUrl = params.get("ocr_manifest") || "";
798
  let returnUrl = params.get("return") || "";
static/reader/core/reader-contract.js CHANGED
@@ -432,7 +432,6 @@
432
  ? `${bucketBase}?path=${encodeURIComponent(asset.c)}`
433
  : assetBase + asset.c
434
  : "",
435
- ReaderFallback: asset.f ? assetBase + asset.f : "",
436
  ReaderOcrManifest: ocrUrl || (ocrPath.endsWith("/ocr-manifest.json") ? assetBase + ocrPath : ""),
437
  ReaderOcrMode: String(asset.om || ""),
438
  ReaderPdfDocument: asset.pd && ["vomebook/reader-assets-v2", "vomebook/pdf-pages-v2"].includes(asset.pdb) &&
@@ -466,8 +465,6 @@
466
  params.set("ocr", record.OcrUrl || record.ocrUrl);
467
  if (!shortId && (record.ReaderOcrManifest || record.readerOcrManifest))
468
  params.set("ocr_manifest", record.ReaderOcrManifest || record.readerOcrManifest);
469
- if (!shortId && (record.ReaderFallback || record.readerFallback))
470
- params.set("fallback", record.ReaderFallback || record.readerFallback);
471
  if (!shortId && record.ReaderPdfDocument) params.set("pdf_document", record.ReaderPdfDocument);
472
  if (
473
  !shortId &&
 
432
  ? `${bucketBase}?path=${encodeURIComponent(asset.c)}`
433
  : assetBase + asset.c
434
  : "",
 
435
  ReaderOcrManifest: ocrUrl || (ocrPath.endsWith("/ocr-manifest.json") ? assetBase + ocrPath : ""),
436
  ReaderOcrMode: String(asset.om || ""),
437
  ReaderPdfDocument: asset.pd && ["vomebook/reader-assets-v2", "vomebook/pdf-pages-v2"].includes(asset.pdb) &&
 
465
  params.set("ocr", record.OcrUrl || record.ocrUrl);
466
  if (!shortId && (record.ReaderOcrManifest || record.readerOcrManifest))
467
  params.set("ocr_manifest", record.ReaderOcrManifest || record.readerOcrManifest);
 
 
468
  if (!shortId && record.ReaderPdfDocument) params.set("pdf_document", record.ReaderPdfDocument);
469
  if (
470
  !shortId &&
static/reader/core/reader-source.mjs CHANGED
@@ -180,21 +180,6 @@ export function createReaderSource(readerContext) {
180
  }
181
  }
182
 
183
- function validFallback(raw) {
184
- try {
185
- const url = new URL(raw);
186
- return (
187
- url.protocol === "https:" &&
188
- ["huggingface.co", "hf-mirror.com"].includes(url.hostname) &&
189
- /\/datasets\/vomebook\/Reader-Assets\/resolve\/[^/]+\/objects\/[0-9a-f]{2}\/[0-9a-f]{64}\/(?:linearized\.pdf|(?:[a-z0-9-]+\/)?document\.pdf)$/.test(
190
- url.pathname
191
- )
192
- );
193
- } catch (_) {
194
- return false;
195
- }
196
- }
197
-
198
  function readerAssetObjectFamily(raw, base = location.href) {
199
  try {
200
  const url = new URL(raw, base);
@@ -330,7 +315,6 @@ export function createReaderSource(readerContext) {
330
  applyReaderPathMetadata,
331
  validSource,
332
  validOcr,
333
- validFallback,
334
  readerAssetObjectFamily,
335
  trustedReaderAssetManifestUrl,
336
  trustedChapterUrl,
 
180
  }
181
  }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  function readerAssetObjectFamily(raw, base = location.href) {
184
  try {
185
  const url = new URL(raw, base);
 
315
  applyReaderPathMetadata,
316
  validSource,
317
  validOcr,
 
318
  readerAssetObjectFamily,
319
  trustedReaderAssetManifestUrl,
320
  trustedChapterUrl,
static/reader/formats/reader-format-controller.mjs CHANGED
@@ -116,7 +116,6 @@ export function createReaderFormatController(readerContext) {
116
  readerContext.contentUrl = readerContext.readerContentUrl(readerContext.sourceUrl);
117
  readerContext.downloadUrl = resolved.download || readerContext.sourceUrl;
118
  readerContext.chapterManifestUrl = resolved.chapter_manifest || "";
119
- readerContext.fallbackUrl = resolved.fallback || "";
120
  if (resolved.extension) readerContext.extension = String(resolved.extension).toLowerCase();
121
  if (
122
  readerContext.extension === "pdf" &&
@@ -231,21 +230,6 @@ export function createReaderFormatController(readerContext) {
231
  } catch (error) {
232
  if (readerContext.readerAbortController.signal.aborted || error?.name === "AbortError") return;
233
  console.error(error);
234
- if (
235
- error?.code !== "READER_RESTORE" &&
236
- readerContext.capability.mode === "epub-chapters" &&
237
- readerContext.validFallback(readerContext.fallbackUrl)
238
- ) {
239
- const target = new URL(location.href);
240
- target.searchParams.set("url", readerContext.fallbackUrl);
241
- target.searchParams.set("ext", "pdf");
242
- target.searchParams.delete("chapter_manifest");
243
- target.searchParams.delete("fallback");
244
- if (readerContext.isReaderOverlayFrame())
245
- window.parent.postMessage({ type: "voice-reader-open", url: target.href }, location.origin);
246
- else location.replace(target.href);
247
- return;
248
- }
249
  readerContext.fail(
250
  error && error.message ? error.message : "原文件加载失败",
251
  readerContext.classifyReaderError(error, readerContext.capability.mode === "foliate" ? "READER_CORRUPT" : "READER_PARSE")
 
116
  readerContext.contentUrl = readerContext.readerContentUrl(readerContext.sourceUrl);
117
  readerContext.downloadUrl = resolved.download || readerContext.sourceUrl;
118
  readerContext.chapterManifestUrl = resolved.chapter_manifest || "";
 
119
  if (resolved.extension) readerContext.extension = String(resolved.extension).toLowerCase();
120
  if (
121
  readerContext.extension === "pdf" &&
 
230
  } catch (error) {
231
  if (readerContext.readerAbortController.signal.aborted || error?.name === "AbortError") return;
232
  console.error(error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  readerContext.fail(
234
  error && error.message ? error.message : "原文件加载失败",
235
  readerContext.classifyReaderError(error, readerContext.capability.mode === "foliate" ? "READER_CORRUPT" : "READER_PARSE")
tests/test_fastapi_endpoints.py CHANGED
@@ -601,7 +601,7 @@ class FastApiEndpointTests(unittest.TestCase):
601
  self.assertEqual(response.status_code, 200)
602
  self.assertEqual(result["ReaderExtension"], "epub-chapters")
603
  self.assertTrue(result["ReaderChapterManifest"].endswith(chapter_manifest))
604
- self.assertTrue(result["ReaderFallback"].endswith(fallback))
605
  self.assertEqual(result["DownloadLink"], original_link)
606
 
607
  def test_reader_resolve_exposes_chapter_metadata_for_id_reader(self):
@@ -620,7 +620,7 @@ class FastApiEndpointTests(unittest.TestCase):
620
  result = response.json()
621
  self.assertEqual(result["extension"], "epub-chapters")
622
  self.assertTrue(result["chapter_manifest"].endswith(chapter_manifest))
623
- self.assertTrue(result["fallback"].endswith(fallback))
624
 
625
  def test_reader_static_assets_are_served(self):
626
  for path in ("/static/reader.html", "/static/reader.js", "/static/reader.css", "/static/reader/core/reader-contract.js"):
 
601
  self.assertEqual(response.status_code, 200)
602
  self.assertEqual(result["ReaderExtension"], "epub-chapters")
603
  self.assertTrue(result["ReaderChapterManifest"].endswith(chapter_manifest))
604
+ self.assertNotIn("ReaderFallback", result)
605
  self.assertEqual(result["DownloadLink"], original_link)
606
 
607
  def test_reader_resolve_exposes_chapter_metadata_for_id_reader(self):
 
620
  result = response.json()
621
  self.assertEqual(result["extension"], "epub-chapters")
622
  self.assertTrue(result["chapter_manifest"].endswith(chapter_manifest))
623
+ self.assertNotIn("fallback", result)
624
 
625
  def test_reader_static_assets_are_served(self):
626
  for path in ("/static/reader.html", "/static/reader.js", "/static/reader.css", "/static/reader/core/reader-contract.js"):
tests/test_reader_backend.py CHANGED
@@ -832,7 +832,8 @@ class ReaderBackendTests(unittest.IsolatedAsyncioTestCase):
832
  self.assertEqual(resolved["extension"], "pdf-pages")
833
  self.assertEqual(resolved["extension"], enriched["ReaderExtension"])
834
  self.assertEqual(resolved["chapter_manifest"], enriched["ReaderChapterManifest"])
835
- self.assertEqual(resolved["fallback"], enriched["ReaderFallback"])
 
836
 
837
 
838
  if __name__ == "__main__":
 
832
  self.assertEqual(resolved["extension"], "pdf-pages")
833
  self.assertEqual(resolved["extension"], enriched["ReaderExtension"])
834
  self.assertEqual(resolved["chapter_manifest"], enriched["ReaderChapterManifest"])
835
+ self.assertNotIn("fallback", resolved)
836
+ self.assertNotIn("ReaderFallback", enriched)
837
 
838
 
839
  if __name__ == "__main__":
tests/test_reader_regression.js CHANGED
@@ -138,7 +138,7 @@ const convertedParams = new URL(`https://example.test${convertedUrl}`).searchPar
138
  assert.strictEqual(convertedParams.get('ext'), 'epub')
139
  assert.strictEqual(convertedParams.get('title'), '原始书名.mobi')
140
  assert.strictEqual(convertedParams.get('download'), 'https://download.example.test/books/book.mobi')
141
- assert.strictEqual(convertedParams.get('fallback'), 'https://huggingface.co/datasets/vomebook/Reader-Assets/resolve/main/fallback.pdf')
142
  assert.strictEqual(convertedParams.get('chapter_manifest'), 'https://huggingface.co/datasets/vomebook/Reader-Assets/resolve/main/chapters.json')
143
  const pageAssetUrl = sandbox.self.VoiceOfMLReader.readerUrl({
144
  ReaderLink: 'https://huggingface.co/datasets/vomebook/Reader-Assets/resolve/main/objects/aa/' + 'a'.repeat(64) + '/1234567890abcdef/page-manifest.json',
 
138
  assert.strictEqual(convertedParams.get('ext'), 'epub')
139
  assert.strictEqual(convertedParams.get('title'), '原始书名.mobi')
140
  assert.strictEqual(convertedParams.get('download'), 'https://download.example.test/books/book.mobi')
141
+ assert.strictEqual(convertedParams.get('fallback'), null)
142
  assert.strictEqual(convertedParams.get('chapter_manifest'), 'https://huggingface.co/datasets/vomebook/Reader-Assets/resolve/main/chapters.json')
143
  const pageAssetUrl = sandbox.self.VoiceOfMLReader.readerUrl({
144
  ReaderLink: 'https://huggingface.co/datasets/vomebook/Reader-Assets/resolve/main/objects/aa/' + 'a'.repeat(64) + '/1234567890abcdef/page-manifest.json',