2019-12-21 19:00:07 +01:00
|
|
|
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
|
|
|
|
|
|
|
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
|
|
|
|
2020-03-19 11:18:29 +01:00
|
|
|
import com.grack.nanojson.JsonObject;
|
|
|
|
import com.grack.nanojson.JsonParserException;
|
2019-12-21 19:00:07 +01:00
|
|
|
import org.jsoup.Jsoup;
|
|
|
|
import org.jsoup.nodes.Document;
|
2020-03-17 20:40:25 +01:00
|
|
|
import org.jsoup.nodes.Element;
|
|
|
|
import org.jsoup.select.Elements;
|
2019-12-21 19:00:07 +01:00
|
|
|
import org.schabi.newpipe.extractor.MediaFormat;
|
|
|
|
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.LinkHandler;
|
|
|
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
|
|
|
import org.schabi.newpipe.extractor.stream.*;
|
|
|
|
|
|
|
|
import javax.annotation.Nonnull;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import java.io.IOException;
|
2020-06-04 14:09:21 +02:00
|
|
|
import java.text.ParseException;
|
|
|
|
import java.text.SimpleDateFormat;
|
2019-12-21 19:31:46 +01:00
|
|
|
import java.util.ArrayList;
|
2020-06-04 14:09:21 +02:00
|
|
|
import java.util.Calendar;
|
|
|
|
import java.util.Date;
|
2019-12-21 19:00:07 +01:00
|
|
|
import java.util.List;
|
2020-03-17 20:40:25 +01:00
|
|
|
import java.util.Locale;
|
2019-12-21 19:00:07 +01:00
|
|
|
|
2020-04-26 23:05:56 +02:00
|
|
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
2020-01-03 13:13:42 +01:00
|
|
|
|
2019-12-21 19:00:07 +01:00
|
|
|
public class BandcampStreamExtractor extends StreamExtractor {
|
|
|
|
|
2020-03-19 11:18:29 +01:00
|
|
|
private JsonObject albumJson;
|
|
|
|
private JsonObject current;
|
2019-12-21 19:00:07 +01:00
|
|
|
private Document document;
|
|
|
|
|
|
|
|
public BandcampStreamExtractor(StreamingService service, LinkHandler 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);
|
2020-03-19 11:18:29 +01:00
|
|
|
current = albumJson.getObject("current");
|
2019-12-21 19:00:07 +01:00
|
|
|
|
2020-03-19 11:18:29 +01:00
|
|
|
if (albumJson.getArray("trackinfo").size() > 1) {
|
2019-12-21 19:00:07 +01:00
|
|
|
// In this case, we are actually viewing an album page!
|
|
|
|
throw new ExtractionException("Page is actually an album, not a track");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the JSON that contains album's metadata from page
|
|
|
|
*
|
|
|
|
* @param html Website
|
|
|
|
* @return Album metadata JSON
|
2019-12-22 02:55:54 +01:00
|
|
|
* @throws ParsingException In case of a faulty website
|
2019-12-21 19:00:07 +01:00
|
|
|
*/
|
2020-03-19 11:18:29 +01:00
|
|
|
public static JsonObject getAlbumInfoJson(String html) throws ParsingException {
|
2019-12-21 19:00:07 +01:00
|
|
|
try {
|
|
|
|
return BandcampExtractorHelper.getJSONFromJavaScriptVariables(html, "TralbumData");
|
2020-03-19 11:18:29 +01:00
|
|
|
} catch (JsonParserException e) {
|
2019-12-21 23:14:23 +01:00
|
|
|
throw new ParsingException("Faulty JSON; page likely does not contain album data", e);
|
2019-12-22 00:42:26 +01:00
|
|
|
} catch (ArrayIndexOutOfBoundsException e) {
|
|
|
|
throw new ParsingException("JSON does not exist", e);
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getName() throws ParsingException {
|
|
|
|
return current.getString("title");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getUploaderUrl() throws ParsingException {
|
|
|
|
String[] parts = getUrl().split("/");
|
|
|
|
// https: (/) (/) * .bandcamp.com (/) and leave out the rest
|
|
|
|
return "https://" + parts[2] + "/";
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getUrl() throws ParsingException {
|
|
|
|
return albumJson.getString("url").replace("http://", "https://");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
2020-04-20 22:46:54 +02:00
|
|
|
public String getUploaderName() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return albumJson.getString("artist");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
@Override
|
2020-04-20 22:46:54 +02:00
|
|
|
public String getTextualUploadDate() {
|
2020-06-04 14:09:21 +02:00
|
|
|
return current.getString("publish_date");
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
@Override
|
2020-06-04 14:09:21 +02:00
|
|
|
public DateWrapper getUploadDate() throws ParsingException {
|
|
|
|
try {
|
|
|
|
Date date = new SimpleDateFormat("dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH).parse(getTextualUploadDate());
|
|
|
|
Calendar calendar = Calendar.getInstance();
|
|
|
|
calendar.setTime(date);
|
|
|
|
return new DateWrapper(calendar, false);
|
|
|
|
} catch (ParseException e) {
|
|
|
|
throw new ParsingException("Could not extract date", e);
|
|
|
|
}
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getThumbnailUrl() throws ParsingException {
|
2020-01-03 13:13:42 +01:00
|
|
|
if (albumJson.isNull("art_id")) return "";
|
|
|
|
else return getImageUrl(albumJson.getLong("art_id"), true);
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getUploaderAvatarUrl() {
|
2019-12-22 01:14:35 +01:00
|
|
|
try {
|
|
|
|
return document.getElementsByClass("band-photo").first().attr("src");
|
|
|
|
} catch (NullPointerException e) {
|
|
|
|
return "";
|
|
|
|
}
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
2020-06-03 21:35:11 +02:00
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getSubChannelUrl() {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getSubChannelName() {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getSubChannelAvatarUrl() {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2019-12-21 19:00:07 +01:00
|
|
|
@Nonnull
|
|
|
|
@Override
|
2020-03-17 20:40:25 +01:00
|
|
|
public Description getDescription() {
|
|
|
|
String s = BandcampExtractorHelper.smartConcatenate(
|
2019-12-21 19:00:07 +01:00
|
|
|
new String[]{
|
2020-03-19 11:18:29 +01:00
|
|
|
current.getString("about"),
|
|
|
|
current.getString("lyrics"),
|
|
|
|
current.getString("credits")
|
2019-12-21 19:00:07 +01:00
|
|
|
}, "\n\n"
|
|
|
|
);
|
2020-03-17 20:40:25 +01:00
|
|
|
return new Description(s, Description.PLAIN_TEXT);
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-04-20 22:46:54 +02:00
|
|
|
public int getAgeLimit() {
|
2020-04-20 22:32:55 +02:00
|
|
|
return NO_AGE_LIMIT;
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-04-20 22:46:54 +02:00
|
|
|
public long getLength() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-04-20 22:46:54 +02:00
|
|
|
public long getTimeStamp() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-04-20 22:46:54 +02:00
|
|
|
public long getViewCount() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-04-20 22:46:54 +02:00
|
|
|
public long getLikeCount() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-04-20 22:46:54 +02:00
|
|
|
public long getDislikeCount() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
2020-04-20 22:46:54 +02:00
|
|
|
public String getDashMpdUrl() {
|
2020-03-17 20:40:25 +01:00
|
|
|
return "";
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
2020-04-20 22:46:54 +02:00
|
|
|
public String getHlsUrl() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-12-21 21:47:06 +01:00
|
|
|
public List<AudioStream> getAudioStreams() {
|
2019-12-21 19:31:46 +01:00
|
|
|
List<AudioStream> audioStreams = new ArrayList<>();
|
|
|
|
|
|
|
|
audioStreams.add(new AudioStream(
|
2020-03-19 11:18:29 +01:00
|
|
|
albumJson.getArray("trackinfo").getObject(0)
|
|
|
|
.getObject("file").getString("mp3-128"),
|
2019-12-21 19:31:46 +01:00
|
|
|
MediaFormat.MP3, 128
|
|
|
|
));
|
|
|
|
return audioStreams;
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-12-21 21:47:06 +01:00
|
|
|
public List<VideoStream> getVideoStreams() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-12-21 21:47:06 +01:00
|
|
|
public List<VideoStream> getVideoOnlyStreams() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
2019-12-21 21:47:06 +01:00
|
|
|
public List<SubtitlesStream> getSubtitlesDefault() {
|
2020-03-17 20:40:25 +01:00
|
|
|
return new ArrayList<>();
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
2019-12-21 21:47:06 +01:00
|
|
|
public List<SubtitlesStream> getSubtitles(MediaFormat format) {
|
2020-03-17 20:40:25 +01:00
|
|
|
return new ArrayList<>();
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-12-21 23:14:23 +01:00
|
|
|
public StreamType getStreamType() {
|
2019-12-21 19:31:46 +01:00
|
|
|
return StreamType.AUDIO_STREAM;
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-12-21 23:14:23 +01:00
|
|
|
public StreamInfoItem getNextStream() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-12-21 23:14:23 +01:00
|
|
|
public StreamInfoItemsCollector getRelatedStreams() {
|
2019-12-21 19:00:07 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getErrorMessage() {
|
|
|
|
return null;
|
|
|
|
}
|
2020-03-17 20:40:25 +01:00
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getHost() {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getPrivacy() {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getCategory() {
|
|
|
|
// Get first tag from html, which is the artist's Genre
|
|
|
|
return document.getElementsByAttributeValue("itemprop", "keywords").first().text();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getLicence() {
|
|
|
|
|
|
|
|
int license = current.getInt("license_type");
|
|
|
|
|
|
|
|
// Tests resulted in this mapping of ints to licence: https://cloud.disroot.org/s/ZTWBxbQ9fKRmRWJ/preview
|
|
|
|
|
|
|
|
switch (license) {
|
|
|
|
case 1:
|
|
|
|
return "All rights reserved ©";
|
|
|
|
case 2:
|
|
|
|
return "CC BY-NC-ND 3.0";
|
|
|
|
case 3:
|
|
|
|
return "CC BY-NC-SA 3.0";
|
|
|
|
case 4:
|
|
|
|
return "CC BY-NC 3.0";
|
|
|
|
case 5:
|
|
|
|
return "CC BY-ND 3.0";
|
|
|
|
case 8:
|
|
|
|
return "CC BY-SA 3.0";
|
|
|
|
case 6:
|
|
|
|
return "CC BY 3.0";
|
|
|
|
default:
|
2020-06-04 18:26:58 +02:00
|
|
|
return "Unknown";
|
2020-03-17 20:40:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
@Override
|
|
|
|
public Locale getLanguageInfo() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public List<String> getTags() {
|
|
|
|
Elements tagElements = document.getElementsByAttributeValue("itemprop", "keywords");
|
|
|
|
|
|
|
|
ArrayList<String> tags = new ArrayList<>();
|
|
|
|
|
|
|
|
for (Element e : tagElements) {
|
|
|
|
tags.add(e.text());
|
|
|
|
}
|
|
|
|
|
|
|
|
return tags;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public String getSupportInfo() {
|
|
|
|
return "";
|
|
|
|
}
|
2019-12-21 19:00:07 +01:00
|
|
|
}
|