diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index 0fb62e9..0000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-# These are supported funding model platforms
-
-github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
-patreon: anacrolix # Replace with a single Patreon username
-open_collective: # Replace with a single Open Collective username
-ko_fi: # Replace with a single Ko-fi username
-tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
-community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
-liberapay: # Replace with a single Liberapay username
-issuehunt: # Replace with a single IssueHunt username
-otechie: # Replace with a single Otechie username
-custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..cddf13e
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+FROM golang
+
+RUN \
+  apt-get update && \
+  DEBIAN_FRONTEND=noninteractive \
+    apt-get install -y --no-install-recommends \
+      ffmpeg \
+      ffmpegthumbnailer \
+  && \
+  apt-get clean && \
+  rm -rf /var/lib/apt/lists/* && \
+  touch /root/.dms-ffprobe-cache
+
+COPY . /go/src/github.com/anacrolix/dms/
+WORKDIR /go/src/github.com/anacrolix/dms/
+RUN \
+  go build -v .
+
+ENTRYPOINT [ "./dms" ]
diff --git a/README.rst b/README.rst
index 4b18778..5036547 100644
--- a/README.rst
+++ b/README.rst
@@ -30,6 +30,17 @@ To run::
 
     $ "$GOPATH"/bin/dms
 
+Running DMS as a systemd service
+=================================
+
+A sample systemd `.service` file has been `provided <helpers/systemd/dms.service>`_ to assist in running DMS as a system service.
+
+Running DMS as a FreeBSD service
+================================
+
+Install the `provided <helpers/bsd/dms>`_ service file to /etc/rc.d or /usr/local/etc/rc.d
+add ``dms_enable="YES"``, and optionally ``dms_root="/path/to/my/media"`` and ``dms_user="myuser"`` to your /etc/rc.conf
+
 Known Compatible Players and Renderers
 ======================================
 
diff --git a/bindata.go b/bindata.go
index 8544a4f..56a4c28 100644
--- a/bindata.go
+++ b/bindata.go
@@ -182,6 +182,7 @@ type bintree struct {
 	Func     func() (*asset, error)
 	Children map[string]*bintree
 }
+
 var _bintree = &bintree{nil, map[string]*bintree{
 	"data": &bintree{nil, map[string]*bintree{
 		"VGC Sonic.png": &bintree{dataVgcSonicPng, map[string]*bintree{}},
@@ -234,4 +235,3 @@ func _filePath(dir, name string) string {
 	cannonicalName := strings.Replace(name, "\\", "/", -1)
 	return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
 }
-
diff --git a/debian/changelog b/debian/changelog
index f5518e4..6061b7f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-anacrolix-dms (1.3.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 20 May 2022 14:21:57 -0000
+
 golang-github-anacrolix-dms (1.1.0-2) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/dlna/dms/cds.go b/dlna/dms/cds.go
index aa333df..b394c85 100644
--- a/dlna/dms/cds.go
+++ b/dlna/dms/cds.go
@@ -47,7 +47,7 @@ func (me *contentDirectoryService) cdsObjectToUpnpavObject(cdsObject object, fil
 	if fileInfo.IsDir() {
 		obj.Class = "object.container.storageFolder"
 		obj.Title = fileInfo.Name()
-		ret = upnpav.Container{Object: obj}
+		ret = upnpav.Container{Object: obj, ChildCount: me.objectChildCount(cdsObject)}
 		return
 	}
 	if !fileInfo.Mode().IsRegular() {
@@ -207,17 +207,17 @@ func (me *contentDirectoryService) objectFromID(id string) (o object, err error)
 	return
 }
 
-func (me *contentDirectoryService) Handle(action string, argsXML []byte, r *http.Request) (map[string]string, error) {
+func (me *contentDirectoryService) Handle(action string, argsXML []byte, r *http.Request) ([][2]string, error) {
 	host := r.Host
 	userAgent := r.UserAgent()
 	switch action {
 	case "GetSystemUpdateID":
-		return map[string]string{
-			"Id": me.updateIDString(),
+		return [][2]string{
+			{"Id", me.updateIDString()},
 		}, nil
 	case "GetSortCapabilities":
-		return map[string]string{
-			"SortCaps": "dc:title",
+		return [][2]string{
+			{"SortCaps", "dc:title"},
 		}, nil
 	case "Browse":
 		var browse browse
@@ -249,11 +249,11 @@ func (me *contentDirectoryService) Handle(action string, argsXML []byte, r *http
 			if err != nil {
 				return nil, err
 			}
-			return map[string]string{
-				"TotalMatches":   fmt.Sprint(totalMatches),
-				"NumberReturned": fmt.Sprint(len(objs)),
-				"Result":         didl_lite(string(result)),
-				"UpdateID":       me.updateIDString(),
+			return [][2]string{
+				{"Result", didl_lite(string(result))},
+				{"NumberReturned", fmt.Sprint(len(objs))},
+				{"TotalMatches", fmt.Sprint(totalMatches)},
+				{"UpdateID", me.updateIDString()},
 			}, nil
 		case "BrowseMetadata":
 			fileInfo, err := os.Stat(obj.FilePath())
@@ -274,18 +274,18 @@ func (me *contentDirectoryService) Handle(action string, argsXML []byte, r *http
 			if err != nil {
 				return nil, err
 			}
-			return map[string]string{
-				"TotalMatches":   "1",
-				"NumberReturned": "1",
-				"Result":         didl_lite(func() string { return string(buf) }()),
-				"UpdateID":       me.updateIDString(),
+			return [][2]string{
+				{"Result", didl_lite(func() string { return string(buf) }())},
+				{"NumberReturned", "1"},
+				{"TotalMatches", "1"},
+				{"UpdateID", me.updateIDString()},
 			}, nil
 		default:
 			return nil, upnp.Errorf(upnp.ArgumentValueInvalidErrorCode, "unhandled browse flag: %v", browse.BrowseFlag)
 		}
 	case "GetSearchCapabilities":
-		return map[string]string{
-			"SearchCaps": "",
+		return [][2]string{
+			{"SearchCaps", ""},
 		}, nil
 	default:
 		return nil, upnp.InvalidActionError
diff --git a/dlna/dms/dms.go b/dlna/dms/dms.go
index e4b7fd2..f69f959 100644
--- a/dlna/dms/dms.go
+++ b/dlna/dms/dms.go
@@ -57,6 +57,7 @@ var transcodes = map[string]transcodeSpec{
 	},
 	"vp8":        {mimeType: "video/webm", Transcode: transcode.VP8Transcode},
 	"chromecast": {mimeType: "video/mp4", Transcode: transcode.ChromecastTranscode},
+	"web":        {mimeType: "video/mp4", Transcode: transcode.WebTranscode},
 }
 
 func makeDeviceUuid(unique string) string {
@@ -251,7 +252,7 @@ type Server struct {
 
 // UPnP SOAP service.
 type UPnPService interface {
-	Handle(action string, argsXML []byte, r *http.Request) (respArgs map[string]string, err error)
+	Handle(action string, argsXML []byte, r *http.Request) (respArgs [][2]string, err error)
 	Subscribe(callback []*url.URL, timeoutSeconds int) (sid string, actualTimeout int, err error)
 	Unsubscribe(sid string) error
 }
@@ -505,9 +506,10 @@ func handleSCPDs(mux *http.ServeMux) {
 }
 
 // Marshal SOAP response arguments into a response XML snippet.
-func marshalSOAPResponse(sa upnp.SoapAction, args map[string]string) []byte {
+func marshalSOAPResponse(sa upnp.SoapAction, args [][2]string) []byte {
 	soapArgs := make([]soap.Arg, 0, len(args))
-	for argName, value := range args {
+	for _, arg := range args {
+		argName, value := arg[0], arg[1]
 		soapArgs = append(soapArgs, soap.Arg{
 			XMLName: xml.Name{Local: argName},
 			Value:   value,
@@ -517,7 +519,7 @@ func marshalSOAPResponse(sa upnp.SoapAction, args map[string]string) []byte {
 }
 
 // Handle a SOAP request and return the response arguments or UPnP error.
-func (me *Server) soapActionResponse(sa upnp.SoapAction, actionRequestXML []byte, r *http.Request) (map[string]string, error) {
+func (me *Server) soapActionResponse(sa upnp.SoapAction, actionRequestXML []byte, r *http.Request) ([][2]string, error) {
 	service, ok := me.services[sa.Type]
 	if !ok {
 		// TODO: What's the invalid service error?!
@@ -530,6 +532,10 @@ func (me *Server) soapActionResponse(sa upnp.SoapAction, actionRequestXML []byte
 func (me *Server) serviceControlHandler(w http.ResponseWriter, r *http.Request) {
 	found := false
 	clientIp, _, _ := net.SplitHostPort(r.RemoteAddr)
+	if zoneDelimiterIdx := strings.Index(clientIp, "%"); zoneDelimiterIdx != -1 {
+		// IPv6 addresses may have the form address%zone (e.g. ::1%eth0)
+		clientIp = clientIp[:zoneDelimiterIdx]
+	}
 	for _, ipnet := range me.AllowedIpNets {
 		if ipnet.Contains(net.ParseIP(clientIp)) {
 			found = true
@@ -736,8 +742,8 @@ func (server *Server) initMux(mux *http.ServeMux) {
 		} else {
 			k = r.URL.Query().Get("transcode")
 		}
-		if k == "" {
-			mimeType, err := MimeTypeByPath(filePath)
+		mimeType, err := MimeTypeByPath(filePath)
+		if k == "" || mimeType.IsImage() {
 			if err != nil {
 				http.Error(w, err.Error(), http.StatusInternalServerError)
 				return
diff --git a/dlna/dms/dms_others.go b/dlna/dms/dms_others.go
index a595c1d..5540261 100644
--- a/dlna/dms/dms_others.go
+++ b/dlna/dms/dms_others.go
@@ -1,4 +1,5 @@
-//+build !unix,!windows
+//go:build !linux && !darwin && !windows
+// +build !linux,!darwin,!windows
 
 package dms
 
diff --git a/dlna/dms/dms_unix.go b/dlna/dms/dms_unix.go
index 0b6a8cf..67f0df0 100644
--- a/dlna/dms/dms_unix.go
+++ b/dlna/dms/dms_unix.go
@@ -1,4 +1,5 @@
-//+build unix
+//go:build linux || darwin
+// +build linux darwin
 
 package dms
 
diff --git a/dlna/dms/dms_unix_test.go b/dlna/dms/dms_unix_test.go
index a1be6fd..e6b279c 100644
--- a/dlna/dms/dms_unix_test.go
+++ b/dlna/dms/dms_unix_test.go
@@ -1,4 +1,5 @@
-//+build unix
+//go:build linux || darwin
+// +build linux darwin
 
 package dms
 
@@ -6,16 +7,16 @@ import "testing"
 
 func TestIsHiddenPath(t *testing.T) {
 	var data = map[string]bool{
-		"/some/path": false,
-		"/some/foo.bar": false,
+		"/some/path":         false,
+		"/some/foo.bar":      false,
 		"/some/path/.hidden": true,
 		"/some/.hidden/path": true,
-		"/.hidden/path": true,
+		"/.hidden/path":      true,
 	}
 	for path, expected := range data {
 		if actual, err := isHiddenPath(path); err != nil {
 			t.Errorf("isHiddenPath(%v) returned unexpected error: %s", path, err)
-		] else if expected != actual {
+		} else if expected != actual {
 			t.Errorf("isHiddenPath(%v), expected %v, got %v", path, expected, actual)
 		}
 	}
diff --git a/dlna/dms/dms_windows.go b/dlna/dms/dms_windows.go
index 03b0513..14db0e4 100644
--- a/dlna/dms/dms_windows.go
+++ b/dlna/dms/dms_windows.go
@@ -1,4 +1,5 @@
-//+build windows
+//go:build windows
+// +build windows
 
 package dms
 
diff --git a/go.mod b/go.mod
index 2457214..527e02b 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,6 @@ require (
 	github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
-	golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2
-	golang.org/x/sys v0.0.0-20190415145633-3fd5a3612ccd
+	golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d
+	golang.org/x/sys v0.0.0-20210415045647-66c3f260301c
 )
diff --git a/go.sum b/go.sum
index 26e9cb1..39f7cea 100644
--- a/go.sum
+++ b/go.sum
@@ -37,10 +37,12 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2 h1:iC0Y6EDq+rhnAePxGvJs2kzUAYcwESqdcGRPzEUfzTU=
-golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190415145633-3fd5a3612ccd h1:MNN7PRW7zYXd8upVO5qfKeOnQG74ivRNv7sz4k4cQMs=
-golang.org/x/sys v0.0.0-20190415145633-3fd5a3612ccd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d h1:BgJvlyh+UqCUaPlscHJ+PN8GcpfrFdr7NHjd1JL0+Gs=
+golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210415045647-66c3f260301c h1:6L+uOeS3OQt/f4eFHXZcTxeZrGCuz+CLElgEBjbcTA4=
+golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/helpers/bsd/dms b/helpers/bsd/dms
new file mode 100644
index 0000000..a4d3ed9
--- /dev/null
+++ b/helpers/bsd/dms
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+. /etc/rc.subr
+
+name="dms"
+rcvar="dms_enable"
+
+: ${dms_user:="root"}
+: ${dms_enable:="NO"}
+: ${dms_media_dir:="/media"}
+
+# Daemon
+pidfile="/var/run/${name}.pid"
+command=/usr/sbin/daemon
+procname="daemon"
+dms="/usr/local/bin/dms -path ${dms_media_dir}"
+command_args=" -P ${pidfile} -r -f -u ${dms_user} ${dms}"
+start_precmd="dms_precmd"
+pidfile="/var/run/${name}.pid"
+
+dms_precmd()
+{
+    install -o ${dms_user} /dev/null ${pidfile}
+}
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/helpers/systemd/dms.service b/helpers/systemd/dms.service
new file mode 100644
index 0000000..e0d0555
--- /dev/null
+++ b/helpers/systemd/dms.service
@@ -0,0 +1,12 @@
+# Put this file in /home/USERNAME/.config/systemd/user/
+#
+# Enable this service with
+# systemctl --user --now enable dms.service
+[Unit]
+Description=DMS UPnP Media Server
+
+[Service]
+ExecStart=/home/USERNAME/go/bin/dms -friendlyName DMS_Server -path /home/share/
+
+[Install]
+WantedBy=default.target
diff --git a/main.go b/main.go
index 3f558f5..6215a9b 100644
--- a/main.go
+++ b/main.go
@@ -120,7 +120,7 @@ func main() {
 	fFprobeCachePath := flag.String("fFprobeCachePath", config.FFprobeCachePath, "path to FFprobe cache file")
 	configFilePath := flag.String("config", "", "json configuration file")
 	allowedIps := flag.String("allowedIps", "", "allowed ip of clients, separated by comma")
-	forceTranscodeTo := flag.String("forceTranscodeTo", config.ForceTranscodeTo, "force transcoding to certain format, supported: 'chromecast', 'vp8'")
+	forceTranscodeTo := flag.String("forceTranscodeTo", config.ForceTranscodeTo, "force transcoding to certain format, supported: 'chromecast', 'vp8', 'web'")
 	flag.BoolVar(&config.NoTranscode, "noTranscode", false, "disable transcoding")
 	flag.BoolVar(&config.NoProbe, "noProbe", false, "disable media probing with ffprobe")
 	flag.BoolVar(&config.StallEventSubscribe, "stallEventSubscribe", false, "workaround for some bad event subscribers")
@@ -134,7 +134,7 @@ func main() {
 		log.Fatalf("%s: %s\n", "unexpected positional arguments", flag.Args())
 	}
 
-	config.Path = *path
+	config.Path, _ = filepath.Abs(*path)
 	config.IfName = *ifName
 	config.Http = *http
 	config.FriendlyName = *friendlyName
@@ -143,9 +143,9 @@ func main() {
 	config.FFprobeCachePath = *fFprobeCachePath
 	config.AllowedIpNets = makeIpNets(*allowedIps)
 	config.ForceTranscodeTo = *forceTranscodeTo
-	// if len(config.AllowedIps) > 0 {
+
 	log.Printf("allowed ip nets are %q", config.AllowedIpNets)
-	// }
+	log.Printf("serving folder %q", config.Path)
 
 	if len(*configFilePath) > 0 {
 		config.load(*configFilePath)
@@ -322,7 +322,7 @@ func makeIpNets(s string) []*net.IPNet {
 	if len(s) < 1 {
 		_, ipnet, _ := net.ParseCIDR("0.0.0.0/0")
 		nets = append(nets, ipnet)
-		_, ipnet, _ = net.ParseCIDR("0:0:0::/128")
+		_, ipnet, _ = net.ParseCIDR("::/0")
 		nets = append(nets, ipnet)
 	} else {
 		for _, el := range strings.Split(s, ",") {
diff --git a/play/attrs.go b/play/attrs.go
index 3902357..44bea14 100644
--- a/play/attrs.go
+++ b/play/attrs.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/bool.go b/play/bool.go
index be40e15..ad21ae5 100644
--- a/play/bool.go
+++ b/play/bool.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/closure.go b/play/closure.go
index af06655..4fe22fa 100644
--- a/play/closure.go
+++ b/play/closure.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/execbug.go b/play/execbug.go
index 2862238..df38b3a 100644
--- a/play/execbug.go
+++ b/play/execbug.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/execgood.go b/play/execgood.go
index b460fab..20893f3 100644
--- a/play/execgood.go
+++ b/play/execgood.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/ffprobe.go b/play/ffprobe.go
index 946e73c..9246b5a 100644
--- a/play/ffprobe.go
+++ b/play/ffprobe.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/mime.go b/play/mime.go
index 5cdc210..1f50719 100644
--- a/play/mime.go
+++ b/play/mime.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/parse_http_version.go b/play/parse_http_version.go
index 65c8005..6355930 100644
--- a/play/parse_http_version.go
+++ b/play/parse_http_version.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/print-ifs.go b/play/print-ifs.go
index 88faf63..f2a8d08 100644
--- a/play/print-ifs.go
+++ b/play/print-ifs.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/scpd.go b/play/scpd.go
index 403c030..d2d9ee8 100644
--- a/play/scpd.go
+++ b/play/scpd.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/soap.go b/play/soap.go
index 38e1f35..57d145e 100644
--- a/play/soap.go
+++ b/play/soap.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/transcode.go b/play/transcode.go
index cc2cc93..41a59b3 100644
--- a/play/transcode.go
+++ b/play/transcode.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/play/url.go b/play/url.go
index 83aad3a..9dcbd09 100644
--- a/play/url.go
+++ b/play/url.go
@@ -1,3 +1,4 @@
+//go:build ignore
 // +build ignore
 
 package main
diff --git a/transcode/transcode.go b/transcode/transcode.go
index 79e5f6f..df7a123 100644
--- a/transcode/transcode.go
+++ b/transcode/transcode.go
@@ -137,3 +137,26 @@ func ChromecastTranscode(path string, start, length time.Duration, stderr io.Wri
 		"pipe:"}...)
 	return transcodePipe(args, stderr)
 }
+
+// Returns a stream of h264 video and mp3 audio
+func WebTranscode(path string, start, length time.Duration, stderr io.Writer) (r io.ReadCloser, err error) {
+	args := []string{
+		"ffmpeg",
+		"-ss", FormatDurationSexagesimal(start),
+		"-i", path,
+		"-pix_fmt", "yuv420p",
+		"-c:v", "libx264", "-crf", "25",
+		"-c:a", "mp3", "-ab", "128k", "-ar", "44100",
+		"-preset", "ultrafast",
+		"-movflags", "+faststart+frag_keyframe+empty_moov",
+	}
+	if length > 0 {
+		args = append(args, []string{
+			"-t", FormatDurationSexagesimal(length),
+		}...)
+	}
+	args = append(args, []string{
+		"-f", "mp4",
+		"pipe:"}...)
+	return transcodePipe(args, stderr)
+}
diff --git a/upnp/upnp.go b/upnp/upnp.go
index 86cd5d8..eef9295 100644
--- a/upnp/upnp.go
+++ b/upnp/upnp.go
@@ -41,6 +41,9 @@ type SoapAction struct {
 }
 
 func ParseActionHTTPHeader(s string) (ret SoapAction, err error) {
+	if len(s) < 3 {
+		return
+	}
 	if s[0] != '"' || s[len(s)-1] != '"' {
 		return
 	}
diff --git a/upnpav/upnpav.go b/upnpav/upnpav.go
index 2ba7f8a..b57b359 100644
--- a/upnpav/upnpav.go
+++ b/upnpav/upnpav.go
@@ -34,9 +34,9 @@ type Object struct {
 	ID          string `xml:"id,attr"`
 	ParentID    string `xml:"parentID,attr"`
 	Restricted  int    `xml:"restricted,attr"` // indicates whether the object is modifiable
+	Title       string `xml:"dc:title"`
 	Class       string `xml:"upnp:class"`
 	Icon        string `xml:"upnp:icon,omitempty"`
-	Title       string `xml:"dc:title"`
 	Artist      string `xml:"upnp:artist,omitempty"`
 	Album       string `xml:"upnp:album,omitempty"`
 	Genre       string `xml:"upnp:genre,omitempty"`