Skip to content

Commit 021da75

Browse files
authored
Merge pull request #232 from fynngodau/dev
Bandcamp support
2 parents cb07ffa + 22fa131 commit 021da75

42 files changed

Lines changed: 3291 additions & 2 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ The following sites are currently supported:
4444
- SoundCloud
4545
- media.ccc.de
4646
- PeerTube (no P2P)
47+
- Bandcamp
4748

4849
## License
4950

extractor/src/main/java/org/schabi/newpipe/extractor/ServiceList.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.schabi.newpipe.extractor;
22

3+
import org.schabi.newpipe.extractor.services.bandcamp.BandcampService;
34
import org.schabi.newpipe.extractor.services.media_ccc.MediaCCCService;
45
import org.schabi.newpipe.extractor.services.peertube.PeertubeService;
56
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudService;
@@ -39,6 +40,7 @@ private ServiceList() {
3940
public static final SoundcloudService SoundCloud;
4041
public static final MediaCCCService MediaCCC;
4142
public static final PeertubeService PeerTube;
43+
public static final BandcampService Bandcamp;
4244

4345
/**
4446
* When creating a new service, put this service in the end of this list,
@@ -49,7 +51,8 @@ private ServiceList() {
4951
YouTube = new YoutubeService(0),
5052
SoundCloud = new SoundcloudService(1),
5153
MediaCCC = new MediaCCCService(2),
52-
PeerTube = new PeertubeService(3)
54+
PeerTube = new PeertubeService(3),
55+
Bandcamp = new BandcampService(4)
5356
));
5457

5558
/**

extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/ListLinkHandlerFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.schabi.newpipe.extractor.utils.Utils;
55

66
import java.util.ArrayList;
7+
import java.util.Collections;
78
import java.util.List;
89

910
public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
@@ -13,7 +14,7 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
1314
///////////////////////////////////
1415

1516
public List<String> getContentFilter(String url) throws ParsingException {
16-
return new ArrayList<>(0);
17+
return Collections.emptyList();
1718
}
1819

1920
public String getSortFilter(String url) throws ParsingException {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
2+
3+
package org.schabi.newpipe.extractor.services.bandcamp;
4+
5+
import org.schabi.newpipe.extractor.StreamingService;
6+
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
7+
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
8+
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
9+
import org.schabi.newpipe.extractor.kiosk.KioskList;
10+
import org.schabi.newpipe.extractor.linkhandler.*;
11+
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
12+
import org.schabi.newpipe.extractor.search.SearchExtractor;
13+
import org.schabi.newpipe.extractor.services.bandcamp.extractors.*;
14+
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.*;
15+
import org.schabi.newpipe.extractor.stream.StreamExtractor;
16+
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
17+
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
18+
19+
import java.util.Collections;
20+
21+
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
22+
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
23+
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor.FEATURED_API_URL;
24+
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor.KIOSK_FEATURED;
25+
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor.KIOSK_RADIO;
26+
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor.RADIO_API_URL;
27+
28+
public class BandcampService extends StreamingService {
29+
30+
public BandcampService(final int id) {
31+
super(id, "Bandcamp", Collections.singletonList(AUDIO));
32+
}
33+
34+
@Override
35+
public String getBaseUrl() {
36+
return BASE_URL;
37+
}
38+
39+
@Override
40+
public LinkHandlerFactory getStreamLHFactory() {
41+
return new BandcampStreamLinkHandlerFactory();
42+
}
43+
44+
@Override
45+
public ListLinkHandlerFactory getChannelLHFactory() {
46+
return new BandcampChannelLinkHandlerFactory();
47+
}
48+
49+
@Override
50+
public ListLinkHandlerFactory getPlaylistLHFactory() {
51+
return new BandcampPlaylistLinkHandlerFactory();
52+
}
53+
54+
@Override
55+
public SearchQueryHandlerFactory getSearchQHFactory() {
56+
return new BandcampSearchQueryHandlerFactory();
57+
}
58+
59+
@Override
60+
public ListLinkHandlerFactory getCommentsLHFactory() {
61+
return null;
62+
}
63+
64+
@Override
65+
public SearchExtractor getSearchExtractor(final SearchQueryHandler queryHandler) {
66+
return new BandcampSearchExtractor(this, queryHandler);
67+
}
68+
69+
@Override
70+
public SuggestionExtractor getSuggestionExtractor() {
71+
return new BandcampSuggestionExtractor(this);
72+
}
73+
74+
@Override
75+
public SubscriptionExtractor getSubscriptionExtractor() {
76+
return null;
77+
}
78+
79+
@Override
80+
public KioskList getKioskList() throws ExtractionException {
81+
82+
KioskList kioskList = new KioskList(this);
83+
84+
try {
85+
kioskList.addKioskEntry((streamingService, url, kioskId) ->
86+
new BandcampFeaturedExtractor(
87+
BandcampService.this,
88+
new BandcampFeaturedLinkHandlerFactory().fromUrl(FEATURED_API_URL), kioskId),
89+
new BandcampFeaturedLinkHandlerFactory(), KIOSK_FEATURED);
90+
91+
kioskList.addKioskEntry((streamingService, url, kioskId) ->
92+
new BandcampRadioExtractor(BandcampService.this,
93+
new BandcampFeaturedLinkHandlerFactory().fromUrl(RADIO_API_URL), kioskId),
94+
new BandcampFeaturedLinkHandlerFactory(), KIOSK_RADIO);
95+
96+
kioskList.setDefaultKiosk(KIOSK_FEATURED);
97+
98+
} catch (final Exception e) {
99+
throw new ExtractionException(e);
100+
}
101+
102+
return kioskList;
103+
}
104+
105+
@Override
106+
public ChannelExtractor getChannelExtractor(final ListLinkHandler linkHandler) {
107+
return new BandcampChannelExtractor(this, linkHandler);
108+
}
109+
110+
@Override
111+
public PlaylistExtractor getPlaylistExtractor(final ListLinkHandler linkHandler) {
112+
return new BandcampPlaylistExtractor(this, linkHandler);
113+
}
114+
115+
@Override
116+
public StreamExtractor getStreamExtractor(final LinkHandler linkHandler) {
117+
if (BandcampExtractorHelper.isRadioUrl(linkHandler.getUrl()))
118+
return new BandcampRadioStreamExtractor(this, linkHandler);
119+
else
120+
return new BandcampStreamExtractor(this, linkHandler);
121+
}
122+
123+
@Override
124+
public CommentsExtractor getCommentsExtractor(ListLinkHandler linkHandler) {
125+
return null;
126+
}
127+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
2+
3+
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
4+
5+
import com.grack.nanojson.JsonArray;
6+
import com.grack.nanojson.JsonObject;
7+
import org.jsoup.Jsoup;
8+
import org.schabi.newpipe.extractor.Page;
9+
import org.schabi.newpipe.extractor.StreamingService;
10+
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
11+
import org.schabi.newpipe.extractor.downloader.Downloader;
12+
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
13+
import org.schabi.newpipe.extractor.exceptions.ParsingException;
14+
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
15+
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
16+
import org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem.BandcampDiscographStreamInfoItemExtractor;
17+
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
18+
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
19+
20+
import javax.annotation.Nonnull;
21+
import java.io.IOException;
22+
23+
public class BandcampChannelExtractor extends ChannelExtractor {
24+
25+
private JsonObject channelInfo;
26+
27+
public BandcampChannelExtractor(final StreamingService service, final ListLinkHandler linkHandler) {
28+
super(service, linkHandler);
29+
}
30+
31+
@Override
32+
public String getAvatarUrl() {
33+
if (channelInfo.getLong("bio_image_id") == 0) return "";
34+
35+
return BandcampExtractorHelper.getImageUrl(channelInfo.getLong("bio_image_id"), false);
36+
}
37+
38+
@Override
39+
public String getBannerUrl() throws ParsingException {
40+
/*
41+
* Why does the mobile endpoint not contain the header?? Or at least not the same one?
42+
* Anyway we're back to querying websites
43+
*/
44+
try {
45+
final String html = getDownloader()
46+
.get(channelInfo.getString("bandcamp_url").replace("http://", "https://"))
47+
.responseBody();
48+
49+
return Jsoup.parse(html)
50+
.getElementById("customHeader")
51+
.getElementsByTag("img")
52+
.first()
53+
.attr("src");
54+
55+
} catch (final IOException | ReCaptchaException e) {
56+
throw new ParsingException("Could not download artist web site", e);
57+
} catch (final NullPointerException e) {
58+
// No banner available
59+
return "";
60+
}
61+
}
62+
63+
/**
64+
* bandcamp stopped providing RSS feeds when appending /feed to any URL
65+
* because too few people used it.
66+
*/
67+
@Override
68+
public String getFeedUrl() {
69+
return null;
70+
}
71+
72+
@Override
73+
public long getSubscriberCount() {
74+
return -1;
75+
}
76+
77+
@Override
78+
public String getDescription() {
79+
return channelInfo.getString("bio");
80+
}
81+
82+
@Override
83+
public String getParentChannelName() {
84+
return null;
85+
}
86+
87+
@Override
88+
public String getParentChannelUrl() {
89+
return null;
90+
}
91+
92+
@Override
93+
public String getParentChannelAvatarUrl() {
94+
return null;
95+
}
96+
97+
@Override
98+
public boolean isVerified() throws ParsingException {
99+
return false;
100+
}
101+
102+
@Nonnull
103+
@Override
104+
public InfoItemsPage<StreamInfoItem> getInitialPage() throws ParsingException {
105+
106+
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
107+
108+
final JsonArray discography = channelInfo.getArray("discography");
109+
110+
for (int i = 0; i < discography.size(); i++) {
111+
// I define discograph as an item that can appear in a discography
112+
final JsonObject discograph = discography.getObject(i);
113+
114+
if (!discograph.getString("item_type").equals("track")) continue;
115+
116+
collector.commit(new BandcampDiscographStreamInfoItemExtractor(discograph, getUrl()));
117+
}
118+
119+
return new InfoItemsPage<>(collector, null);
120+
}
121+
122+
@Override
123+
public InfoItemsPage<StreamInfoItem> getPage(Page page) {
124+
return null;
125+
}
126+
127+
@Override
128+
public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException {
129+
channelInfo = BandcampExtractorHelper.getArtistDetails(getId());
130+
}
131+
132+
@Nonnull
133+
@Override
134+
public String getName() {
135+
return channelInfo.getString("name");
136+
}
137+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
2+
3+
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
4+
5+
import org.jsoup.nodes.Element;
6+
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
7+
import org.schabi.newpipe.extractor.exceptions.ParsingException;
8+
9+
public class BandcampChannelInfoItemExtractor implements ChannelInfoItemExtractor {
10+
11+
private final Element resultInfo, searchResult;
12+
13+
public BandcampChannelInfoItemExtractor(final Element searchResult) {
14+
this.searchResult = searchResult;
15+
resultInfo = searchResult.getElementsByClass("result-info").first();
16+
}
17+
18+
@Override
19+
public String getName() throws ParsingException {
20+
return resultInfo.getElementsByClass("heading").text();
21+
}
22+
23+
@Override
24+
public String getUrl() throws ParsingException {
25+
return resultInfo.getElementsByClass("itemurl").text();
26+
}
27+
28+
@Override
29+
public String getThumbnailUrl() throws ParsingException {
30+
final Element img = searchResult.getElementsByClass("art").first()
31+
.getElementsByTag("img").first();
32+
if (img != null) {
33+
return img.attr("src");
34+
} else {
35+
return null;
36+
}
37+
}
38+
39+
@Override
40+
public String getDescription() {
41+
return resultInfo.getElementsByClass("subhead").text();
42+
}
43+
44+
@Override
45+
public long getSubscriberCount() {
46+
return -1;
47+
}
48+
49+
@Override
50+
public long getStreamCount() {
51+
return -1;
52+
}
53+
54+
@Override
55+
public boolean isVerified() throws ParsingException {
56+
return false;
57+
}
58+
}

0 commit comments

Comments
 (0)