summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-24 20:55:52 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-24 20:55:52 +0200
commit75d1f8b93dfa0bffda3a59a6d143b06dcc3ca67f (patch)
tree7c4662b8a0e7d0d591f9b9a7e1bc9a83beb578f8
parent6c56877675958e40ae526e0965266ab68aae44f2 (diff)
downloadmeshbay-75d1f8b93dfa0bffda3a59a6d143b06dcc3ca67f.tar.gz
fix(node): skip indexing audio files under 50KB, likely-corrupt source
The "P.H. Theme" failure investigated earlier turned out to be a genuinely corrupt 1256-byte source file with no audio stream at all, just an ID3 tag — a real, if rare, corruption pattern worth guarding against directly rather than only handling gracefully at playback time. Scoped to audio only, applied wherever a file actually gets hashed/typed (fresh scan and the cache-miss rehash path alike) — a tiny file of any other type is still indexed normally.
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/indexer.py17
-rw-r--r--packages/meshbay-node/tests/test_indexer.py37
-rw-r--r--packages/meshbay-node/tests/test_root_availability.py6
3 files changed, 58 insertions, 2 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py
index 3ca34f6..10981c0 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py
@@ -79,6 +79,19 @@ def _is_indexable(path: Path) -> bool:
)
+# Found live: a 1256-byte ".mp3" with no audio stream at all, just an ID3
+# tag — a truncated/corrupted rip, sitting between two good tracks of the
+# same album (docs/musicbay.md). A source this small claiming to be audio
+# is far more likely broken than real, so it is skipped before ever being
+# hashed rather than indexed and left to fail at playback time. Scoped to
+# audio only — a tiny real file of any other type is still worth indexing.
+MIN_AUDIO_SIZE_BYTES = 50 * 1024
+
+
+def _is_indexable_size(path: Path, size: int) -> bool:
+ return not (_detect_type(path) == "audio" and size < MIN_AUDIO_SIZE_BYTES)
+
+
_HASH_CHUNK = 8 * 1024 * 1024 # 8 MB streaming hash chunks
@@ -134,6 +147,8 @@ def _scan_file(root: Root, file_path: Path) -> IndexEntry | None:
return None
try:
stat = file_path.stat()
+ if not _is_indexable_size(file_path, stat.st_size):
+ return None
hasher = blake3.blake3()
# long_path is a no-op off Windows; there it is what lets a deep media
# library past MAX_PATH.
@@ -338,6 +353,8 @@ class DirectoryIndexer:
st = file_path.stat()
except OSError:
return None
+ if not _is_indexable_size(file_path, st.st_size):
+ return None
if self._cache is not None:
cached = await self._cache.lookup(str(file_path), st.st_size, st.st_mtime)
diff --git a/packages/meshbay-node/tests/test_indexer.py b/packages/meshbay-node/tests/test_indexer.py
index 56571a4..4bae620 100644
--- a/packages/meshbay-node/tests/test_indexer.py
+++ b/packages/meshbay-node/tests/test_indexer.py
@@ -28,7 +28,11 @@ def shared_dir(tmp_path):
d = tmp_path / "shared"
d.mkdir()
(d / "video.mkv").write_bytes(os.urandom(1024))
- (d / "music.mp3").write_bytes(os.urandom(512))
+ # Above MIN_AUDIO_SIZE_BYTES: these tests use this file as a generic
+ # "here's an audio entry" stand-in, not as a test of the tiny-file gate
+ # itself (see test_is_indexable_size_* / test_initial_scan_skips_a_
+ # corrupt_tiny_audio_file below for that).
+ (d / "music.mp3").write_bytes(os.urandom(indexer_mod.MIN_AUDIO_SIZE_BYTES + 1024))
(d / "readme.md").write_bytes(b"# Hello MeshBay")
subdir = d / "docs"
subdir.mkdir()
@@ -157,6 +161,37 @@ def test_type_detection_covers_wma_and_musepack():
assert indexer_mod._detect_type(Path("track.mpc")) == "audio"
+def test_is_indexable_size_rejects_tiny_audio(tmp_path):
+ assert indexer_mod._is_indexable_size(Path("track.mp3"), 1256) is False
+ assert indexer_mod._is_indexable_size(
+ Path("track.mp3"), indexer_mod.MIN_AUDIO_SIZE_BYTES) is True
+ assert indexer_mod._is_indexable_size(
+ Path("track.mp3"), indexer_mod.MIN_AUDIO_SIZE_BYTES - 1) is False
+
+
+def test_is_indexable_size_does_not_apply_to_other_types():
+ """A tiny document/image is still worth indexing — the rule exists
+ because a truncated *audio* file is a known corruption signature, not
+ because small files in general are suspect."""
+ assert indexer_mod._is_indexable_size(Path("readme.txt"), 10) is True
+ assert indexer_mod._is_indexable_size(Path("icon.png"), 10) is True
+
+
+@pytest.mark.asyncio
+async def test_initial_scan_skips_a_corrupt_tiny_audio_file(tmp_path, sk_node, gek):
+ d = tmp_path / "shared"
+ d.mkdir()
+ (d / "good.mp3").write_bytes(os.urandom(indexer_mod.MIN_AUDIO_SIZE_BYTES + 1))
+ (d / "corrupt.mp3").write_bytes(os.urandom(1256))
+
+ indexer = DirectoryIndexer(roots=one_root(d), group_id="g", sk_node=sk_node, gek=gek)
+ await indexer.initial_scan()
+
+ names = {e.name for e in indexer.index.entries}
+ assert "good.mp3" in names
+ assert "corrupt.mp3" not in names
+
+
@pytest.mark.asyncio
async def test_hidden_files_excluded(tmp_path, sk_node, gek):
d = tmp_path / "dir"
diff --git a/packages/meshbay-node/tests/test_root_availability.py b/packages/meshbay-node/tests/test_root_availability.py
index 028c308..0201c1f 100644
--- a/packages/meshbay-node/tests/test_root_availability.py
+++ b/packages/meshbay-node/tests/test_root_availability.py
@@ -14,6 +14,7 @@ straightforward implementation does.
"""
import asyncio
+import os
from pathlib import Path
import pytest
@@ -90,7 +91,10 @@ async def test_one_root_going_away_leaves_the_others_alone(tmp_path):
films.mkdir()
music.mkdir()
(films / "a.mkv").write_bytes(b"a")
- (music / "b.mp3").write_bytes(b"b")
+ # Above the tiny-audio-file cutoff (indexer.py's MIN_AUDIO_SIZE_BYTES) —
+ # this test is about root availability, not that gate, so the content
+ # just needs to actually get indexed as an entry.
+ (music / "b.mp3").write_bytes(os.urandom(60 * 1024))
idx = await _indexer(_roots(films, music))
assert _names(idx) == {"a.mkv", "b.mp3"}