Explorar el Código

complement itunes tag

per [official
recommendation](https://help.apple.com/itc/podcasts_connect/#/itcb54353390)
master
magodo hace 7 años
padre
commit
67f38f898c
Se han modificado 9 ficheros con 195 adiciones y 59 borrados
  1. +14
    -7
      lib/domain/rss_item_itunes.dart
  2. +29
    -25
      lib/domain/rss_itunes.dart
  3. +1
    -1
      lib/domain/rss_itunes_category.dart
  4. +19
    -0
      lib/domain/rss_itunes_episode_type.dart
  5. +18
    -0
      lib/domain/rss_itunes_owner.dart
  6. +17
    -0
      lib/domain/rss_itunes_type.dart
  7. +18
    -1
      lib/util/helpers.dart
  8. +67
    -25
      test/rss_test.dart
  9. +12
    -0
      test/xml/RSS-Itunes.xml

+ 14
- 7
lib/domain/rss_item_itunes.dart Ver fichero

@@ -2,12 +2,15 @@ import 'package:webfeed/util/helpers.dart';
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';


import 'rss_itunes_category.dart'; import 'rss_itunes_category.dart';
import 'rss_itunes_episode_type.dart';
import 'rss_itunes_image.dart'; import 'rss_itunes_image.dart';


class RssItemItunes { class RssItemItunes {
final String title;
final int episode; final int episode;
final int season;
final Duration duration; final Duration duration;
final String episodeType;
final RssItunesEpisodeType episodeType;
final String author; final String author;
final String summary; final String summary;
final bool explicit; final bool explicit;
@@ -15,9 +18,12 @@ class RssItemItunes {
final List<String> keywords; final List<String> keywords;
final RssItunesImage image; final RssItunesImage image;
final RssItunesCategory category; final RssItunesCategory category;
final bool block;


RssItemItunes({ RssItemItunes({
this.title,
this.episode, this.episode,
this.season,
this.duration, this.duration,
this.episodeType, this.episodeType,
this.author, this.author,
@@ -27,31 +33,32 @@ class RssItemItunes {
this.keywords, this.keywords,
this.image, this.image,
this.category, this.category,
this.block,
}); });


factory RssItemItunes.parse(XmlElement element) { factory RssItemItunes.parse(XmlElement element) {
if (element == null) { if (element == null) {
return null; return null;
} }
var explicitStr =
findElementOrNull(element, "itunes:explicit")?.text?.toLowerCase()?.trim();
var episodeStr = findElementOrNull(element, "itunes:episode")?.text?.trim(); var episodeStr = findElementOrNull(element, "itunes:episode")?.text?.trim();
var seasonStr = findElementOrNull(element, "itunes:season")?.text?.trim();
var durationStr = findElementOrNull(element, "itunes:duration")?.text?.trim(); var durationStr = findElementOrNull(element, "itunes:duration")?.text?.trim();


return RssItemItunes( return RssItemItunes(
title: findElementOrNull(element, "itunes:title")?.text?.trim(),
episode: episodeStr == null ? null : int.parse(episodeStr), episode: episodeStr == null ? null : int.parse(episodeStr),
season: seasonStr == null ? null : int.parse(seasonStr),
duration: durationStr == null ? null : parseDuration(durationStr), duration: durationStr == null ? null : parseDuration(durationStr),
episodeType: findElementOrNull(element, "itunes:episodeType")?.text?.trim(),
episodeType: newRssItunesEpisodeType(findElementOrNull(element, "itunes:episodeType")),
author: findElementOrNull(element, "itunes:author")?.text?.trim(), author: findElementOrNull(element, "itunes:author")?.text?.trim(),
summary: findElementOrNull(element, "itunes:summary")?.text?.trim(), summary: findElementOrNull(element, "itunes:summary")?.text?.trim(),
explicit: explicitStr == null
? null
: explicitStr == "yes" || explicitStr == "true",
explicit: parseBoolLiteral(element, "itunes:explicit"),
subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(), subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(),
keywords: findElementOrNull(element, "itunes:keywords")?.text?.split(",")?.map((keyword) => keyword.trim())?.toList(), keywords: findElementOrNull(element, "itunes:keywords")?.text?.split(",")?.map((keyword) => keyword.trim())?.toList(),
image: RssItunesImage.parse(findElementOrNull(element, "itunes:image")), image: RssItunesImage.parse(findElementOrNull(element, "itunes:image")),
category: RssItunesCategory.parse( category: RssItunesCategory.parse(
findElementOrNull(element, "itunes:category")), findElementOrNull(element, "itunes:category")),
block: parseBoolLiteral(element, "itunes:block"),
); );
} }
} }


+ 29
- 25
lib/domain/rss_itunes.dart Ver fichero

@@ -4,62 +4,66 @@ import 'package:webfeed/util/helpers.dart';


import 'rss_itunes_category.dart'; import 'rss_itunes_category.dart';
import 'rss_itunes_image.dart'; import 'rss_itunes_image.dart';
import 'rss_itunes_owner.dart';
import 'rss_itunes_type.dart';


class RssItunes { class RssItunes {
final String author; final String author;
final String summary; final String summary;
final bool explicit; final bool explicit;
final String title;
final String subtitle; final String subtitle;
final RssItunesOwner owner; final RssItunesOwner owner;
final List<String> keywords; final List<String> keywords;
final RssItunesImage image; final RssItunesImage image;
final RssItunesCategory category;
final List<RssItunesCategory> categories;
final RssItunesType type;
final String newFeedUrl;
final bool block;
final bool complete;


RssItunes({ RssItunes({
this.author, this.author,
this.summary, this.summary,
this.explicit, this.explicit,
this.title,
this.subtitle, this.subtitle,
this.owner, this.owner,
this.keywords, this.keywords,
this.image, this.image,
this.category,
this.categories,
this.type,
this.newFeedUrl,
this.block,
this.complete,
}); });


factory RssItunes.parse(XmlElement element) { factory RssItunes.parse(XmlElement element) {
if (element == null) { if (element == null) {
return null; return null;
} }
var explicitStr =
findElementOrNull(element, "itunes:explicit")?.text?.toLowerCase()?.trim();

return RssItunes( return RssItunes(
author: findElementOrNull(element, "itunes:author")?.text?.trim(), author: findElementOrNull(element, "itunes:author")?.text?.trim(),
summary: findElementOrNull(element, "itunes:summary")?.text?.trim(), summary: findElementOrNull(element, "itunes:summary")?.text?.trim(),
explicit: explicitStr == null
? null
: explicitStr == "yes" || explicitStr == "true",
explicit: parseBoolLiteral(element, "itunes:explicit"),
title: findElementOrNull(element, "itunes:title")?.text?.trim(),
subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(), subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(),
owner: RssItunesOwner.parse(findElementOrNull(element, "itunes:owner")), owner: RssItunesOwner.parse(findElementOrNull(element, "itunes:owner")),
keywords: findElementOrNull(element, "itunes:keywords")?.text?.split(",")?.map((keyword) => keyword.trim())?.toList(),
keywords: findElementOrNull(element, "itunes:keywords")
?.text
?.split(",")
?.map((keyword) => keyword.trim())
?.toList(),
image: RssItunesImage.parse(findElementOrNull(element, "itunes:image")), image: RssItunesImage.parse(findElementOrNull(element, "itunes:image")),
category: RssItunesCategory.parse(
findElementOrNull(element, "itunes:category")),
categories: findAllDirectElementsOrNull(element, "itunes:category")
.map((ele) => RssItunesCategory.parse(ele))
.toList(),
type: newRssItunesType(findElementOrNull(element, "itunes:type")),
newFeedUrl:
findElementOrNull(element, "itunes:new-feed-url")?.text?.trim(),
block: parseBoolLiteral(element, "itunes:block"),
complete: parseBoolLiteral(element, "itunes:complete"),
); );
} }
} }


class RssItunesOwner {
final String name;
final String email;

RssItunesOwner({this.name, this.email});

factory RssItunesOwner.parse(XmlElement element) {
if (element == null) return null;
return RssItunesOwner(
name: findElementOrNull(element, "itunes:name")?.text?.trim(),
email: findElementOrNull(element, "itunes:email")?.text?.trim(),
);
}
}

+ 1
- 1
lib/domain/rss_itunes_category.dart Ver fichero

@@ -11,7 +11,7 @@ class RssItunesCategory {


Iterable<XmlElement> subCategories; Iterable<XmlElement> subCategories;
try { try {
subCategories = element.findAllElements("itunes:category");
subCategories = element.findElements("itunes:category");
} on StateError { } on StateError {
subCategories = null; subCategories = null;
} }


+ 19
- 0
lib/domain/rss_itunes_episode_type.dart Ver fichero

@@ -0,0 +1,19 @@
import 'package:xml/xml.dart';

enum RssItunesEpisodeType {full, trailer, bonus}

RssItunesEpisodeType newRssItunesEpisodeType(XmlElement element) {
// "full" is default type
if (element == null) return RssItunesEpisodeType.full;

switch (element.text) {
case "full":
return RssItunesEpisodeType.full;
case "trailer":
return RssItunesEpisodeType.trailer;
case "bonus":
return RssItunesEpisodeType.bonus;
default:
return null;
}
}

+ 18
- 0
lib/domain/rss_itunes_owner.dart Ver fichero

@@ -0,0 +1,18 @@
import 'package:xml/xml.dart';

import '../util/helpers.dart';

class RssItunesOwner {
final String name;
final String email;

RssItunesOwner({this.name, this.email});

factory RssItunesOwner.parse(XmlElement element) {
if (element == null) return null;
return RssItunesOwner(
name: findElementOrNull(element, "itunes:name")?.text?.trim(),
email: findElementOrNull(element, "itunes:email")?.text?.trim(),
);
}
}

+ 17
- 0
lib/domain/rss_itunes_type.dart Ver fichero

@@ -0,0 +1,17 @@
import 'package:xml/xml.dart';

enum RssItunesType { episodic, serial }

RssItunesType newRssItunesType(XmlElement element) {
// "episodic" is default type
if (element == null) return RssItunesType.episodic;

switch (element.text) {
case "episodic":
return RssItunesType.episodic;
case "serial":
return RssItunesType.serial;
default:
return null;
}
}

+ 18
- 1
lib/util/helpers.dart Ver fichero

@@ -2,10 +2,27 @@ import 'dart:core';


import 'package:xml/xml.dart'; import 'package:xml/xml.dart';


XmlElement findElementOrNull(XmlElement element, String name, {String namespace}) {
XmlElement findElementOrNull(XmlElement element, String name,
{String namespace}) {
try { try {
return element.findAllElements(name, namespace: namespace).first; return element.findAllElements(name, namespace: namespace).first;
} on StateError { } on StateError {
return null; return null;
} }
} }

List<XmlElement> findAllDirectElementsOrNull(XmlElement element, String name,
{String namespace}) {
try {
return element.findElements(name, namespace: namespace).toList();
} on StateError {
return null;
}
}

bool parseBoolLiteral(XmlElement element, String tagName) {
var v = findElementOrNull(element, tagName)?.text?.toLowerCase()?.trim();
if (v == null) return null;
return ["yes", "true"].contains(v);
}


+ 67
- 25
test/rss_test.dart Ver fichero

@@ -4,6 +4,9 @@ import 'dart:io';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:webfeed/webfeed.dart'; import 'package:webfeed/webfeed.dart';


import 'package:webfeed/domain/rss_itunes_type.dart';
import 'package:webfeed/domain/rss_itunes_episode_type.dart';

void main() { void main() {
test("parse Invalid.xml", () { test("parse Invalid.xml", () {
var xmlString = new File("test/xml/Invalid.xml").readAsStringSync(); var xmlString = new File("test/xml/Invalid.xml").readAsStringSync();
@@ -19,7 +22,8 @@ void main() {
var feed = new RssFeed.parse(xmlString); var feed = new RssFeed.parse(xmlString);


expect(feed.title, "News - Foo bar News"); expect(feed.title, "News - Foo bar News");
expect(feed.description, "Foo bar News and Updates feed provided by Foo bar, Inc.");
expect(feed.description,
"Foo bar News and Updates feed provided by Foo bar, Inc.");
expect(feed.link, "https://foo.bar.news/"); expect(feed.link, "https://foo.bar.news/");
expect(feed.author, "hello@world.net"); expect(feed.author, "hello@world.net");
expect(feed.language, "en-US"); expect(feed.language, "en-US");
@@ -62,8 +66,10 @@ void main() {


expect(feed.items.length, 2); expect(feed.items.length, 2);


expect(feed.items.first.title, "The standard Lorem Ipsum passage, used since the 1500s");
expect(feed.items.first.description, "Lorem ipsum dolor sit amet, consectetur adipiscing elit");
expect(feed.items.first.title,
"The standard Lorem Ipsum passage, used since the 1500s");
expect(feed.items.first.description,
"Lorem ipsum dolor sit amet, consectetur adipiscing elit");
expect(feed.items.first.link, "https://foo.bar.news/1"); expect(feed.items.first.link, "https://foo.bar.news/1");
expect(feed.items.first.guid, "https://foo.bar.news/1?guid"); expect(feed.items.first.guid, "https://foo.bar.news/1?guid");
expect(feed.items.first.pubDate, "Mon, 26 Mar 2018 14:00:00 PDT"); expect(feed.items.first.pubDate, "Mon, 26 Mar 2018 14:00:00 PDT");
@@ -73,22 +79,26 @@ void main() {
expect(feed.items.first.source.url, "https://foo.bar.news/1?source"); expect(feed.items.first.source.url, "https://foo.bar.news/1?source");
expect(feed.items.first.source.value, "Foo Bar"); expect(feed.items.first.source.value, "Foo Bar");
expect(feed.items.first.comments, "https://foo.bar.news/1/comments"); expect(feed.items.first.comments, "https://foo.bar.news/1/comments");
expect(feed.items.first.enclosure.url, "http://www.scripting.com/mp3s/weatherReportSuite.mp3");
expect(feed.items.first.enclosure.url,
"http://www.scripting.com/mp3s/weatherReportSuite.mp3");
expect(feed.items.first.enclosure.length, 12216320); expect(feed.items.first.enclosure.length, 12216320);
expect(feed.items.first.enclosure.type, "audio/mpeg"); expect(feed.items.first.enclosure.type, "audio/mpeg");


expect(feed.items.first.content.value, "<img width=\"1000\" height=\"690\" src=\"https://test.com/image_link\"/> Test content<br />");
expect(feed.items.first.content.images.first, "https://test.com/image_link");
expect(feed.items.first.content.value,
"<img width=\"1000\" height=\"690\" src=\"https://test.com/image_link\"/> Test content<br />");
expect(
feed.items.first.content.images.first, "https://test.com/image_link");
}); });
test("parse RSS-Media.xml", (){
test("parse RSS-Media.xml", () {
var xmlString = new File("test/xml/RSS-Media.xml").readAsStringSync(); var xmlString = new File("test/xml/RSS-Media.xml").readAsStringSync();


var feed = new RssFeed.parse(xmlString); var feed = new RssFeed.parse(xmlString);
expect(feed.title, "Song Site"); expect(feed.title, "Song Site");
expect(feed.description, "Media RSS example with new fields added in v1.5.0");
expect(
feed.description, "Media RSS example with new fields added in v1.5.0");


expect(feed.items.length, 1); expect(feed.items.length, 1);
var item = feed.items.first; var item = feed.items.first;
expect(item.title, null); expect(item.title, null);
expect(item.link, "http://www.foo.com"); expect(item.link, "http://www.foo.com");
@@ -118,7 +128,8 @@ void main() {
expect(mediaCredit.scheme, "urn:yvs"); expect(mediaCredit.scheme, "urn:yvs");
expect(mediaCredit.value, "copyright holder of the entity"); expect(mediaCredit.value, "copyright holder of the entity");


expect(item.media.category.scheme, "http://search.yahoo.com/mrss/category_ schema");
expect(item.media.category.scheme,
"http://search.yahoo.com/mrss/category_ schema");
expect(item.media.category.label, "Music"); expect(item.media.category.label, "Music");
expect(item.media.category.value, "music/artist/album/song"); expect(item.media.category.value, "music/artist/album/song");


@@ -129,10 +140,11 @@ void main() {
expect(item.media.title.value, "The Judy's -- The Moo Song"); expect(item.media.title.value, "The Judy's -- The Moo Song");


expect(item.media.description.type, "plain"); expect(item.media.description.type, "plain");
expect(item.media.description.value, "This was some really bizarre band I listened to as a young lad.");
expect(item.media.description.value,
"This was some really bizarre band I listened to as a young lad.");

expect(item.media.keywords, "kitty, cat, big dog, yarn, fluffy"); expect(item.media.keywords, "kitty, cat, big dog, yarn, fluffy");
expect(item.media.thumbnails.length, 2); expect(item.media.thumbnails.length, 2);
var mediaThumbnail = item.media.thumbnails.first; var mediaThumbnail = item.media.thumbnails.first;
expect(mediaThumbnail.url, "http://www.foo.com/keyframe1.jpg"); expect(mediaThumbnail.url, "http://www.foo.com/keyframe1.jpg");
@@ -179,7 +191,8 @@ void main() {
expect(item.media.embed.height, 323); expect(item.media.embed.height, 323);
expect(item.media.embed.params.length, 5); expect(item.media.embed.params.length, 5);
expect(item.media.embed.params.first.name, "type"); expect(item.media.embed.params.first.name, "type");
expect(item.media.embed.params.first.value, "application/x-shockwave-flash");
expect(
item.media.embed.params.first.value, "application/x-shockwave-flash");


expect(item.media.responses.length, 2); expect(item.media.responses.length, 2);
expect(item.media.responses.first, "http://www.response1.com"); expect(item.media.responses.first, "http://www.response1.com");
@@ -195,7 +208,8 @@ void main() {
expect(item.media.prices.length, 2); expect(item.media.prices.length, 2);
expect(item.media.prices.first.price, 19.99); expect(item.media.prices.first.price, 19.99);
expect(item.media.prices.first.type, "rent"); expect(item.media.prices.first.type, "rent");
expect(item.media.prices.first.info, "http://www.dummy.jp/package_info.html");
expect(
item.media.prices.first.info, "http://www.dummy.jp/package_info.html");
expect(item.media.prices.first.currency, "EUR"); expect(item.media.prices.first.currency, "EUR");


expect(item.media.license.type, "text/html"); expect(item.media.license.type, "text/html");
@@ -214,7 +228,7 @@ void main() {
expect(item.media.scenes.first.startTime, "00:15"); expect(item.media.scenes.first.startTime, "00:15");
expect(item.media.scenes.first.endTime, "00:45"); expect(item.media.scenes.first.endTime, "00:45");
}); });
test("parse RSS-DC.xml", (){
test("parse RSS-DC.xml", () {
var xmlString = File("test/xml/RSS-DC.xml").readAsStringSync(); var xmlString = File("test/xml/RSS-DC.xml").readAsStringSync();


var feed = RssFeed.parse(xmlString); var feed = RssFeed.parse(xmlString);
@@ -256,7 +270,7 @@ void main() {
var xmlString = File("test/xml/RSS-Empty.xml").readAsStringSync(); var xmlString = File("test/xml/RSS-Empty.xml").readAsStringSync();


var feed = RssFeed.parse(xmlString); var feed = RssFeed.parse(xmlString);
expect(feed.title, null); expect(feed.title, null);
expect(feed.description, null); expect(feed.description, null);
expect(feed.link, null); expect(feed.link, null);
@@ -304,22 +318,50 @@ void main() {
expect(feed.itunes.author, "Changelog Media"); expect(feed.itunes.author, "Changelog Media");
expect(feed.itunes.summary, "Foo"); expect(feed.itunes.summary, "Foo");
expect(feed.itunes.explicit, false); expect(feed.itunes.explicit, false);
expect(feed.itunes.image.href, "https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357");
expect(feed.itunes.keywords, "go,golang,open source,software,development".split(","));
expect(feed.itunes.image.href,
"https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357");
expect(feed.itunes.keywords,
"go,golang,open source,software,development".split(","));
expect(feed.itunes.owner.name, "Changelog Media"); expect(feed.itunes.owner.name, "Changelog Media");
expect(feed.itunes.owner.email, "editors@changelog.com"); expect(feed.itunes.owner.email, "editors@changelog.com");
expect(feed.itunes.category.category, "Technology");
expect(feed.itunes.category.subCategories, ["Software How-To", "Tech News"]);
expect(
Set.from([
feed.itunes.categories[0].category,
feed.itunes.categories[1].category
]),
["Technology", "Foo"]);
for (var category in feed.itunes.categories) {
switch (category.category) {
case "Foo":
expect(category.subCategories, ["Bar", "Baz"]);
break;
case "Technology":
expect(category.subCategories, ["Software How-To", "Tech News"]);
break;
}
}
expect(feed.itunes.title, "Go Time");
expect(feed.itunes.type, RssItunesType.serial);
expect(feed.itunes.newFeedUrl, "wubawuba");
expect(feed.itunes.block, true);
expect(feed.itunes.complete, true);


var item = feed.items[0]; var item = feed.items[0];
expect(item.itunes.episodeType, "full");
expect(item.itunes.episodeType, RssItunesEpisodeType.full);
expect(item.itunes.episode, 1); expect(item.itunes.episode, 1);
expect(item.itunes.image.href, "https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357");
expect(item.itunes.season, 1);
expect(item.itunes.image.href,
"https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357");
expect(item.itunes.duration, Duration(minutes: 32, seconds: 30)); expect(item.itunes.duration, Duration(minutes: 32, seconds: 30));
expect(item.itunes.explicit, false); expect(item.itunes.explicit, false);
expect(item.itunes.keywords, "go,golang,open source,software,development".split(","));
expect(item.itunes.keywords,
"go,golang,open source,software,development".split(","));
expect(item.itunes.subtitle, "with Erik, Carlisia, and Brian"); expect(item.itunes.subtitle, "with Erik, Carlisia, and Brian");
expect(item.itunes.summary, "Foo"); expect(item.itunes.summary, "Foo");
expect(item.itunes.author, "Erik St. Martin, Carlisia Pinto, and Brian Ketelsen");
expect(item.itunes.author,
"Erik St. Martin, Carlisia Pinto, and Brian Ketelsen");
expect(item.itunes.explicit, false);
expect(item.itunes.title, "awesome title");
expect(item.itunes.block, false);
}); });
} }

+ 12
- 0
test/xml/RSS-Itunes.xml Ver fichero

@@ -1,6 +1,11 @@
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0" nighteye="disabled"> <rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0" nighteye="disabled">
<channel> <channel>
<title>Go Time</title> <title>Go Time</title>
<itunes:title>Go Time</itunes:title>
<itunes:type>serial</itunes:type>
<itunes:block>Yes</itunes:block>
<itunes:complete>TRUE</itunes:complete>
<itunes:new-feed-url>wubawuba</itunes:new-feed-url>
<copyright>All rights reserved</copyright> <copyright>All rights reserved</copyright>
<link>https://changelog.com/gotime</link> <link>https://changelog.com/gotime</link>
<atom:link href="https://changelog.com/gotime/feed" rel="self" type="application/rss+xml"/> <atom:link href="https://changelog.com/gotime/feed" rel="self" type="application/rss+xml"/>
@@ -24,9 +29,16 @@
<itunes:category text="Software How-To"/> <itunes:category text="Software How-To"/>
<itunes:category text="Tech News"/> <itunes:category text="Tech News"/>
</itunes:category> </itunes:category>
<itunes:category text="Foo">
<itunes:category text="Bar"/>
<itunes:category text="Baz"/>
</itunes:category>
<item> <item>
<itunes:title>awesome title</itunes:title>
<itunes:episodeType>full</itunes:episodeType> <itunes:episodeType>full</itunes:episodeType>
<itunes:episode>1</itunes:episode> <itunes:episode>1</itunes:episode>
<itunes:season>1</itunes:season>
<itunes:block>xxx</itunes:block>
<itunes:image href="https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357"/> <itunes:image href="https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357"/>
<itunes:duration>32:30</itunes:duration> <itunes:duration>32:30</itunes:duration>
<itunes:explicit>no</itunes:explicit> <itunes:explicit>no</itunes:explicit>


Cargando…
Cancelar
Guardar