From 56468c83e4e9210cfad0a631f9431a02b5f9c71f Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 10 Aug 2026 19:57:24 -0700 Subject: [PATCH] format: make Sniff an optional capability Sniff sat in the mandatory adapter interface but has exactly one caller, the repack gate, which only Indexer adapters can reach - the hls-ts implementation was dead code. Move it to a Sniffer capability discovered by assertion like the others: parquet keeps it, hls-ts drops it, and repack skips the gate when an adapter cannot sniff. --- weed/format/format.go | 6 +++++ weed/format/hlsts/hlsts.go | 9 ------- weed/format/hlsts/hlsts_test.go | 11 -------- weed/server/filer_server_format.go | 41 ++++++++++++++++-------------- 4 files changed, 28 insertions(+), 39 deletions(-) diff --git a/weed/format/format.go b/weed/format/format.go index ccd16158a..74c917dd5 100644 --- a/weed/format/format.go +++ b/weed/format/format.go @@ -47,6 +47,12 @@ type Hint struct { // discovered by type assertion. type Format interface { Name() string +} + +// Sniffer cheaply recognizes the format from identification signals. Repack +// gates on it before parsing, and policy-driven detection will rely on it; +// ingest-only adapters, whose files carry their layout from birth, skip it. +type Sniffer interface { Sniff(h Hint) bool } diff --git a/weed/format/hlsts/hlsts.go b/weed/format/hlsts/hlsts.go index f6d3b60f9..b29eb7b5a 100644 --- a/weed/format/hlsts/hlsts.go +++ b/weed/format/hlsts/hlsts.go @@ -23,8 +23,6 @@ const ( // interior chunk cuts land on packet boundaries. TSPacketSize = 188 - tsSyncByte = 0x47 - PlaylistContentType = "application/vnd.apple.mpegurl" MediaContentType = "video/MP2T" ) @@ -37,13 +35,6 @@ type Adapter struct{} func (Adapter) Name() string { return FormatName } -func (Adapter) Sniff(h format.Hint) bool { - if len(h.Head) > TSPacketSize { - return h.Head[0] == tsSyncByte && h.Head[TSPacketSize] == tsSyncByte - } - return len(h.Head) > 0 && h.Head[0] == tsSyncByte -} - // playlistInfo is the adapter payload: what the generated playback playlist // needs beyond the extent sizes. type playlistInfo struct { diff --git a/weed/format/hlsts/hlsts_test.go b/weed/format/hlsts/hlsts_test.go index 7302863a2..4d7260a72 100644 --- a/weed/format/hlsts/hlsts_test.go +++ b/weed/format/hlsts/hlsts_test.go @@ -190,14 +190,3 @@ func TestMediaSequenceBoundaryRoundTrip(t *testing.T) { } } -func TestSniff(t *testing.T) { - head := make([]byte, 400) - head[0], head[TSPacketSize] = tsSyncByte, tsSyncByte - if !(Adapter{}).Sniff(format.Hint{Head: head}) { - t.Fatalf("Sniff() rejected TS head") - } - head[TSPacketSize] = 0 - if (Adapter{}).Sniff(format.Hint{Head: head}) { - t.Fatalf("Sniff() accepted non-TS head") - } -} diff --git a/weed/server/filer_server_format.go b/weed/server/filer_server_format.go index cfd176d59..b2350e0c8 100644 --- a/weed/server/filer_server_format.go +++ b/weed/server/filer_server_format.go @@ -373,25 +373,28 @@ func (fs *FilerServer) formatRepack(ctx context.Context, w http.ResponseWriter, // Close releases the private reader cache and its in-flight prefetches. defer readerAt.Close() - hint := format.Hint{Name: entry.Name(), ContentType: entry.Attr.Mime, Size: size} - sniffSize := int64(formatSniffBytes) - if sniffSize > size { - sniffSize = size - } - head := make([]byte, sniffSize) - if _, err := readerAt.ReadAt(head, 0); err != nil && err != io.EOF { - writeJsonError(w, r, http.StatusInternalServerError, fmt.Errorf("read head: %w", err)) - return - } - tail := make([]byte, sniffSize) - if _, err := readerAt.ReadAt(tail, size-sniffSize); err != nil && err != io.EOF { - writeJsonError(w, r, http.StatusInternalServerError, fmt.Errorf("read tail: %w", err)) - return - } - hint.Head, hint.Tail = head, tail - if !adapter.Sniff(hint) { - writeJsonError(w, r, http.StatusBadRequest, fmt.Errorf("%s does not look like %s", entry.Name(), adapterName)) - return + // cheap magic-byte gate before handing the bytes to the parser + if sniffer, ok := adapter.(format.Sniffer); ok { + hint := format.Hint{Name: entry.Name(), ContentType: entry.Attr.Mime, Size: size} + sniffSize := int64(formatSniffBytes) + if sniffSize > size { + sniffSize = size + } + head := make([]byte, sniffSize) + if _, err := readerAt.ReadAt(head, 0); err != nil && err != io.EOF { + writeJsonError(w, r, http.StatusInternalServerError, fmt.Errorf("read head: %w", err)) + return + } + tail := make([]byte, sniffSize) + if _, err := readerAt.ReadAt(tail, size-sniffSize); err != nil && err != io.EOF { + writeJsonError(w, r, http.StatusInternalServerError, fmt.Errorf("read tail: %w", err)) + return + } + hint.Head, hint.Tail = head, tail + if !sniffer.Sniff(hint) { + writeJsonError(w, r, http.StatusBadRequest, fmt.Errorf("%s does not look like %s", entry.Name(), adapterName)) + return + } } layout, err := indexer.Index(ctx, readerAt, size)