diff options
Diffstat (limited to 'reader/media')
-rw-r--r-- | reader/media/media.go | 176 | ||||
-rw-r--r-- | reader/media/media_test.go | 110 |
2 files changed, 286 insertions, 0 deletions
diff --git a/reader/media/media.go b/reader/media/media.go new file mode 100644 index 0000000..d414191 --- /dev/null +++ b/reader/media/media.go @@ -0,0 +1,176 @@ +// Copyright 2019 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package media // import "miniflux.app/reader/media" + +import ( + "regexp" + "strconv" + "strings" +) + +var textLinkRegex = regexp.MustCompile(`(?mi)(\bhttps?:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])`) + +// Element represents XML media elements. +type Element struct { + MediaGroups []Group `xml:"http://search.yahoo.com/mrss/ group"` + MediaContents []Content `xml:"http://search.yahoo.com/mrss/ content"` + MediaThumbnails []Thumbnail `xml:"http://search.yahoo.com/mrss/ thumbnail"` + MediaDescriptions DescriptionList `xml:"http://search.yahoo.com/mrss/ description"` + MediaPeerLinks []PeerLink `xml:"http://search.yahoo.com/mrss/ peerLink"` +} + +// AllMediaThumbnails returns all thumbnail elements merged together. +func (e *Element) AllMediaThumbnails() []Thumbnail { + var items []Thumbnail + items = append(items, e.MediaThumbnails...) + for _, mediaGroup := range e.MediaGroups { + items = append(items, mediaGroup.MediaThumbnails...) + } + return items +} + +// AllMediaContents returns all content elements merged together. +func (e *Element) AllMediaContents() []Content { + var items []Content + items = append(items, e.MediaContents...) + for _, mediaGroup := range e.MediaGroups { + items = append(items, mediaGroup.MediaContents...) + } + return items +} + +// AllMediaPeerLinks returns all peer link elements merged together. +func (e *Element) AllMediaPeerLinks() []PeerLink { + var items []PeerLink + items = append(items, e.MediaPeerLinks...) + for _, mediaGroup := range e.MediaGroups { + items = append(items, mediaGroup.MediaPeerLinks...) + } + return items +} + +// FirstMediaDescription returns the first description element. +func (e *Element) FirstMediaDescription() string { + description := e.MediaDescriptions.First() + if description != "" { + return description + } + + for _, mediaGroup := range e.MediaGroups { + description = mediaGroup.MediaDescriptions.First() + if description != "" { + return description + } + } + + return "" +} + +// Group represents a XML element "media:group". +type Group struct { + MediaContents []Content `xml:"http://search.yahoo.com/mrss/ content"` + MediaThumbnails []Thumbnail `xml:"http://search.yahoo.com/mrss/ thumbnail"` + MediaDescriptions DescriptionList `xml:"http://search.yahoo.com/mrss/ description"` + MediaPeerLinks []PeerLink `xml:"http://search.yahoo.com/mrss/ peerLink"` +} + +// Content represents a XML element "media:content". +type Content struct { + URL string `xml:"url,attr"` + Type string `xml:"type,attr"` + FileSize string `xml:"fileSize,attr"` + Medium string `xml:"medium,attr"` +} + +// MimeType returns the attachment mime type. +func (mc *Content) MimeType() string { + switch { + case mc.Type == "" && mc.Medium == "image": + return "image/*" + case mc.Type == "" && mc.Medium == "video": + return "video/*" + case mc.Type == "" && mc.Medium == "audio": + return "audio/*" + case mc.Type == "" && mc.Medium == "video": + return "video/*" + case mc.Type != "": + return mc.Type + default: + return "application/octet-stream" + } +} + +// Size returns the attachment size. +func (mc *Content) Size() int64 { + if mc.FileSize == "" { + return 0 + } + size, _ := strconv.ParseInt(mc.FileSize, 10, 0) + return size +} + +// Thumbnail represents a XML element "media:thumbnail". +type Thumbnail struct { + URL string `xml:"url,attr"` +} + +// MimeType returns the attachment mime type. +func (t *Thumbnail) MimeType() string { + return "image/*" +} + +// Size returns the attachment size. +func (t *Thumbnail) Size() int64 { + return 0 +} + +// PeerLink represents a XML element "media:peerLink". +type PeerLink struct { + URL string `xml:"href,attr"` + Type string `xml:"type,attr"` +} + +// MimeType returns the attachment mime type. +func (p *PeerLink) MimeType() string { + if p.Type != "" { + return p.Type + } + return "application/octet-stream" +} + +// Size returns the attachment size. +func (p *PeerLink) Size() int64 { + return 0 +} + +// Description represents a XML element "media:description". +type Description struct { + Type string `xml:"type,attr"` + Description string `xml:",chardata"` +} + +// HTML returns the description as HTML. +func (d *Description) HTML() string { + if d.Type == "html" { + return d.Description + } + + content := strings.Replace(d.Description, "\n", "<br>", -1) + return textLinkRegex.ReplaceAllString(content, `<a href="${1}">${1}</a>`) +} + +// DescriptionList represents a list of "media:description" XML elements. +type DescriptionList []Description + +// First returns the first non-empty description. +func (dl DescriptionList) First() string { + for _, description := range dl { + contents := description.HTML() + if contents != "" { + return contents + } + } + return "" +} diff --git a/reader/media/media_test.go b/reader/media/media_test.go new file mode 100644 index 0000000..b0d2842 --- /dev/null +++ b/reader/media/media_test.go @@ -0,0 +1,110 @@ +// Copyright 2019 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package media // import "miniflux.app/reader/media" + +import "testing" + +func TestContentMimeType(t *testing.T) { + scenarios := []struct { + inputType, inputMedium, expectedMimeType string + }{ + {"image/png", "image", "image/png"}, + {"", "image", "image/*"}, + {"", "video", "video/*"}, + {"", "audio", "audio/*"}, + {"", "", "application/octet-stream"}, + } + + for _, scenario := range scenarios { + content := &Content{Type: scenario.inputType, Medium: scenario.inputMedium} + result := content.MimeType() + if result != scenario.expectedMimeType { + t.Errorf(`Unexpected mime type, got %q instead of %q for type=%q medium=%q`, + result, + scenario.expectedMimeType, + scenario.inputType, + scenario.inputMedium, + ) + } + } +} + +func TestContentSize(t *testing.T) { + scenarios := []struct { + inputSize string + expectedSize int64 + }{ + {"", 0}, + {"123", int64(123)}, + } + + for _, scenario := range scenarios { + content := &Content{FileSize: scenario.inputSize} + result := content.Size() + if result != scenario.expectedSize { + t.Errorf(`Unexpected size, got %d instead of %d for %q`, + result, + scenario.expectedSize, + scenario.inputSize, + ) + } + } +} + +func TestPeerLinkType(t *testing.T) { + scenarios := []struct { + inputType string + expectedMimeType string + }{ + {"", "application/octet-stream"}, + {"application/x-bittorrent", "application/x-bittorrent"}, + } + + for _, scenario := range scenarios { + peerLink := &PeerLink{Type: scenario.inputType} + result := peerLink.MimeType() + if result != scenario.expectedMimeType { + t.Errorf(`Unexpected mime type, got %q instead of %q for %q`, + result, + scenario.expectedMimeType, + scenario.inputType, + ) + } + } +} + +func TestDescription(t *testing.T) { + scenarios := []struct { + inputType string + inputContent string + expectedDescription string + }{ + {"", "", ""}, + {"html", "a <b>c</b>", "a <b>c</b>"}, + {"plain", "a\nhttp://www.example.org/", `a<br><a href="http://www.example.org/">http://www.example.org/</a>`}, + } + + for _, scenario := range scenarios { + desc := &Description{Type: scenario.inputType, Description: scenario.inputContent} + result := desc.HTML() + if result != scenario.expectedDescription { + t.Errorf(`Unexpected description, got %q instead of %q for %q`, + result, + scenario.expectedDescription, + scenario.inputType, + ) + } + } +} + +func TestFirstDescription(t *testing.T) { + var descList DescriptionList + descList = append(descList, Description{}) + descList = append(descList, Description{Description: "Something"}) + + if descList.First() != "Something" { + t.Errorf(`Unexpected description`) + } +} |