Add bandcamp playlists (albums)

This commit is contained in:
Fynn Godau 2019-12-22 02:55:54 +01:00
parent d05b14ae48
commit ba700bfb3e
12 changed files with 330 additions and 20 deletions

View File

@ -11,9 +11,11 @@ import org.schabi.newpipe.extractor.linkhandler.*;
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor; import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
import org.schabi.newpipe.extractor.search.SearchExtractor; import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampChannelExtractor; import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampChannelExtractor;
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampPlaylistExtractor;
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampSearchExtractor; import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampSearchExtractor;
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor; import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor;
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampChannelLinkHandlerFactory; import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampChannelLinkHandlerFactory;
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampPlaylistLinkHandlerFactory;
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampSearchQueryHandlerFactory; import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampSearchQueryHandlerFactory;
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampStreamLinkHandlerFactory; import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampStreamLinkHandlerFactory;
import org.schabi.newpipe.extractor.stream.StreamExtractor; import org.schabi.newpipe.extractor.stream.StreamExtractor;
@ -47,7 +49,7 @@ public class BandcampService extends StreamingService {
@Override @Override
public ListLinkHandlerFactory getPlaylistLHFactory() { public ListLinkHandlerFactory getPlaylistLHFactory() {
return null; return new BandcampPlaylistLinkHandlerFactory();
} }
@Override @Override
@ -87,7 +89,7 @@ public class BandcampService extends StreamingService {
@Override @Override
public PlaylistExtractor getPlaylistExtractor(ListLinkHandler linkHandler) throws ExtractionException { public PlaylistExtractor getPlaylistExtractor(ListLinkHandler linkHandler) throws ExtractionException {
return null; return new BandcampPlaylistExtractor(this, linkHandler);
} }
@Override @Override

View File

@ -1,3 +1,5 @@
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
package org.schabi.newpipe.extractor.services.bandcamp.extractors; package org.schabi.newpipe.extractor.services.bandcamp.extractors;
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor; import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;

View File

@ -0,0 +1,133 @@
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
import javax.annotation.Nonnull;
import java.io.IOException;
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getJSONFromJavaScriptVariables;
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor.getAlbumInfoJson;
public class BandcampPlaylistExtractor extends PlaylistExtractor {
private Document document;
private JSONObject albumJson;
private JSONArray trackInfo;
private String name;
public BandcampPlaylistExtractor(StreamingService service, ListLinkHandler linkHandler) {
super(service, linkHandler);
}
@Override
public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException {
String html = downloader.get(getLinkHandler().getUrl()).responseBody();
document = Jsoup.parse(html);
albumJson = getAlbumInfoJson(html);
trackInfo = albumJson.getJSONArray("trackinfo");
try {
name = getJSONFromJavaScriptVariables(html, "EmbedData").getString("album_title");
} catch (JSONException e) {
throw new ParsingException("Faulty JSON; page likely does not contain album data", e);
} catch (ArrayIndexOutOfBoundsException e) {
throw new ParsingException("JSON does not exist", e);
}
if (trackInfo.length() <= 1) {
// In this case, we are actually viewing a track page!
throw new ExtractionException("Page is actually a track, not an album");
}
}
@Override
public String getThumbnailUrl() throws ParsingException {
try {
return document.getElementsByAttributeValue("property", "og:image").get(0).attr("content");
} catch (NullPointerException e) {
return "";
}
}
@Override
public String getBannerUrl() {
return "";
}
@Override
public String getUploaderUrl() throws ParsingException {
String[] parts = getUrl().split("/");
// https: (/) (/) * .bandcamp.com (/) and leave out the rest
return "https://" + parts[2] + "/";
}
@Override
public String getUploaderName() {
return albumJson.getString("artist");
}
@Override
public String getUploaderAvatarUrl() {
try {
return document.getElementsByClass("band-photo").first().attr("src");
} catch (NullPointerException e) {
return "";
}
}
@Override
public long getStreamCount() {
return trackInfo.length();
}
@Nonnull
@Override
public InfoItemsPage<StreamInfoItem> getInitialPage() throws ExtractionException {
StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
for (int i = 0; i < trackInfo.length(); i++) {
JSONObject track = trackInfo.getJSONObject(i);
collector.commit(new BandcampStreamInfoItemExtractor(
track.getString("title"),
getUploaderUrl() + track.getString("title_link"),
"",
"",
track.getLong("duration")
));
}
return new InfoItemsPage<>(collector, null);
}
@Nonnull
@Override
public String getName() throws ParsingException {
return name;
}
@Override
public String getNextPageUrl() {
return null;
}
@Override
public InfoItemsPage<StreamInfoItem> getPage(String pageUrl) {
return null;
}
}

View File

@ -0,0 +1,40 @@
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
public class BandcampPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
private String title, artist, url, cover;
public BandcampPlaylistInfoItemExtractor(String title, String artist, String url, String cover) {
this.title = title;
this.artist = artist;
this.url = url;
this.cover = cover;
}
@Override
public String getUploaderName() {
return artist;
}
@Override
public long getStreamCount() {
return -1;
}
@Override
public String getName() {
return title;
}
@Override
public String getUrl() {
return url;
}
@Override
public String getThumbnailUrl() {
return cover;
}
}

View File

@ -74,8 +74,7 @@ public class BandcampSearchExtractor extends SearchExtractor {
case "ALBUM": case "ALBUM":
String artist = subhead.split(" by")[0]; String artist = subhead.split(" by")[0];
//searchResults.add(new Album(heading, artist, url, image)); collector.commit(new BandcampPlaylistInfoItemExtractor(heading, artist, url, image));
//collector.commit Playlist with heading, artist, url, image
break; break;
case "TRACK": case "TRACK":

View File

@ -103,7 +103,11 @@ public class BandcampStreamExtractor extends StreamExtractor {
@Nonnull @Nonnull
@Override @Override
public String getThumbnailUrl() throws ParsingException { public String getThumbnailUrl() throws ParsingException {
try {
return document.getElementsByAttributeValue("property", "og:image").get(0).attr("content"); return document.getElementsByAttributeValue("property", "og:image").get(0).attr("content");
} catch (NullPointerException e) {
return "";
}
} }
@Nonnull @Nonnull

View File

@ -15,48 +15,54 @@ public class BandcampStreamInfoItemExtractor implements StreamInfoItemExtractor
private String url; private String url;
private String cover; private String cover;
private String artist; private String artist;
private long duration;
public BandcampStreamInfoItemExtractor(String title, String url, String cover, String artist) { public BandcampStreamInfoItemExtractor(String title, String url, String cover, String artist) {
this(title, url, cover, artist, -1);
}
public BandcampStreamInfoItemExtractor(String title, String url, String cover, String artist, long duration) {
this.title = title; this.title = title;
this.url = url; this.url = url;
this.cover = cover; this.cover = cover;
this.artist = artist; this.artist = artist;
this.duration = duration;
} }
@Override @Override
public StreamType getStreamType() throws ParsingException { public StreamType getStreamType() {
return StreamType.AUDIO_STREAM; return StreamType.AUDIO_STREAM;
} }
@Override @Override
public long getDuration() throws ParsingException { public long getDuration() {
return duration;
}
@Override
public long getViewCount() {
return -1; return -1;
} }
@Override @Override
public long getViewCount() throws ParsingException { public String getUploaderName() {
return -1;
}
@Override
public String getUploaderName() throws ParsingException {
return artist; return artist;
} }
@Override @Override
public String getUploaderUrl() throws ParsingException { public String getUploaderUrl() {
return null; return null;
} }
@Nullable @Nullable
@Override @Override
public String getTextualUploadDate() throws ParsingException { public String getTextualUploadDate() {
return null; // TODO return null; // TODO
} }
@Nullable @Nullable
@Override @Override
public DateWrapper getUploadDate() throws ParsingException { public DateWrapper getUploadDate() {
return null; return null;
} }

View File

@ -0,0 +1,31 @@
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
package org.schabi.newpipe.extractor.services.bandcamp.linkHandler;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
import java.util.List;
/**
* Just as with streams, the album ids are essentially useless for us.
*/
public class BandcampPlaylistLinkHandlerFactory extends ListLinkHandlerFactory {
@Override
public String getId(String url) throws ParsingException {
return getUrl(url);
}
@Override
public String getUrl(String url, List<String> contentFilter, String sortFilter) throws ParsingException {
if (url.endsWith("/"))
url = url.substring(0, url.length() - 1);
url = url.replace("http://", "https://").toLowerCase();
return url;
}
@Override
public boolean onAcceptUrl(String url) throws ParsingException {
return getUrl(url).matches("https?://.+\\..+/album/.+");
}
}

View File

@ -1,3 +1,5 @@
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
package org.schabi.newpipe.extractor.services.bandcamp; package org.schabi.newpipe.extractor.services.bandcamp;
import org.junit.BeforeClass; import org.junit.BeforeClass;

View File

@ -0,0 +1,29 @@
package org.schabi.newpipe.extractor.services.bandcamp;
import org.junit.BeforeClass;
import org.junit.Test;
import org.schabi.newpipe.DownloaderTestImpl;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.schabi.newpipe.extractor.ServiceList.bandcamp;
public class BandcampPlaylistExtractorTest {
@BeforeClass
public static void setUp() {
NewPipe.init(DownloaderTestImpl.getInstance());
}
@Test
public void testCount() throws ExtractionException, IOException {
PlaylistExtractor extractor = bandcamp.getPlaylistExtractor("https://macbenson.bandcamp.com/album/coming-of-age");
extractor.fetchPage();
assertEquals(5, extractor.getStreamCount());
}
}

View File

@ -0,0 +1,45 @@
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
package org.schabi.newpipe.extractor.services.bandcamp;
import org.junit.BeforeClass;
import org.junit.Test;
import org.schabi.newpipe.DownloaderTestImpl;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampPlaylistLinkHandlerFactory;
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampStreamLinkHandlerFactory;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Test for {@link BandcampPlaylistLinkHandlerFactory}
*/
public class BandcampPlaylistLinkHandlerFactoryTest {
private static BandcampPlaylistLinkHandlerFactory linkHandler;
@BeforeClass
public static void setUp() {
linkHandler = new BandcampPlaylistLinkHandlerFactory();
NewPipe.init(DownloaderTestImpl.getInstance());
}
@Test
public void testAcceptUrl() throws ParsingException {
// Tests expecting false
assertFalse(linkHandler.acceptUrl("http://interovgm.com/releases/"));
assertFalse(linkHandler.acceptUrl("https://interovgm.com/releases"));
assertFalse(linkHandler.acceptUrl("http://zachbenson.bandcamp.com"));
assertFalse(linkHandler.acceptUrl("https://bandcamp.com"));
assertFalse(linkHandler.acceptUrl("https://zachbenson.bandcamp.com/"));
assertFalse(linkHandler.acceptUrl("https://zachbenson.bandcamp.com/track/kitchen"));
assertFalse(linkHandler.acceptUrl("https://interovgm.com/track/title"));
// Tests expecting true
assertTrue(linkHandler.acceptUrl("https://powertothequeerkids.bandcamp.com/album/power-to-the-queer-kids"));
assertTrue(linkHandler.acceptUrl("https://zachbenson.bandcamp.com/album/prom"));
}
}

View File

@ -38,7 +38,7 @@ public class BandcampSearchExtractorTest {
* the accordingly named song by Zach Benson * the accordingly named song by Zach Benson
*/ */
@Test @Test
public void testBestFriendsBasement() throws ExtractionException, IOException { public void testStreamSearch() throws ExtractionException, IOException {
SearchExtractor extractor = bandcamp.getSearchExtractor("best friend's basement"); SearchExtractor extractor = bandcamp.getSearchExtractor("best friend's basement");
ListExtractor.InfoItemsPage<InfoItem> page = extractor.getInitialPage(); ListExtractor.InfoItemsPage<InfoItem> page = extractor.getInitialPage();
@ -55,7 +55,7 @@ public class BandcampSearchExtractorTest {
* Tests whether searching bandcamp for "C418" returns the artist's profile * Tests whether searching bandcamp for "C418" returns the artist's profile
*/ */
@Test @Test
public void testC418() throws ExtractionException, IOException { public void testChannelSearch() throws ExtractionException, IOException {
SearchExtractor extractor = bandcamp.getSearchExtractor("C418"); SearchExtractor extractor = bandcamp.getSearchExtractor("C418");
InfoItem c418 = extractor.getInitialPage() InfoItem c418 = extractor.getInitialPage()
.getItems().get(0); .getItems().get(0);
@ -67,4 +67,21 @@ public class BandcampSearchExtractorTest {
assertEquals("https://c418.bandcamp.com", c418.getUrl()); assertEquals("https://c418.bandcamp.com", c418.getUrl());
} }
/**
* Tests whether searching bandcamp for "minecraft volume alpha" returns the corresponding album
*/
@Test
public void testAlbumSearch() throws ExtractionException, IOException {
SearchExtractor extractor = bandcamp.getSearchExtractor("minecraft volume alpha");
InfoItem minecraft = extractor.getInitialPage()
.getItems().get(0);
// C418's artist profile should be the first result, no?
assertEquals("Minecraft - Volume Alpha", minecraft.getName());
assertTrue(minecraft.getThumbnailUrl().endsWith(".jpg"));
assertTrue(minecraft.getThumbnailUrl().contains("f4.bcbits.com/img/"));
assertEquals("https://c418.bandcamp.com/album/minecraft-volume-alpha", minecraft.getUrl());
}
} }