// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package media // import "miniflux.app/v2/internal/reader/media"
import (
"regexp"
"strconv"
"strings"
)
var textLinkRegex = regexp.MustCompile(`(?mi)(\bhttps?:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])`)
// Specs: https://www.rssboard.org/media-rss
type MediaItemElement struct {
MediaCategories MediaCategoryList `xml:"http://search.yahoo.com/mrss/ category"`
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 *MediaItemElement) 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 *MediaItemElement) 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 *MediaItemElement) 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 *MediaItemElement) 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.ReplaceAll(d.Description, "\n", "
")
return textLinkRegex.ReplaceAllString(content, `${1}`)
}
// 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 ""
}
type MediaCategoryList []MediaCategory
func (mcl MediaCategoryList) Labels() []string {
var labels []string
for _, category := range mcl {
label := strings.TrimSpace(category.Label)
if label != "" {
labels = append(labels, label)
}
}
return labels
}
type MediaCategory struct {
Label string `xml:"label,attr"`
}