Merge pull request #723 from FireMasterK/uploader-avatar

Extract Uploader's Avatar on YouTube and PeerTube
This commit is contained in:
Tobi 2021-09-03 18:13:59 +02:00 committed by GitHub
commit c8037f5e00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1392 additions and 16 deletions

View File

@ -79,6 +79,12 @@ public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
return "";
}
@Nullable
@Override
public String getUploaderAvatarUrl() {
return null;
}
@Override
public boolean isUploaderVerified() throws ParsingException {
return false;

View File

@ -4,6 +4,8 @@ import com.grack.nanojson.JsonObject;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper;
import javax.annotation.Nullable;
public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
private final JsonObject discograph;
@ -18,6 +20,12 @@ public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInf
return discograph.getString("band_name");
}
@Nullable
@Override
public String getUploaderAvatarUrl() {
return null;
}
@Override
public String getName() {
return discograph.getString("title");

View File

@ -8,6 +8,7 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import javax.annotation.Nullable;
import java.io.IOException;
@ -53,6 +54,12 @@ public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoI
return "";
}
@Nullable
@Override
public String getUploaderAvatarUrl() {
return null;
}
/**
* Each track can have its own cover art. Therefore, unless a substitute is provided,
* the thumbnail is extracted using a stream extractor.

View File

@ -3,6 +3,8 @@ package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem
import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import javax.annotation.Nullable;
public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
private final Element resultInfo, searchResult;
@ -24,6 +26,12 @@ public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoIte
}
}
@Nullable
@Override
public String getUploaderAvatarUrl() {
return null;
}
@Override
public String getName() throws ParsingException {
return resultInfo.getElementsByClass("heading").text();

View File

@ -73,6 +73,12 @@ public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor
return "https://media.ccc.de/c/" + conferenceInfo.getString("slug");
}
@Nullable
@Override
public String getUploaderAvatarUrl() {
return null;
}
@Override
public boolean isUploaderVerified() throws ParsingException {
return false;

View File

@ -68,6 +68,12 @@ public class MediaCCCRecentKioskExtractor implements StreamInfoItemExtractor {
.getUrl(); // web URL
}
@Nullable
@Override
public String getUploaderAvatarUrl() {
return null;
}
@Override
public boolean isUploaderVerified() throws ParsingException {
return false;

View File

@ -46,6 +46,12 @@ public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor
return event.getString("conference_url");
}
@Nullable
@Override
public String getUploaderAvatarUrl() {
return null;
}
@Override
public boolean isUploaderVerified() throws ParsingException {
return false;

View File

@ -9,6 +9,8 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.utils.JsonUtils;
import javax.annotation.Nullable;
public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
protected final JsonObject item;
@ -54,6 +56,16 @@ public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor
.fromId("accounts/" + name + "@" + host, baseUrl).getUrl();
}
@Nullable
@Override
public String getUploaderAvatarUrl() {
final JsonObject account = item.getObject("account");
if (account.has("avatar") && !account.isNull("avatar")) {
return baseUrl + account.getObject("avatar").getString("path");
}
return null;
}
@Override
public boolean isUploaderVerified() throws ParsingException {
return false;

View File

@ -7,6 +7,8 @@ import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper;
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
import org.schabi.newpipe.extractor.stream.StreamType;
import javax.annotation.Nullable;
import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING;
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
@ -43,6 +45,12 @@ public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtracto
return replaceHttpWithHttps(itemObject.getObject("user").getString("permalink_url"));
}
@Nullable
@Override
public String getUploaderAvatarUrl() {
return null;
}
@Override
public boolean isUploaderVerified() throws ParsingException {
return itemObject.getObject("user").getBoolean("verified");

View File

@ -50,6 +50,12 @@ public class YoutubeFeedInfoItemExtractor implements StreamInfoItemExtractor {
return entryElement.select("author > uri").first().text();
}
@Nullable
@Override
public String getUploaderAvatarUrl() throws ParsingException {
return null;
}
@Override
public boolean isUploaderVerified() throws ParsingException {
return false;

View File

@ -9,6 +9,7 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory;
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.utils.JsonUtils;
import org.schabi.newpipe.extractor.utils.Utils;
import javax.annotation.Nullable;
@ -40,7 +41,7 @@ import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
*/
public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
private JsonObject videoInfo;
private final JsonObject videoInfo;
private final TimeAgoParser timeAgoParser;
private StreamType cachedStreamType;
@ -162,6 +163,18 @@ public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
return url;
}
@Nullable
@Override
public String getUploaderAvatarUrl() throws ParsingException {
if(videoInfo.has("channelThumbnailSupportedRenderers")) {
return JsonUtils.getArray(videoInfo, "channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail.thumbnails")
.getObject(0).getString("url");
}
return null;
}
@Override
public boolean isUploaderVerified() throws ParsingException {
return YoutubeParsingHelper.isVerified(videoInfo.getArray("ownerBadges"));

View File

@ -39,6 +39,7 @@ public class StreamInfoItem extends InfoItem {
private long duration = -1;
private String uploaderUrl = null;
private String uploaderAvatarUrl = null;
private boolean uploaderVerified = false;
public StreamInfoItem(int serviceId, String url, String name, StreamType streamType) {
@ -82,6 +83,15 @@ public class StreamInfoItem extends InfoItem {
this.uploaderUrl = uploaderUrl;
}
@Nullable
public String getUploaderAvatarUrl() {
return uploaderAvatarUrl;
}
public void setUploaderAvatarUrl(final String uploaderAvatarUrl) {
this.uploaderAvatarUrl = uploaderAvatarUrl;
}
@Nullable
public String getTextualUploadDate() {
return textualUploadDate;

View File

@ -71,6 +71,15 @@ public interface StreamInfoItemExtractor extends InfoItemExtractor {
String getUploaderUrl() throws ParsingException;
/**
* Get the uploader's avatar
*
* @return The uploader's avatar url or {@code null} if not provided by the service.
* @throws ParsingException if there is an error in the extraction
*/
@Nullable
String getUploaderAvatarUrl() throws ParsingException;
/**
* Whether the uploader has been verified by the service's provider.
* If there is no verification implemented, return <code>false</code>.

View File

@ -91,6 +91,11 @@ public class StreamInfoItemsCollector extends InfoItemsCollector<StreamInfoItem,
} catch (Exception e) {
addError(e);
}
try {
resultItem.setUploaderAvatarUrl(extractor.getUploaderAvatarUrl());
} catch (Exception e) {
addError(e);
}
try {
resultItem.setUploaderVerified(extractor.isUploaderVerified());
} catch (Exception e) {

View File

@ -48,6 +48,11 @@ public final class DefaultTests {
assertExpectedLinkType(expectedService, uploaderUrl, LinkType.CHANNEL);
}
final String uploaderAvatarUrl = streamInfoItem.getUploaderAvatarUrl();
if (!isNullOrEmpty(uploaderAvatarUrl)) {
assertIsSecureUrl(uploaderAvatarUrl);
}
assertExpectedLinkType(expectedService, streamInfoItem.getUrl(), LinkType.STREAM);
if (!isNullOrEmpty(streamInfoItem.getTextualUploadDate())) {

View File

@ -1,24 +1,18 @@
package org.schabi.newpipe.extractor.services.youtube.search;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.schabi.newpipe.downloader.DownloaderFactory;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.MetaInfo;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.*;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.services.DefaultSearchExtractorTest;
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import javax.annotation.Nullable;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
@ -29,14 +23,11 @@ import java.util.Random;
import static java.util.Collections.singletonList;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmptyErrors;
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
import static org.schabi.newpipe.extractor.services.DefaultTests.assertNoDuplicatedItems;
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.CHANNELS;
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.PLAYLISTS;
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.VIDEOS;
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.*;
public class YoutubeSearchExtractorTest {
@ -320,4 +311,36 @@ public class YoutubeSearchExtractorTest {
assertTrue(verified);
}
}
public static class VideoUploaderAvatar extends DefaultSearchExtractorTest {
private static SearchExtractor extractor;
private static final String QUERY = "sidemen";
@BeforeClass
public static void setUp() throws Exception {
YoutubeParsingHelper.resetClientVersionAndKey();
YoutubeParsingHelper.setNumberGenerator(new Random(1));
NewPipe.init(new DownloaderFactory().getDownloader(RESOURCE_PATH + "video_uploader_avatar"));
extractor = YouTube.getSearchExtractor(QUERY, singletonList(VIDEOS), "");
extractor.fetchPage();
}
@Override public SearchExtractor extractor() { return extractor; }
@Override public StreamingService expectedService() { return YouTube; }
@Override public String expectedName() { return QUERY; }
@Override public String expectedId() { return QUERY; }
@Override public String expectedUrlContains() { return "youtube.com/results?search_query=" + QUERY; }
@Override public String expectedOriginalUrlContains() { return "youtube.com/results?search_query=" + QUERY; }
@Override public String expectedSearchString() { return QUERY; }
@Nullable @Override public String expectedSearchSuggestion() { return null; }
@Override public InfoItem.InfoType expectedInfoItemType() { return InfoItem.InfoType.STREAM; }
@Test
public void testUploaderAvatar() throws IOException, ExtractionException {
final List<InfoItem> items = extractor.getInitialPage().getItems();
for (final InfoItem item : items) {
assertNotNull(((StreamInfoItem) item).getUploaderAvatarUrl());
}
}
}
}