serve dlna: remove file extensions from titles to prevent Samsung TV duplication

Samsung TVs have a bug where they duplicate file extensions when both
the title contains an extension and the MIME type indicates the same
file type. For example, "photo.jpg" becomes "photo.jpg.jpg".

Remove extensions from <dc:title> while keeping them in the resource URL
and MIME type. This provides a cleaner display and prevents Samsung TVs
from incorrectly "fixing" what they perceive as missing extensions.
This commit is contained in:
Nick Craig-Wood
2026-04-24 16:27:09 +01:00
parent 8502532c22
commit 328ac017c1
2 changed files with 48 additions and 1 deletions
+3 -1
View File
@@ -80,7 +80,9 @@ func (cds *contentDirectoryService) cdsObjectToUpnpavObject(cdsObject object, fi
}
obj.Class = "object.item." + mediaType[1] + "Item"
obj.Title = fileInfo.Name()
// Remove file extension from title to prevent Samsung TVs from duplicating it
// File type is already provided via MIME type in protocolInfo
obj.Title = strings.TrimSuffix(fileInfo.Name(), filepath.Ext(fileInfo.Name()))
obj.Date = upnpav.Timestamp{Time: fileInfo.ModTime()}
item := upnpav.Item{
+45
View File
@@ -3,7 +3,9 @@ package dlna
import (
"context"
"encoding/xml"
"path/filepath"
"sort"
"strings"
"testing"
"github.com/anacrolix/dms/soap"
@@ -176,6 +178,49 @@ func TestSOAPResponseQuoteEscaping(t *testing.T) {
assert.Contains(t, resultStr, "&quot;", "SOAP arguments should contain &quot; entities")
}
func TestTitleExtensionRemoval(t *testing.T) {
// Test that file extensions are removed from titles to prevent Samsung TV duplication
tests := []struct {
name string
filename string
expected string
}{
{
name: "image file",
filename: "photo.jpg",
expected: "photo",
},
{
name: "video file",
filename: "movie.mp4",
expected: "movie",
},
{
name: "multiple dots",
filename: "file.name.with.dots.mkv",
expected: "file.name.with.dots",
},
{
name: "no extension",
filename: "filename_no_ext",
expected: "filename_no_ext",
},
{
name: "hidden file",
filename: ".hidden.txt",
expected: ".hidden",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Simulate the title processing logic
title := strings.TrimSuffix(tt.filename, filepath.Ext(tt.filename))
assert.Equal(t, tt.expected, title)
})
}
}
func TestAdjustXMLApostrophes(t *testing.T) {
tests := []struct {
name string