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.
This commit is contained in:
Chris Lu
2026-08-10 19:57:24 -07:00
parent bfe4b810bf
commit 56468c83e4
4 changed files with 28 additions and 39 deletions
+6
View File
@@ -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
}
-9
View File
@@ -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 {
-11
View File
@@ -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")
}
}
+22 -19
View File
@@ -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)