2023-06-19 23:42:47 +02:00
|
|
|
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
2019-11-29 08:47:53 +01:00
|
|
|
|
2023-08-11 04:46:45 +02:00
|
|
|
package media // import "miniflux.app/v2/internal/reader/media"
|
2019-11-29 08:47:53 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
var textLinkRegex = regexp.MustCompile(`(?mi)(\bhttps?:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])`)
|
|
|
|
|
2024-03-12 04:43:14 +01:00
|
|
|
// Specs: https://www.rssboard.org/media-rss
|
2024-03-14 05:06:28 +01:00
|
|
|
type MediaItemElement struct {
|
2024-03-14 05:26:39 +01:00
|
|
|
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"`
|
2019-11-29 08:47:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// AllMediaThumbnails returns all thumbnail elements merged together.
|
2024-03-14 05:06:28 +01:00
|
|
|
func (e *MediaItemElement) AllMediaThumbnails() []Thumbnail {
|
2019-11-29 08:47:53 +01:00
|
|
|
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.
|
2024-03-14 05:06:28 +01:00
|
|
|
func (e *MediaItemElement) AllMediaContents() []Content {
|
2019-11-29 08:47:53 +01:00
|
|
|
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.
|
2024-03-14 05:06:28 +01:00
|
|
|
func (e *MediaItemElement) AllMediaPeerLinks() []PeerLink {
|
2019-11-29 08:47:53 +01:00
|
|
|
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.
|
2024-03-14 05:06:28 +01:00
|
|
|
func (e *MediaItemElement) FirstMediaDescription() string {
|
2019-11-29 08:47:53 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-02-29 00:29:35 +01:00
|
|
|
content := strings.ReplaceAll(d.Description, "\n", "<br>")
|
2019-11-29 08:47:53 +01:00
|
|
|
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 ""
|
|
|
|
}
|
2024-03-14 05:26:39 +01:00
|
|
|
|
|
|
|
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"`
|
|
|
|
}
|