package sitemap // sitemap formatting with syntactic sugar. © Arthur Mingard 2022 // See https://developers.google.com/search/docs/crawling-indexing/sitemaps/video-sitemaps import ( "encoding/xml" "strings" ) const ( // TagLimit is the number of permitted video tags. TagLimit = 32 // MaxDescriptionLength is the maximum length of the video:description. MaxDescriptionLength = 2048 // DurationMin is the minimum video duration of 1 second. DurationMin int = 1 // DurationMax is the maximum video duration length in seconds (8 hours). DurationMax int = 28800 // RatingLow is the lowest posible rating. RatingLow float32 = 0.0 // RatingHigh is the highest posible rating. RatingHigh float32 = 5.0 ) // Restriction stores country restriction details type Restriction struct { XMLName xml.Name `xml:"video:restriction,omitempty"` Relationship *xml.Attr `xml:",attr,omitempty"` Value string `xml:",chardata"` } // Uploader stores the uploader of the video. type Uploader struct { XMLName xml.Name `xml:"video:uploader,omitempty"` // Info (optional) specifies the URL of a webpage with additional information about this uploader. Info string `xml:"info,attr,omitempty"` // Value is the video uploader's name, a string with a maximum of 255 characters. Value string `xml:",chardata"` } // Video stores video entry data. type Video struct { XMLName xml.Name `xml:"video:video"` // Required // Title HTML entities must be escaped or wrapped in a CDATA block. Title string `xml:"video:title"` // Description Maximum {{MaxDescriptionLength}} characters. All HTML entities must be escaped or wrapped in a CDATA block. It must match the description displayed on the web page (it doesn't need to be a word-for-word match). Description string `xml:"video:description,omitempty"` ThumbnailLoc string `xml:"video:thumbnail_loc,omitempty"` // ContentLoc HTML and Flash aren't supported formats. ContentLoc string `xml:"video:content_loc,omitempty"` // Can be used instead of or alongside ContentLoc. PlayerLoc string `xml:"video:player_loc,omitempty"` // Recommended // Duration value must be from {{DurationMin}} to {{DurationMax}} inclusive. Duration int `xml:"video:duration,omitempty"` // ExpirationDate format either YYYY-MM-DD or YYYY-MM-DDThh:mm:ss+TZD ExpirationDate customDate `xml:"video:expiration_date,omitempty"` // Optional // Rating values are float numbers in the range {{RatingLow}} (low) to {{RatingHigh}} (high), inclusive. Rating float32 `xml:"video:rating,omitempty"` ViewCount int `xml:"video:view_count,omitempty"` PublicationDate customDate `xml:"video:publication_date,omitempty"` // FamilyFriendly whether the video is available with SafeSearch. FamilyFriendly BoolStr `xml:"video:family_friendly,omitempty"` Restrictions []*Restriction `xml:"video:restriction,omitempty"` Platforms []*Platform `xml:"video:platform,omitempty"` // RequiresSubscription indicates whether a subscription is required to view the video. RequiresSubscription BoolStr `xml:"video:requires_subscription,omitempty"` Uploader *Uploader `xml:"video:uploader,omitempty"` // Live indicates whether the video is a live stream Live BoolStr `xml:"video:live,omitempty"` // Tags are limited to a max of {{TagLimit}}. Tags []string `xml:"video:tag,omitempty"` } // SetTitle sets the video extensions title parameter. func (v *Video) SetTitle(t string) *Video { v.Title = t return v } // SetDescription sets the video extensions description parameter. func (v *Video) SetDescription(d string) *Video { // Limit to the maximum length of a description. v.Description = d if len(v.Description) > MaxDescriptionLength { v.Description = d[:MaxDescriptionLength] } return v } // SetThumbnailLocation sets the video thumbnail location parameter. func (v *Video) SetThumbnailLocation(t string) *Video { v.ThumbnailLoc = t return v } // SetContentLocation sets the video content location parameter. func (v *Video) SetContentLocation(c string) *Video { v.ContentLoc = c return v } // SetPlayerLocation sets the video player location parameter. func (v *Video) SetPlayerLocation(p string) *Video { v.PlayerLoc = p return v } // SetDuration sets the video duration parameter. func (v *Video) SetDuration(d int) *Video { // Must be no less than min and no more than max. if d >= DurationMin && d <= DurationMax { v.Duration = d } return v } // SetExpirationDate sets the video ExpirationDate parameter. func (v *Video) SetExpirationDate(c customDate) *Video { v.ExpirationDate = c return v } // SetRating sets the video rating. func (v *Video) SetRating(r float32) *Video { if r >= RatingLow && r <= RatingHigh { v.Rating = r } return v } // SetViewCount sets the video view_count. func (v *Video) SetViewCount(vc int) *Video { v.ViewCount = vc return v } // SetPublicationDate sets the video extensions PublicationDate parameter. func (v *Video) SetPublicationDate(c customDate) *Video { v.PublicationDate = c return v } // IsFamilyFriendly sets the family friendly option to 'yes' func (v *Video) IsFamilyFriendly() *Video { v.FamilyFriendly = Yes return v } // NotFamilyFriendly sets the family friendly option to 'no' func (v *Video) NotFamilyFriendly() *Video { v.FamilyFriendly = No return v } // setRestrictions creates a country restriction block. func (v *Video) setRestrictions(r *xml.Attr, c string) { v.Restrictions = append(v.Restrictions, &Restriction{ Relationship: r, Value: c, }) } // AllowCountries creates a list of allowed countries. func (v *Video) AllowCountries(c string) *Video { v.setRestrictions(Allow, c) return v } // DenyCountries creates a list of denied countries. func (v *Video) DenyCountries(c string) *Video { v.setRestrictions(Deny, c) return v } // addPlatform creates a platform restriction block. func (v *Video) addPlatform(r *xml.Attr, p ...PlatformName) { // Convert platforms to string platforms := make([]string, 0) for _, a := range p { platforms = append(platforms, string(a)) } v.Platforms = append(v.Platforms, &Platform{ Relationship: r, Value: strings.Join(platforms, " "), }) } // AllowPlatforms creates a list of allowed platforms. func (v *Video) AllowPlatforms(p ...PlatformName) *Video { v.addPlatform(Allow, p...) return v } // DenyPlatforms creates a list of denied platforms. func (v *Video) DenyPlatforms(p ...PlatformName) *Video { v.addPlatform(Deny, p...) return v } // SubRequired sets the requires_subscription option to 'yes' func (v *Video) SubRequired() *Video { v.RequiresSubscription = Yes return v } // SubNotRequired sets the requires_subscription option to 'no' func (v *Video) SubNotRequired() *Video { v.RequiresSubscription = No return v } // setUploader sets the uploader func (v *Video) setUploader(u *Uploader) *Video { v.Uploader = u return v } // SetUploaderInfo sets the uploader func (v *Video) SetUploaderInfo(i string) *Video { if v.Uploader == nil { return v.setUploader(&Uploader{ Info: i, }) } v.Uploader.Info = i return v } // SetUploaderVal sets the uploader value func (v *Video) SetUploaderVal(val string) *Video { if v.Uploader == nil { return v.setUploader(&Uploader{ Value: val, }) } v.Uploader.Value = val return v } // IsLive sets the live option to 'yes' func (v *Video) IsLive() *Video { v.Live = Yes return v } // NotLive sets the live option to 'no' func (v *Video) NotLive() *Video { v.Live = No return v } // SetTags sets tag values func (v *Video) SetTags(t ...string) *Video { // Only push tags if we're below the limit. if len(v.Tags)+len(t) <= cap(v.Tags) { v.Tags = append(v.Tags, t...) } return v } func defaultVideo() *Video { return &Video{ Restrictions: make([]*Restriction, 0), Platforms: make([]*Platform, 0), Tags: make([]string, 0, TagLimit), } } // NewVideo returns a new instance of the default Video extension. func NewVideo() *Video { return defaultVideo() }