mirror of
https://github.com/TeamNewPipe/NewPipeExtractor.git
synced 2025-04-29 00:10:35 +05:30
Merge pull request #889 from AudricV/multiple-images-support
Multiple images support
This commit is contained in:
commit
1f08d28ae5
211
extractor/src/main/java/org/schabi/newpipe/extractor/Image.java
Normal file
211
extractor/src/main/java/org/schabi/newpipe/extractor/Image.java
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
package org.schabi.newpipe.extractor;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing images in the extractor.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* An image has four properties: its URL, its height, its width and its estimated quality level.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Depending of the services, the height, the width or both properties may be not known.
|
||||||
|
* Implementations <b>must use</b> the relevant unknown constants in this case
|
||||||
|
* ({@link #HEIGHT_UNKNOWN} and {@link #WIDTH_UNKNOWN}), to ensure properly the lack of knowledge
|
||||||
|
* of one or both of these properties to extractor clients.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* They should also respect the ranges defined in the estimated image resolution levels as much as
|
||||||
|
* possible, to ensure consistency to extractor clients.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class Image implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant representing that the height of an {@link Image} is unknown.
|
||||||
|
*/
|
||||||
|
public static final int HEIGHT_UNKNOWN = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant representing that the width of an {@link Image} is unknown.
|
||||||
|
*/
|
||||||
|
public static final int WIDTH_UNKNOWN = -1;
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private final String url;
|
||||||
|
private final int height;
|
||||||
|
private final int width;
|
||||||
|
@Nonnull
|
||||||
|
private final ResolutionLevel estimatedResolutionLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an {@link Image} instance.
|
||||||
|
*
|
||||||
|
* @param url the URL to the image, which should be not null or empty
|
||||||
|
* @param height the image's height
|
||||||
|
* @param width the image's width
|
||||||
|
* @param estimatedResolutionLevel the image's estimated resolution level, which must not be
|
||||||
|
* null
|
||||||
|
* @throws NullPointerException if {@code estimatedResolutionLevel} is null
|
||||||
|
*/
|
||||||
|
public Image(@Nonnull final String url,
|
||||||
|
final int height,
|
||||||
|
final int width,
|
||||||
|
@Nonnull final ResolutionLevel estimatedResolutionLevel)
|
||||||
|
throws NullPointerException {
|
||||||
|
this.url = url;
|
||||||
|
this.height = height;
|
||||||
|
this.width = width;
|
||||||
|
this.estimatedResolutionLevel = Objects.requireNonNull(
|
||||||
|
estimatedResolutionLevel, "estimatedResolutionLevel is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the URL of this {@link Image}.
|
||||||
|
*
|
||||||
|
* @return the {@link Image}'s URL.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the height of this {@link Image}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If it is unknown, {@link #HEIGHT_UNKNOWN} is returned instead.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return the {@link Image}'s height or {@link #HEIGHT_UNKNOWN}
|
||||||
|
*/
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the width of this {@link Image}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If it is unknown, {@link #WIDTH_UNKNOWN} is returned instead.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return the {@link Image}'s width or {@link #WIDTH_UNKNOWN}
|
||||||
|
*/
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the estimated resolution level of this image.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If it is unknown, {@link ResolutionLevel#UNKNOWN} is returned instead.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return the estimated resolution level, which is never {@code null}
|
||||||
|
* @see ResolutionLevel
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public ResolutionLevel getEstimatedResolutionLevel() {
|
||||||
|
return estimatedResolutionLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a string representation of this {@link Image} instance.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The representation will be in the following format, where {@code url}, {@code height},
|
||||||
|
* {@code width} and {@code estimatedResolutionLevel} represent the corresponding properties:
|
||||||
|
* <br>
|
||||||
|
* <br>
|
||||||
|
* {@code Image {url=url, height='height, width=width,
|
||||||
|
* estimatedResolutionLevel=estimatedResolutionLevel}'}
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return a string representation of this {@link Image} instance
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Image {" + "url=" + url + ", height=" + height + ", width=" + width
|
||||||
|
+ ", estimatedResolutionLevel=" + estimatedResolutionLevel + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The estimated resolution level of an {@link Image}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Some services don't return the size of their images, but we may know for a specific image
|
||||||
|
* type that a service returns, according to real data, an approximation of the resolution
|
||||||
|
* level.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public enum ResolutionLevel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The high resolution level.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This level applies to images with a height greater than or equal to 720px.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
HIGH,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The medium resolution level.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This level applies to images with a height between 175px inclusive and 720px exclusive.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
MEDIUM,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The low resolution level.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This level applies to images with a height between 1px inclusive and 175px exclusive.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
LOW,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unknown resolution level.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This value is returned when the extractor doesn't know what resolution level an image
|
||||||
|
* could have, for example if the extractor loops in an array of images with different
|
||||||
|
* resolution levels without knowing the height.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
UNKNOWN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link ResolutionLevel} based from the given height.
|
||||||
|
*
|
||||||
|
* @param heightPx the height from which returning the good {@link ResolutionLevel}
|
||||||
|
* @return the {@link ResolutionLevel} corresponding to the height provided. See the
|
||||||
|
* {@link ResolutionLevel} values for details about what value is returned.
|
||||||
|
*/
|
||||||
|
public static ResolutionLevel fromHeight(final int heightPx) {
|
||||||
|
if (heightPx <= 0) {
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heightPx < 175) {
|
||||||
|
return LOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heightPx < 720) {
|
||||||
|
return MEDIUM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HIGH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +1,36 @@
|
|||||||
package org.schabi.newpipe.extractor;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by Christian Schabesberger on 11.02.17.
|
* Created by Christian Schabesberger on 11.02.17.
|
||||||
*
|
*
|
||||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||||
* InfoItem.java is part of NewPipe.
|
* InfoItem.java is part of NewPipe Extractor.
|
||||||
*
|
*
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package org.schabi.newpipe.extractor;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class InfoItem implements Serializable {
|
public abstract class InfoItem implements Serializable {
|
||||||
private final InfoType infoType;
|
private final InfoType infoType;
|
||||||
private final int serviceId;
|
private final int serviceId;
|
||||||
private final String url;
|
private final String url;
|
||||||
private final String name;
|
private final String name;
|
||||||
private String thumbnailUrl;
|
@Nonnull
|
||||||
|
private List<Image> thumbnails = List.of();
|
||||||
|
|
||||||
public InfoItem(final InfoType infoType,
|
public InfoItem(final InfoType infoType,
|
||||||
final int serviceId,
|
final int serviceId,
|
||||||
@ -55,12 +58,13 @@ public abstract class InfoItem implements Serializable {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setThumbnailUrl(final String thumbnailUrl) {
|
public void setThumbnails(@Nonnull final List<Image> thumbnails) {
|
||||||
this.thumbnailUrl = thumbnailUrl;
|
this.thumbnails = thumbnails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getThumbnailUrl() {
|
@Nonnull
|
||||||
return thumbnailUrl;
|
public List<Image> getThumbnails() {
|
||||||
|
return thumbnails;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,8 +2,12 @@ package org.schabi.newpipe.extractor;
|
|||||||
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface InfoItemExtractor {
|
public interface InfoItemExtractor {
|
||||||
String getName() throws ParsingException;
|
String getName() throws ParsingException;
|
||||||
String getUrl() throws ParsingException;
|
String getUrl() throws ParsingException;
|
||||||
String getThumbnailUrl() throws ParsingException;
|
@Nonnull
|
||||||
|
List<Image> getThumbnails() throws ParsingException;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Created by Christian Schabesberger on 25.07.16.
|
||||||
|
*
|
||||||
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
|
* ChannelExtractor.java is part of NewPipe Extractor.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.schabi.newpipe.extractor.channel;
|
package org.schabi.newpipe.extractor.channel;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.Extractor;
|
import org.schabi.newpipe.extractor.Extractor;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||||
@ -8,26 +29,6 @@ import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/*
|
|
||||||
* Created by Christian Schabesberger on 25.07.16.
|
|
||||||
*
|
|
||||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
|
||||||
* ChannelExtractor.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public abstract class ChannelExtractor extends Extractor {
|
public abstract class ChannelExtractor extends Extractor {
|
||||||
|
|
||||||
public static final long UNKNOWN_SUBSCRIBER_COUNT = -1;
|
public static final long UNKNOWN_SUBSCRIBER_COUNT = -1;
|
||||||
@ -36,14 +37,17 @@ public abstract class ChannelExtractor extends Extractor {
|
|||||||
super(service, linkHandler);
|
super(service, linkHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract String getAvatarUrl() throws ParsingException;
|
@Nonnull
|
||||||
public abstract String getBannerUrl() throws ParsingException;
|
public abstract List<Image> getAvatars() throws ParsingException;
|
||||||
|
@Nonnull
|
||||||
|
public abstract List<Image> getBanners() throws ParsingException;
|
||||||
public abstract String getFeedUrl() throws ParsingException;
|
public abstract String getFeedUrl() throws ParsingException;
|
||||||
public abstract long getSubscriberCount() throws ParsingException;
|
public abstract long getSubscriberCount() throws ParsingException;
|
||||||
public abstract String getDescription() throws ParsingException;
|
public abstract String getDescription() throws ParsingException;
|
||||||
public abstract String getParentChannelName() throws ParsingException;
|
public abstract String getParentChannelName() throws ParsingException;
|
||||||
public abstract String getParentChannelUrl() throws ParsingException;
|
public abstract String getParentChannelUrl() throws ParsingException;
|
||||||
public abstract String getParentChannelAvatarUrl() throws ParsingException;
|
@Nonnull
|
||||||
|
public abstract List<Image> getParentChannelAvatars() throws ParsingException;
|
||||||
public abstract boolean isVerified() throws ParsingException;
|
public abstract boolean isVerified() throws ParsingException;
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public abstract List<ListLinkHandler> getTabs() throws ParsingException;
|
public abstract List<ListLinkHandler> getTabs() throws ParsingException;
|
||||||
|
@ -1,6 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Created by Christian Schabesberger on 31.07.16.
|
||||||
|
*
|
||||||
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
|
* ChannelInfo.java is part of NewPipe Extractor.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.schabi.newpipe.extractor.channel;
|
package org.schabi.newpipe.extractor.channel;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.Info;
|
import org.schabi.newpipe.extractor.Info;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
@ -11,26 +32,6 @@ import java.util.List;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/*
|
|
||||||
* Created by Christian Schabesberger on 31.07.16.
|
|
||||||
*
|
|
||||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
|
||||||
* ChannelInfo.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class ChannelInfo extends Info {
|
public class ChannelInfo extends Info {
|
||||||
|
|
||||||
public ChannelInfo(final int serviceId,
|
public ChannelInfo(final int serviceId,
|
||||||
@ -64,13 +65,13 @@ public class ChannelInfo extends Info {
|
|||||||
final ChannelInfo info = new ChannelInfo(serviceId, id, url, originalUrl, name);
|
final ChannelInfo info = new ChannelInfo(serviceId, id, url, originalUrl, name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
info.setAvatarUrl(extractor.getAvatarUrl());
|
info.setAvatars(extractor.getAvatars());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
info.addError(e);
|
info.addError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
info.setBannerUrl(extractor.getBannerUrl());
|
info.setBanners(extractor.getBanners());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
info.addError(e);
|
info.addError(e);
|
||||||
}
|
}
|
||||||
@ -106,7 +107,7 @@ public class ChannelInfo extends Info {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
info.setParentChannelAvatarUrl(extractor.getParentChannelAvatarUrl());
|
info.setParentChannelAvatars(extractor.getParentChannelAvatars());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
info.addError(e);
|
info.addError(e);
|
||||||
}
|
}
|
||||||
@ -132,15 +133,18 @@ public class ChannelInfo extends Info {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String avatarUrl;
|
|
||||||
private String parentChannelName;
|
private String parentChannelName;
|
||||||
private String parentChannelUrl;
|
private String parentChannelUrl;
|
||||||
private String parentChannelAvatarUrl;
|
|
||||||
private String bannerUrl;
|
|
||||||
private String feedUrl;
|
private String feedUrl;
|
||||||
private long subscriberCount = -1;
|
private long subscriberCount = -1;
|
||||||
private String description;
|
private String description;
|
||||||
private String[] donationLinks;
|
private String[] donationLinks;
|
||||||
|
@Nonnull
|
||||||
|
private List<Image> avatars = List.of();
|
||||||
|
@Nonnull
|
||||||
|
private List<Image> banners = List.of();
|
||||||
|
@Nonnull
|
||||||
|
private List<Image> parentChannelAvatars = List.of();
|
||||||
private boolean verified;
|
private boolean verified;
|
||||||
private List<ListLinkHandler> tabs = List.of();
|
private List<ListLinkHandler> tabs = List.of();
|
||||||
private List<String> tags = List.of();
|
private List<String> tags = List.of();
|
||||||
@ -161,28 +165,31 @@ public class ChannelInfo extends Info {
|
|||||||
this.parentChannelUrl = parentChannelUrl;
|
this.parentChannelUrl = parentChannelUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getParentChannelAvatarUrl() {
|
@Nonnull
|
||||||
return parentChannelAvatarUrl;
|
public List<Image> getParentChannelAvatars() {
|
||||||
|
return parentChannelAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setParentChannelAvatarUrl(final String parentChannelAvatarUrl) {
|
public void setParentChannelAvatars(@Nonnull final List<Image> parentChannelAvatars) {
|
||||||
this.parentChannelAvatarUrl = parentChannelAvatarUrl;
|
this.parentChannelAvatars = parentChannelAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAvatarUrl() {
|
@Nonnull
|
||||||
return avatarUrl;
|
public List<Image> getAvatars() {
|
||||||
|
return avatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAvatarUrl(final String avatarUrl) {
|
public void setAvatars(@Nonnull final List<Image> avatars) {
|
||||||
this.avatarUrl = avatarUrl;
|
this.avatars = avatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBannerUrl() {
|
@Nonnull
|
||||||
return bannerUrl;
|
public List<Image> getBanners() {
|
||||||
|
return banners;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBannerUrl(final String bannerUrl) {
|
public void setBanners(@Nonnull final List<Image> banners) {
|
||||||
this.bannerUrl = bannerUrl;
|
this.banners = banners;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFeedUrl() {
|
public String getFeedUrl() {
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
package org.schabi.newpipe.extractor.channel;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by Christian Schabesberger on 12.02.17.
|
* Created by Christian Schabesberger on 12.02.17.
|
||||||
*
|
*
|
||||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||||
* ChannelInfoItemsCollector.java is part of NewPipe.
|
* ChannelInfoItemsCollector.java is part of NewPipe Extractor.
|
||||||
*
|
*
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package org.schabi.newpipe.extractor.channel;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
|
||||||
public final class ChannelInfoItemsCollector
|
public final class ChannelInfoItemsCollector
|
||||||
extends InfoItemsCollector<ChannelInfoItem, ChannelInfoItemExtractor> {
|
extends InfoItemsCollector<ChannelInfoItem, ChannelInfoItemExtractor> {
|
||||||
public ChannelInfoItemsCollector(final int serviceId) {
|
public ChannelInfoItemsCollector(final int serviceId) {
|
||||||
@ -47,7 +47,7 @@ public final class ChannelInfoItemsCollector
|
|||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
resultItem.setThumbnailUrl(extractor.getThumbnailUrl());
|
resultItem.setThumbnails(extractor.getThumbnails());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
package org.schabi.newpipe.extractor.comments;
|
package org.schabi.newpipe.extractor.comments;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.stream.Description;
|
import org.schabi.newpipe.extractor.stream.Description;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class CommentsInfoItem extends InfoItem {
|
public class CommentsInfoItem extends InfoItem {
|
||||||
|
|
||||||
private String commentId;
|
private String commentId;
|
||||||
private Description commentText;
|
private Description commentText;
|
||||||
private String uploaderName;
|
private String uploaderName;
|
||||||
private String uploaderAvatarUrl;
|
@Nonnull
|
||||||
|
private List<Image> uploaderAvatars = List.of();
|
||||||
private String uploaderUrl;
|
private String uploaderUrl;
|
||||||
private boolean uploaderVerified;
|
private boolean uploaderVerified;
|
||||||
private String textualUploadDate;
|
private String textualUploadDate;
|
||||||
@ -60,12 +64,13 @@ public class CommentsInfoItem extends InfoItem {
|
|||||||
this.uploaderName = uploaderName;
|
this.uploaderName = uploaderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUploaderAvatarUrl() {
|
@Nonnull
|
||||||
return uploaderAvatarUrl;
|
public List<Image> getUploaderAvatars() {
|
||||||
|
return uploaderAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUploaderAvatarUrl(final String uploaderAvatarUrl) {
|
public void setUploaderAvatars(@Nonnull final List<Image> uploaderAvatars) {
|
||||||
this.uploaderAvatarUrl = uploaderAvatarUrl;
|
this.uploaderAvatars = uploaderAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUploaderUrl() {
|
public String getUploaderUrl() {
|
||||||
@ -94,8 +99,8 @@ public class CommentsInfoItem extends InfoItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the comment's like count
|
* @return the comment's like count or {@link CommentsInfoItem#NO_LIKE_COUNT} if it is
|
||||||
* or {@link CommentsInfoItem#NO_LIKE_COUNT} if it is unavailable
|
* unavailable
|
||||||
*/
|
*/
|
||||||
public int getLikeCount() {
|
public int getLikeCount() {
|
||||||
return likeCount;
|
return likeCount;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.schabi.newpipe.extractor.comments;
|
package org.schabi.newpipe.extractor.comments;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
@ -8,7 +9,9 @@ import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeCommentsI
|
|||||||
import org.schabi.newpipe.extractor.stream.Description;
|
import org.schabi.newpipe.extractor.stream.Description;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface CommentsInfoItemExtractor extends InfoItemExtractor {
|
public interface CommentsInfoItemExtractor extends InfoItemExtractor {
|
||||||
|
|
||||||
@ -77,8 +80,9 @@ public interface CommentsInfoItemExtractor extends InfoItemExtractor {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
default String getUploaderAvatarUrl() throws ParsingException {
|
@Nonnull
|
||||||
return "";
|
default List<Image> getUploaderAvatars() throws ParsingException {
|
||||||
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +36,7 @@ public final class CommentsInfoItemsCollector
|
|||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
resultItem.setUploaderAvatarUrl(extractor.getUploaderAvatarUrl());
|
resultItem.setUploaderAvatars(extractor.getUploaderAvatars());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ public final class CommentsInfoItemsCollector
|
|||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
resultItem.setThumbnailUrl(extractor.getThumbnailUrl());
|
resultItem.setThumbnails(extractor.getThumbnails());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.schabi.newpipe.extractor.playlist;
|
package org.schabi.newpipe.extractor.playlist;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
@ -9,6 +10,9 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class PlaylistExtractor extends ListExtractor<StreamInfoItem> {
|
public abstract class PlaylistExtractor extends ListExtractor<StreamInfoItem> {
|
||||||
|
|
||||||
public PlaylistExtractor(final StreamingService service, final ListLinkHandler linkHandler) {
|
public PlaylistExtractor(final StreamingService service, final ListLinkHandler linkHandler) {
|
||||||
@ -17,7 +21,8 @@ public abstract class PlaylistExtractor extends ListExtractor<StreamInfoItem> {
|
|||||||
|
|
||||||
public abstract String getUploaderUrl() throws ParsingException;
|
public abstract String getUploaderUrl() throws ParsingException;
|
||||||
public abstract String getUploaderName() throws ParsingException;
|
public abstract String getUploaderName() throws ParsingException;
|
||||||
public abstract String getUploaderAvatarUrl() throws ParsingException;
|
@Nonnull
|
||||||
|
public abstract List<Image> getUploaderAvatars() throws ParsingException;
|
||||||
public abstract boolean isUploaderVerified() throws ParsingException;
|
public abstract boolean isUploaderVerified() throws ParsingException;
|
||||||
|
|
||||||
public abstract long getStreamCount() throws ParsingException;
|
public abstract long getStreamCount() throws ParsingException;
|
||||||
@ -26,15 +31,13 @@ public abstract class PlaylistExtractor extends ListExtractor<StreamInfoItem> {
|
|||||||
public abstract Description getDescription() throws ParsingException;
|
public abstract Description getDescription() throws ParsingException;
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return "";
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public String getBannerUrl() throws ParsingException {
|
public List<Image> getBanners() throws ParsingException {
|
||||||
// Banner can't be handled by frontend right now.
|
return List.of();
|
||||||
// Whoever is willing to implement this should also implement it in the frontend.
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -48,8 +51,8 @@ public abstract class PlaylistExtractor extends ListExtractor<StreamInfoItem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public String getSubChannelAvatarUrl() throws ParsingException {
|
public List<Image> getSubChannelAvatars() throws ParsingException {
|
||||||
return "";
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlaylistInfo.PlaylistType getPlaylistType() throws ParsingException {
|
public PlaylistInfo.PlaylistType getPlaylistType() throws ParsingException {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.schabi.newpipe.extractor.playlist;
|
package org.schabi.newpipe.extractor.playlist;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
||||||
import org.schabi.newpipe.extractor.ListInfo;
|
import org.schabi.newpipe.extractor.ListInfo;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
@ -12,6 +13,7 @@ import org.schabi.newpipe.extractor.stream.Description;
|
|||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -109,26 +111,23 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||||||
info.addError(e);
|
info.addError(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
info.setThumbnailUrl(extractor.getThumbnailUrl());
|
info.setThumbnails(extractor.getThumbnails());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
info.addError(e);
|
info.addError(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
info.setUploaderUrl(extractor.getUploaderUrl());
|
info.setUploaderUrl(extractor.getUploaderUrl());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
info.setUploaderUrl("");
|
|
||||||
uploaderParsingErrors.add(e);
|
uploaderParsingErrors.add(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
info.setUploaderName(extractor.getUploaderName());
|
info.setUploaderName(extractor.getUploaderName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
info.setUploaderName("");
|
|
||||||
uploaderParsingErrors.add(e);
|
uploaderParsingErrors.add(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
info.setUploaderAvatarUrl(extractor.getUploaderAvatarUrl());
|
info.setUploaderAvatars(extractor.getUploaderAvatars());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
info.setUploaderAvatarUrl("");
|
|
||||||
uploaderParsingErrors.add(e);
|
uploaderParsingErrors.add(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -142,12 +141,12 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||||||
uploaderParsingErrors.add(e);
|
uploaderParsingErrors.add(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
info.setSubChannelAvatarUrl(extractor.getSubChannelAvatarUrl());
|
info.setSubChannelAvatars(extractor.getSubChannelAvatars());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
uploaderParsingErrors.add(e);
|
uploaderParsingErrors.add(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
info.setBannerUrl(extractor.getBannerUrl());
|
info.setBanners(extractor.getBanners());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
info.addError(e);
|
info.addError(e);
|
||||||
}
|
}
|
||||||
@ -171,32 +170,38 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String thumbnailUrl;
|
private String uploaderUrl = "";
|
||||||
private String bannerUrl;
|
private String uploaderName = "";
|
||||||
private String uploaderUrl;
|
|
||||||
private String uploaderName;
|
|
||||||
private String uploaderAvatarUrl;
|
|
||||||
private String subChannelUrl;
|
private String subChannelUrl;
|
||||||
private String subChannelName;
|
private String subChannelName;
|
||||||
private String subChannelAvatarUrl;
|
|
||||||
private long streamCount = 0;
|
|
||||||
private Description description;
|
private Description description;
|
||||||
|
@Nonnull
|
||||||
|
private List<Image> banners = List.of();
|
||||||
|
@Nonnull
|
||||||
|
private List<Image> subChannelAvatars = List.of();
|
||||||
|
@Nonnull
|
||||||
|
private List<Image> thumbnails = List.of();
|
||||||
|
@Nonnull
|
||||||
|
private List<Image> uploaderAvatars = List.of();
|
||||||
|
private long streamCount;
|
||||||
private PlaylistType playlistType;
|
private PlaylistType playlistType;
|
||||||
|
|
||||||
public String getThumbnailUrl() {
|
@Nonnull
|
||||||
return thumbnailUrl;
|
public List<Image> getThumbnails() {
|
||||||
|
return thumbnails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setThumbnailUrl(final String thumbnailUrl) {
|
public void setThumbnails(@Nonnull final List<Image> thumbnails) {
|
||||||
this.thumbnailUrl = thumbnailUrl;
|
this.thumbnails = thumbnails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBannerUrl() {
|
@Nonnull
|
||||||
return bannerUrl;
|
public List<Image> getBanners() {
|
||||||
|
return banners;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBannerUrl(final String bannerUrl) {
|
public void setBanners(@Nonnull final List<Image> banners) {
|
||||||
this.bannerUrl = bannerUrl;
|
this.banners = banners;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUploaderUrl() {
|
public String getUploaderUrl() {
|
||||||
@ -215,12 +220,13 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||||||
this.uploaderName = uploaderName;
|
this.uploaderName = uploaderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUploaderAvatarUrl() {
|
@Nonnull
|
||||||
return uploaderAvatarUrl;
|
public List<Image> getUploaderAvatars() {
|
||||||
|
return uploaderAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUploaderAvatarUrl(final String uploaderAvatarUrl) {
|
public void setUploaderAvatars(@Nonnull final List<Image> uploaderAvatars) {
|
||||||
this.uploaderAvatarUrl = uploaderAvatarUrl;
|
this.uploaderAvatars = uploaderAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSubChannelUrl() {
|
public String getSubChannelUrl() {
|
||||||
@ -239,12 +245,13 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||||||
this.subChannelName = subChannelName;
|
this.subChannelName = subChannelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSubChannelAvatarUrl() {
|
@Nonnull
|
||||||
return subChannelAvatarUrl;
|
public List<Image> getSubChannelAvatars() {
|
||||||
|
return subChannelAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSubChannelAvatarUrl(final String subChannelAvatarUrl) {
|
public void setSubChannelAvatars(@Nonnull final List<Image> subChannelAvatars) {
|
||||||
this.subChannelAvatarUrl = subChannelAvatarUrl;
|
this.subChannelAvatars = subChannelAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getStreamCount() {
|
public long getStreamCount() {
|
||||||
|
@ -32,7 +32,7 @@ public class PlaylistInfoItemsCollector
|
|||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
resultItem.setThumbnailUrl(extractor.getThumbnailUrl());
|
resultItem.setThumbnails(extractor.getThumbnails());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class BandcampAlbumInfoItemExtractor implements PlaylistInfoItemExtractor {
|
public class BandcampAlbumInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||||
private final JsonObject albumInfoItem;
|
private final JsonObject albumInfoItem;
|
||||||
private final String uploaderUrl;
|
private final String uploaderUrl;
|
||||||
@ -28,9 +34,10 @@ public class BandcampAlbumInfoItemExtractor implements PlaylistInfoItemExtractor
|
|||||||
albumInfoItem.getString("item_type"));
|
albumInfoItem.getString("item_type"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return BandcampExtractorHelper.getImageUrl(albumInfoItem.getLong("art_id"), true);
|
return BandcampExtractorHelper.getImagesFromImageId(albumInfoItem.getLong("art_id"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,12 +2,18 @@
|
|||||||
|
|
||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.Image.HEIGHT_UNKNOWN;
|
||||||
|
import static org.schabi.newpipe.extractor.Image.WIDTH_UNKNOWN;
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getArtistDetails;
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||||
@ -25,6 +31,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
@ -38,17 +45,15 @@ public class BandcampChannelExtractor extends ChannelExtractor {
|
|||||||
super(service, linkHandler);
|
super(service, linkHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getAvatarUrl() {
|
public List<Image> getAvatars() {
|
||||||
if (channelInfo.getLong("bio_image_id") == 0) {
|
return getImagesFromImageId(channelInfo.getLong("bio_image_id"), false);
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return BandcampExtractorHelper.getImageUrl(channelInfo.getLong("bio_image_id"), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getBannerUrl() throws ParsingException {
|
public List<Image> getBanners() throws ParsingException {
|
||||||
/*
|
/*
|
||||||
* Mobile API does not return the header or not the correct header.
|
* Mobile API does not return the header or not the correct header.
|
||||||
* Therefore, we need to query the website
|
* Therefore, we need to query the website
|
||||||
@ -62,8 +67,11 @@ public class BandcampChannelExtractor extends ChannelExtractor {
|
|||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.flatMap(element -> element.getElementsByTag("img").stream())
|
.flatMap(element -> element.getElementsByTag("img").stream())
|
||||||
.map(element -> element.attr("src"))
|
.map(element -> element.attr("src"))
|
||||||
.findFirst()
|
.filter(url -> !url.isEmpty())
|
||||||
.orElse(""); // no banner available
|
.map(url -> new Image(
|
||||||
|
replaceHttpWithHttps(url), HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||||
|
ResolutionLevel.UNKNOWN))
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
|
|
||||||
} catch (final IOException | ReCaptchaException e) {
|
} catch (final IOException | ReCaptchaException e) {
|
||||||
throw new ParsingException("Could not download artist web site", e);
|
throw new ParsingException("Could not download artist web site", e);
|
||||||
@ -98,9 +106,10 @@ public class BandcampChannelExtractor extends ChannelExtractor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getParentChannelAvatarUrl() {
|
public List<Image> getParentChannelAvatars() {
|
||||||
return null;
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -156,7 +165,7 @@ public class BandcampChannelExtractor extends ChannelExtractor {
|
|||||||
@Override
|
@Override
|
||||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||||
throws IOException, ExtractionException {
|
throws IOException, ExtractionException {
|
||||||
channelInfo = BandcampExtractorHelper.getArtistDetails(getId());
|
channelInfo = getArtistDetails(getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -3,9 +3,15 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||||
|
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromSearchResult;
|
||||||
|
|
||||||
public class BandcampChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
public class BandcampChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
||||||
|
|
||||||
private final Element resultInfo;
|
private final Element resultInfo;
|
||||||
@ -26,9 +32,10 @@ public class BandcampChannelInfoItemExtractor implements ChannelInfoItemExtracto
|
|||||||
return resultInfo.getElementsByClass("itemurl").text();
|
return resultInfo.getElementsByClass("itemurl").text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return BandcampExtractorHelper.getThumbnailUrlFromSearchResult(searchResult);
|
return getImagesFromSearchResult(searchResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItemExtractor;
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.stream.Description;
|
import org.schabi.newpipe.extractor.stream.Description;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class BandcampCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
public class BandcampCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
||||||
|
|
||||||
private final JsonObject review;
|
private final JsonObject review;
|
||||||
@ -28,9 +32,10 @@ public class BandcampCommentsInfoItemExtractor implements CommentsInfoItemExtrac
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return getUploaderAvatarUrl();
|
return getUploaderAvatars();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -43,8 +48,9 @@ public class BandcampCommentsInfoItemExtractor implements CommentsInfoItemExtrac
|
|||||||
return review.getString("name");
|
return review.getString("name");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
return getImageUrl(review.getLong("image_id"), false);
|
return getImagesFromImageId(review.getLong("image_id"), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,25 +6,81 @@ import com.grack.nanojson.JsonObject;
|
|||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
import com.grack.nanojson.JsonWriter;
|
import com.grack.nanojson.JsonWriter;
|
||||||
|
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.utils.Utils;
|
import org.schabi.newpipe.extractor.utils.ImageSuffix;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.DateTimeException;
|
import java.time.DateTimeException;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.Image.HEIGHT_UNKNOWN;
|
||||||
|
import static org.schabi.newpipe.extractor.Image.WIDTH_UNKNOWN;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||||
|
|
||||||
public final class BandcampExtractorHelper {
|
public final class BandcampExtractorHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of image IDs which preserve aspect ratio with their theoretical dimension known.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Bandcamp images are not always squares, so images which preserve aspect ratio are only used.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* One of the direct consequences of this specificity is that only one dimension of images is
|
||||||
|
* known at time, depending of the image ID.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note also that dimensions are only theoretical because if the image size is less than the
|
||||||
|
* dimensions of the image ID, it will be not upscaled but kept to its original size.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* IDs come from <a href="https://gist.github.com/f2k1de/06f5fd0ae9c919a7c3693a44ee522213">the
|
||||||
|
* GitHub Gist "Bandcamp File Format Parameters" by f2k1de</a>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private static final List<ImageSuffix> IMAGE_URL_SUFFIXES_AND_RESOLUTIONS = List.of(
|
||||||
|
// ID | HEIGHT | WIDTH
|
||||||
|
new ImageSuffix("10.jpg", HEIGHT_UNKNOWN, 1200, ResolutionLevel.HIGH),
|
||||||
|
new ImageSuffix("101.jpg", 90, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||||
|
new ImageSuffix("170.jpg", 422, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||||
|
// 180 returns the same image aspect ratio and size as 171
|
||||||
|
new ImageSuffix("171.jpg", 646, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||||
|
new ImageSuffix("20.jpg", HEIGHT_UNKNOWN, 1024, ResolutionLevel.HIGH),
|
||||||
|
// 203 returns the same image aspect ratio and size as 200
|
||||||
|
new ImageSuffix("200.jpg", 420, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||||
|
new ImageSuffix("201.jpg", 280, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||||
|
new ImageSuffix("202.jpg", 140, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||||
|
new ImageSuffix("204.jpg", 360, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||||
|
new ImageSuffix("205.jpg", 240, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||||
|
new ImageSuffix("206.jpg", 180, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||||
|
new ImageSuffix("207.jpg", 120, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||||
|
new ImageSuffix("43.jpg", 100, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||||
|
new ImageSuffix("44.jpg", 200, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM));
|
||||||
|
|
||||||
|
private static final String IMAGE_URL_APPENDIX_AND_EXTENSION_REGEX = "_\\d+\\.\\w+";
|
||||||
|
private static final String IMAGES_DOMAIN_AND_PATH = "https://f4.bcbits.com/img/";
|
||||||
|
|
||||||
public static final String BASE_URL = "https://bandcamp.com";
|
public static final String BASE_URL = "https://bandcamp.com";
|
||||||
public static final String BASE_API_URL = BASE_URL + "/api";
|
public static final String BASE_API_URL = BASE_URL + "/api";
|
||||||
|
|
||||||
@ -44,7 +100,7 @@ public final class BandcampExtractorHelper {
|
|||||||
+ "&tralbum_id=" + itemId + "&tralbum_type=" + itemType.charAt(0))
|
+ "&tralbum_id=" + itemId + "&tralbum_type=" + itemType.charAt(0))
|
||||||
.responseBody();
|
.responseBody();
|
||||||
|
|
||||||
return Utils.replaceHttpWithHttps(JsonParser.object().from(jsonString)
|
return replaceHttpWithHttps(JsonParser.object().from(jsonString)
|
||||||
.getString("bandcamp_url"));
|
.getString("bandcamp_url"));
|
||||||
|
|
||||||
} catch (final JsonParserException | ReCaptchaException | IOException e) {
|
} catch (final JsonParserException | ReCaptchaException | IOException e) {
|
||||||
@ -76,17 +132,26 @@ public final class BandcampExtractorHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate image url from image ID.
|
* Generate an image url from an image ID.
|
||||||
* <p>
|
|
||||||
* The appendix "_10" was chosen because it provides images sized 1200x1200. Other integer
|
|
||||||
* values are possible as well (e.g. 0 is a very large resolution, possibly the original).
|
|
||||||
*
|
*
|
||||||
* @param id The image ID
|
* <p>
|
||||||
* @param album True if this is the cover of an album or track
|
* The image ID {@code 10} was chosen because it provides images wide up to 1200px (when
|
||||||
* @return URL of image with this ID sized 1200x1200
|
* the original image width is more than or equal this resolution).
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Other integer values are possible as well (e.g. 0 is a very large resolution, possibly the
|
||||||
|
* original); see {@link #IMAGE_URL_SUFFIXES_AND_RESOLUTIONS} for more details about image
|
||||||
|
* resolution IDs.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param id the image ID
|
||||||
|
* @param isAlbum whether the image is the cover of an album or a track
|
||||||
|
* @return a URL of the image with this ID with a width up to 1200px
|
||||||
*/
|
*/
|
||||||
public static String getImageUrl(final long id, final boolean album) {
|
@Nonnull
|
||||||
return "https://f4.bcbits.com/img/" + (album ? 'a' : "") + id + "_10.jpg";
|
public static String getImageUrl(final long id, final boolean isAlbum) {
|
||||||
|
return IMAGES_DOMAIN_AND_PATH + (isAlbum ? 'a' : "") + id + "_10.jpg";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,13 +201,94 @@ public final class BandcampExtractorHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
/**
|
||||||
public static String getThumbnailUrlFromSearchResult(final Element searchResult) {
|
* Get a list of images from a search result {@link Element}.
|
||||||
return searchResult.getElementsByClass("art").stream()
|
*
|
||||||
|
* <p>
|
||||||
|
* This method will call {@link #getImagesFromImageUrl(String)} using the first non null and
|
||||||
|
* non empty image URL found from the {@code src} attribute of {@code img} HTML elements, or an
|
||||||
|
* empty string if no valid image URL was found.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param searchResult a search result {@link Element}
|
||||||
|
* @return an unmodifiable list of {@link Image}s, which is never null but can be empty, in the
|
||||||
|
* case where no valid image URL was found
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getImagesFromSearchResult(@Nonnull final Element searchResult) {
|
||||||
|
return getImagesFromImageUrl(searchResult.getElementsByClass("art")
|
||||||
|
.stream()
|
||||||
.flatMap(element -> element.getElementsByTag("img").stream())
|
.flatMap(element -> element.getElementsByTag("img").stream())
|
||||||
.map(element -> element.attr("src"))
|
.map(element -> element.attr("src"))
|
||||||
.filter(string -> !string.isEmpty())
|
.filter(imageUrl -> !isNullOrEmpty(imageUrl))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all images which have resolutions preserving aspect ratio from an image URL.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This method will remove the image ID and its extension from the end of the URL and then call
|
||||||
|
* {@link #getImagesFromImageBaseUrl(String)}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param imageUrl the full URL of an image provided by Bandcamp, such as in its HTML code
|
||||||
|
* @return an unmodifiable list of {@link Image}s, which is never null but can be empty, in the
|
||||||
|
* case where the image URL has been not extracted (and so is null or empty)
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getImagesFromImageUrl(@Nullable final String imageUrl) {
|
||||||
|
if (isNullOrEmpty(imageUrl)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getImagesFromImageBaseUrl(
|
||||||
|
imageUrl.replaceFirst(IMAGE_URL_APPENDIX_AND_EXTENSION_REGEX, "_"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all images which have resolutions preserving aspect ratio from an image ID.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This method will call {@link #getImagesFromImageBaseUrl(String)}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param id the id of an image provided by Bandcamp
|
||||||
|
* @param isAlbum whether the image is the cover of an album
|
||||||
|
* @return an unmodifiable list of {@link Image}s, which is never null but can be empty, in the
|
||||||
|
* case where the image ID has been not extracted (and so equal to 0)
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getImagesFromImageId(final long id, final boolean isAlbum) {
|
||||||
|
if (id == 0) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getImagesFromImageBaseUrl(IMAGES_DOMAIN_AND_PATH + (isAlbum ? 'a' : "") + id + "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all images resolutions preserving aspect ratio from a base image URL.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Base image URLs are images containing the image path, a {@code a} letter if it comes from an
|
||||||
|
* album, its ID and an underscore.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Images resolutions returned are the ones of {@link #IMAGE_URL_SUFFIXES_AND_RESOLUTIONS}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param baseUrl the base URL of the image
|
||||||
|
* @return an unmodifiable and non-empty list of {@link Image}s
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
private static List<Image> getImagesFromImageBaseUrl(@Nonnull final String baseUrl) {
|
||||||
|
return IMAGE_URL_SUFFIXES_AND_RESOLUTIONS.stream()
|
||||||
|
.map(imageSuffix -> new Image(baseUrl + imageSuffix.getSuffix(),
|
||||||
|
imageSuffix.getHeight(), imageSuffix.getWidth(),
|
||||||
|
imageSuffix.getResolutionLevel()))
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageUrl;
|
||||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor.getAlbumInfoJson;
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor.getAlbumInfoJson;
|
||||||
import static org.schabi.newpipe.extractor.utils.JsonUtils.getJsonData;
|
import static org.schabi.newpipe.extractor.utils.JsonUtils.getJsonData;
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
|
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
|
||||||
@ -13,6 +14,7 @@ import org.jsoup.Jsoup;
|
|||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
import org.jsoup.select.Elements;
|
import org.jsoup.select.Elements;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
@ -28,6 +30,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
@ -74,11 +77,11 @@ public class BandcampPlaylistExtractor extends PlaylistExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
if (albumJson.isNull("art_id")) {
|
if (albumJson.isNull("art_id")) {
|
||||||
return "";
|
return List.of();
|
||||||
} else {
|
} else {
|
||||||
return getImageUrl(albumJson.getLong("art_id"), true);
|
return getImagesFromImageId(albumJson.getLong("art_id"), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,12 +97,14 @@ public class BandcampPlaylistExtractor extends PlaylistExtractor {
|
|||||||
return albumJson.getString("artist");
|
return albumJson.getString("artist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
return document.getElementsByClass("band-photo").stream()
|
return getImagesFromImageUrl(document.getElementsByClass("band-photo")
|
||||||
|
.stream()
|
||||||
.map(element -> element.attr("src"))
|
.map(element -> element.attr("src"))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse("");
|
.orElse(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -154,7 +159,7 @@ public class BandcampPlaylistExtractor extends PlaylistExtractor {
|
|||||||
} else {
|
} else {
|
||||||
// Pretend every track has the same cover art as the album
|
// Pretend every track has the same cover art as the album
|
||||||
collector.commit(new BandcampPlaylistStreamInfoItemExtractor(
|
collector.commit(new BandcampPlaylistStreamInfoItemExtractor(
|
||||||
track, getUploaderUrl(), getThumbnailUrl()));
|
track, getUploaderUrl(), getThumbnails()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||||
|
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromSearchResult;
|
||||||
|
|
||||||
public class BandcampPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
public class BandcampPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||||
private final Element searchResult;
|
private final Element searchResult;
|
||||||
@ -46,8 +50,9 @@ public class BandcampPlaylistInfoItemExtractor implements PlaylistInfoItemExtrac
|
|||||||
return resultInfo.getElementsByClass("itemurl").text();
|
return resultInfo.getElementsByClass("itemurl").text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
return BandcampExtractorHelper.getThumbnailUrlFromSearchResult(searchResult);
|
return getImagesFromSearchResult(searchResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||||
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||||
|
|
||||||
public class BandcampPlaylistInfoItemFeaturedExtractor implements PlaylistInfoItemExtractor {
|
public class BandcampPlaylistInfoItemFeaturedExtractor implements PlaylistInfoItemExtractor {
|
||||||
|
|
||||||
@ -40,12 +45,14 @@ public class BandcampPlaylistInfoItemFeaturedExtractor implements PlaylistInfoIt
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
return featuredStory.getString("item_url").replaceAll("http://", "https://");
|
return Utils.replaceHttpWithHttps(featuredStory.getString("item_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
return featuredStory.has("art_id") ? getImageUrl(featuredStory.getLong("art_id"), true)
|
return featuredStory.has("art_id")
|
||||||
: getImageUrl(featuredStory.getLong("item_art_id"), true);
|
? getImagesFromImageId(featuredStory.getLong("art_id"), true)
|
||||||
|
: getImagesFromImageId(featuredStory.getLong("item_art_id"), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,20 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
||||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.parseDate;
|
||||||
|
|
||||||
public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
|
public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
|
||||||
|
|
||||||
@ -39,7 +44,7 @@ public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public DateWrapper getUploadDate() throws ParsingException {
|
public DateWrapper getUploadDate() throws ParsingException {
|
||||||
return BandcampExtractorHelper.parseDate(getTextualUploadDate());
|
return parseDate(getTextualUploadDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -52,9 +57,10 @@ public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
return BASE_URL + "/?show=" + show.getInt("id");
|
return BASE_URL + "/?show=" + show.getInt("id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
return getImageUrl(show.getLong("image_id"), false);
|
return getImagesFromImageId(show.getLong("image_id"), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -78,12 +84,6 @@ public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getUploaderAvatarUrl() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUploaderVerified() throws ParsingException {
|
public boolean isUploaderVerified() throws ParsingException {
|
||||||
return false;
|
return false;
|
||||||
|
@ -3,6 +3,7 @@ package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
|||||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
||||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
||||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
@ -11,6 +12,8 @@ import com.grack.nanojson.JsonParserException;
|
|||||||
|
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
@ -97,14 +100,16 @@ public class BandcampRadioStreamExtractor extends BandcampStreamExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return getImageUrl(showInfo.getLong("show_image_id"), false);
|
return getImagesFromImageId(showInfo.getLong("show_image_id"), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
return BASE_URL + "/img/buttons/bandcamp-button-circle-whitecolor-512.png";
|
return Collections.singletonList(
|
||||||
|
new Image(BASE_URL + "/img/buttons/bandcamp-button-circle-whitecolor-512.png",
|
||||||
|
512, 512, ResolutionLevel.MEDIUM));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -3,10 +3,14 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||||
|
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts recommended albums from tracks' website
|
* Extracts recommended albums from tracks' website
|
||||||
@ -28,9 +32,10 @@ public class BandcampRelatedPlaylistInfoItemExtractor implements PlaylistInfoIte
|
|||||||
return relatedAlbum.getElementsByClass("album-link").attr("abs:href");
|
return relatedAlbum.getElementsByClass("album-link").attr("abs:href");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return relatedAlbum.getElementsByClass("album-art").attr("src");
|
return getImagesFromImageUrl(relatedAlbum.getElementsByClass("album-art").attr("src"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.parseDate;
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
|
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
@ -11,6 +14,7 @@ import com.grack.nanojson.JsonParserException;
|
|||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
@ -98,7 +102,7 @@ public class BandcampStreamExtractor extends StreamExtractor {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUrl() throws ParsingException {
|
public String getUrl() throws ParsingException {
|
||||||
return albumJson.getString("url").replace("http://", "https://");
|
return replaceHttpWithHttps(albumJson.getString("url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -116,26 +120,27 @@ public class BandcampStreamExtractor extends StreamExtractor {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public DateWrapper getUploadDate() throws ParsingException {
|
public DateWrapper getUploadDate() throws ParsingException {
|
||||||
return BandcampExtractorHelper.parseDate(getTextualUploadDate());
|
return parseDate(getTextualUploadDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
if (albumJson.isNull("art_id")) {
|
if (albumJson.isNull("art_id")) {
|
||||||
return "";
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
return getImageUrl(albumJson.getLong("art_id"), true);
|
return getImagesFromImageId(albumJson.getLong("art_id"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
return document.getElementsByClass("band-photo").stream()
|
return getImagesFromImageUrl(document.getElementsByClass("band-photo")
|
||||||
|
.stream()
|
||||||
.map(element -> element.attr("src"))
|
.map(element -> element.attr("src"))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse("");
|
.orElse(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper;
|
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||||
|
|
||||||
public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
|
public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
|
||||||
|
|
||||||
@ -20,12 +24,6 @@ public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInf
|
|||||||
return discograph.getString("band_name");
|
return discograph.getString("band_name");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getUploaderAvatarUrl() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return discograph.getString("title");
|
return discograph.getString("title");
|
||||||
@ -40,11 +38,10 @@ public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInf
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return BandcampExtractorHelper.getImageUrl(
|
return getImagesFromImageId(discograph.getLong("art_id"), true);
|
||||||
discograph.getLong("art_id"), true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -3,19 +3,21 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nonnull;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
|
public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
|
||||||
|
|
||||||
private final JsonObject track;
|
private final JsonObject track;
|
||||||
private String substituteCoverUrl;
|
private List<Image> substituteCovers;
|
||||||
private final StreamingService service;
|
private final StreamingService service;
|
||||||
|
|
||||||
public BandcampPlaylistStreamInfoItemExtractor(final JsonObject track,
|
public BandcampPlaylistStreamInfoItemExtractor(final JsonObject track,
|
||||||
@ -24,13 +26,14 @@ public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoI
|
|||||||
super(uploaderUrl);
|
super(uploaderUrl);
|
||||||
this.track = track;
|
this.track = track;
|
||||||
this.service = service;
|
this.service = service;
|
||||||
|
substituteCovers = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BandcampPlaylistStreamInfoItemExtractor(final JsonObject track,
|
public BandcampPlaylistStreamInfoItemExtractor(final JsonObject track,
|
||||||
final String uploaderUrl,
|
final String uploaderUrl,
|
||||||
final String substituteCoverUrl) {
|
final List<Image> substituteCovers) {
|
||||||
this(track, uploaderUrl, (StreamingService) null);
|
this(track, uploaderUrl, (StreamingService) null);
|
||||||
this.substituteCoverUrl = substituteCoverUrl;
|
this.substituteCovers = substituteCovers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -56,28 +59,23 @@ public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoI
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getUploaderAvatarUrl() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Each track can have its own cover art. Therefore, unless a substitute is provided,
|
* Each track can have its own cover art. Therefore, unless a substitute is provided,
|
||||||
* the thumbnail is extracted using a stream extractor.
|
* the thumbnail is extracted using a stream extractor.
|
||||||
*/
|
*/
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
if (substituteCoverUrl != null) {
|
if (substituteCovers.isEmpty()) {
|
||||||
return substituteCoverUrl;
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
final StreamExtractor extractor = service.getStreamExtractor(getUrl());
|
final StreamExtractor extractor = service.getStreamExtractor(getUrl());
|
||||||
extractor.fetchPage();
|
extractor.fetchPage();
|
||||||
return extractor.getThumbnailUrl();
|
return extractor.getThumbnails();
|
||||||
} catch (final ExtractionException | IOException e) {
|
} catch (final ExtractionException | IOException e) {
|
||||||
throw new ParsingException("could not download cover art location", e);
|
throw new ParsingException("Could not download cover art location", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return substituteCovers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem;
|
package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem;
|
||||||
|
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromSearchResult;
|
||||||
|
|
||||||
public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
|
public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
|
||||||
|
|
||||||
@ -29,12 +32,6 @@ public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoIte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getUploaderAvatarUrl() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() throws ParsingException {
|
public String getName() throws ParsingException {
|
||||||
return resultInfo.getElementsByClass("heading").text();
|
return resultInfo.getElementsByClass("heading").text();
|
||||||
@ -45,9 +42,10 @@ public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoIte
|
|||||||
return resultInfo.getElementsByClass("itemurl").text();
|
return resultInfo.getElementsByClass("itemurl").text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return BandcampExtractorHelper.getThumbnailUrlFromSearchResult(searchResult);
|
return getImagesFromSearchResult(searchResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -7,6 +7,7 @@ import com.grack.nanojson.JsonParserException;
|
|||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||||
@ -21,10 +22,13 @@ import org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems.Medi
|
|||||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferenceLinkHandlerFactory;
|
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferenceLinkHandlerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
|
||||||
|
|
||||||
public class MediaCCCConferenceExtractor extends ChannelExtractor {
|
public class MediaCCCConferenceExtractor extends ChannelExtractor {
|
||||||
private JsonObject conferenceData;
|
private JsonObject conferenceData;
|
||||||
|
|
||||||
@ -33,14 +37,16 @@ public class MediaCCCConferenceExtractor extends ChannelExtractor {
|
|||||||
super(service, linkHandler);
|
super(service, linkHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getAvatarUrl() {
|
public List<Image> getAvatars() {
|
||||||
return conferenceData.getString("logo_url");
|
return getImageListFromLogoImageUrl(conferenceData.getString("logo_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getBannerUrl() {
|
public List<Image> getBanners() {
|
||||||
return conferenceData.getString("logo_url");
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -68,9 +74,10 @@ public class MediaCCCConferenceExtractor extends ChannelExtractor {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getParentChannelAvatarUrl() {
|
public List<Image> getParentChannelAvatars() {
|
||||||
return "";
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromLiveStreamItem;
|
||||||
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
|
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
|
||||||
import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN;
|
import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
@ -77,8 +79,8 @@ public class MediaCCCLiveStreamExtractor extends StreamExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return room.getString("thumb");
|
return getThumbnailsFromLiveStreamItem(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromLiveStreamItem;
|
||||||
|
|
||||||
public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor {
|
public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor {
|
||||||
|
|
||||||
@ -32,9 +37,10 @@ public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor
|
|||||||
return roomInfo.getString("link");
|
return roomInfo.getString("link");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return roomInfo.getString("thumb");
|
return getThumbnailsFromLiveStreamItem(roomInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -75,12 +81,6 @@ public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor
|
|||||||
return "https://media.ccc.de/c/" + conferenceInfo.getString("slug");
|
return "https://media.ccc.de/c/" + conferenceInfo.getString("slug");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getUploaderAvatarUrl() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUploaderVerified() throws ParsingException {
|
public boolean isUploaderVerified() throws ParsingException {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,21 +1,33 @@
|
|||||||
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||||
import org.schabi.newpipe.extractor.localization.Localization;
|
import org.schabi.newpipe.extractor.localization.Localization;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.Image.HEIGHT_UNKNOWN;
|
||||||
|
import static org.schabi.newpipe.extractor.Image.WIDTH_UNKNOWN;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
public final class MediaCCCParsingHelper {
|
public final class MediaCCCParsingHelper {
|
||||||
// {conference_slug}/{room_slug}
|
// conference_slug/room_slug
|
||||||
private static final Pattern LIVE_STREAM_ID_PATTERN = Pattern.compile("\\w+/\\w+");
|
private static final Pattern LIVE_STREAM_ID_PATTERN = Pattern.compile("\\w+/\\w+");
|
||||||
private static JsonArray liveStreams = null;
|
private static JsonArray liveStreams = null;
|
||||||
|
|
||||||
@ -69,4 +81,98 @@ public final class MediaCCCParsingHelper {
|
|||||||
}
|
}
|
||||||
return liveStreams;
|
return liveStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an {@link Image} list from a given image logo URL.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the image URL is null or empty, an empty list is returned; otherwise, a singleton list is
|
||||||
|
* returned containing an {@link Image} with the image URL with its height, width and
|
||||||
|
* resolution unknown.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param logoImageUrl a logo image URL, which can be null or empty
|
||||||
|
* @return an unmodifiable list of {@link Image}s, which is always empty or a singleton
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getImageListFromLogoImageUrl(@Nullable final String logoImageUrl) {
|
||||||
|
if (isNullOrEmpty(logoImageUrl)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of(new Image(logoImageUrl, HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||||
|
ResolutionLevel.UNKNOWN));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link Image} list of thumbnails from a given stream item.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* MediaCCC API provides two thumbnails for a stream item: a {@code thumb_url} one, which is
|
||||||
|
* medium quality and a {@code poster_url} one, which is high quality in most cases.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param streamItem a stream JSON item of MediaCCC's API, which must not be null
|
||||||
|
* @return an unmodifiable list, which is never null but can be empty.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getThumbnailsFromStreamItem(@Nonnull final JsonObject streamItem) {
|
||||||
|
return getThumbnailsFromObject(streamItem, "thumb_url", "poster_url");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link Image} list of thumbnails from a given live stream item.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* MediaCCC API provides two URL thumbnails for a livestream item: a {@code thumb} one,
|
||||||
|
* which should be medium quality and a {@code poster_url} one, which should be high quality.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param liveStreamItem a stream JSON item of MediaCCC's API, which must not be null
|
||||||
|
* @return an unmodifiable list, which is never null but can be empty.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getThumbnailsFromLiveStreamItem(
|
||||||
|
@Nonnull final JsonObject liveStreamItem) {
|
||||||
|
return getThumbnailsFromObject(liveStreamItem, "thumb", "poster");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to get an {@link Image} list of thumbnails from a stream or a livestream.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* MediaCCC's API thumbnails come from two elements: a {@code thumb} element, which links to a
|
||||||
|
* medium thumbnail and a {@code poster} element, which links to a high thumbnail.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Thumbnails are only added if their URLs are not null or empty.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param streamOrLivestreamItem a (live)stream JSON item of MediaCCC's API, which must not be
|
||||||
|
* null
|
||||||
|
* @param thumbUrlKey the name of the {@code thumb} URL key
|
||||||
|
* @param posterUrlKey the name of the {@code poster} URL key
|
||||||
|
* @return an unmodifiable list, which is never null but can be empty.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
private static List<Image> getThumbnailsFromObject(
|
||||||
|
@Nonnull final JsonObject streamOrLivestreamItem,
|
||||||
|
@Nonnull final String thumbUrlKey,
|
||||||
|
@Nonnull final String posterUrlKey) {
|
||||||
|
final List<Image> imageList = new ArrayList<>(2);
|
||||||
|
|
||||||
|
final String thumbUrl = streamOrLivestreamItem.getString(thumbUrlKey);
|
||||||
|
if (!isNullOrEmpty(thumbUrl)) {
|
||||||
|
imageList.add(new Image(thumbUrl, HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||||
|
ResolutionLevel.MEDIUM));
|
||||||
|
}
|
||||||
|
|
||||||
|
final String posterUrl = streamOrLivestreamItem.getString(posterUrlKey);
|
||||||
|
if (!isNullOrEmpty(posterUrl)) {
|
||||||
|
imageList.add(new Image(posterUrl, HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||||
|
ResolutionLevel.HIGH));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(imageList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
|||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferenceLinkHandlerFactory;
|
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferenceLinkHandlerFactory;
|
||||||
@ -10,9 +11,13 @@ import org.schabi.newpipe.extractor.stream.StreamType;
|
|||||||
|
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
|
||||||
|
|
||||||
public class MediaCCCRecentKioskExtractor implements StreamInfoItemExtractor {
|
public class MediaCCCRecentKioskExtractor implements StreamInfoItemExtractor {
|
||||||
|
|
||||||
private final JsonObject event;
|
private final JsonObject event;
|
||||||
@ -31,9 +36,10 @@ public class MediaCCCRecentKioskExtractor implements StreamInfoItemExtractor {
|
|||||||
return event.getString("frontend_link");
|
return event.getString("frontend_link");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return event.getString("thumb_url");
|
return getImageListFromLogoImageUrl(event.getString("poster_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -70,12 +76,6 @@ public class MediaCCCRecentKioskExtractor implements StreamInfoItemExtractor {
|
|||||||
.getUrl(); // web URL
|
.getUrl(); // web URL
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getUploaderAvatarUrl() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUploaderVerified() throws ParsingException {
|
public boolean isUploaderVerified() throws ParsingException {
|
||||||
return false;
|
return false;
|
||||||
|
@ -9,8 +9,10 @@ import com.grack.nanojson.JsonObject;
|
|||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.MetaInfo;
|
import org.schabi.newpipe.extractor.MetaInfo;
|
||||||
|
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||||
@ -18,7 +20,6 @@ import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
|||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
|
||||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems.MediaCCCStreamInfoItemExtractor;
|
import org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems.MediaCCCStreamInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferencesListLinkHandlerFactory;
|
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferencesListLinkHandlerFactory;
|
||||||
@ -157,9 +158,10 @@ public class MediaCCCSearchExtractor extends SearchExtractor {
|
|||||||
return item.getUrl();
|
return item.getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
return item.getThumbnailUrl();
|
return item.getThumbnails();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromStreamItem;
|
||||||
|
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.parseDateFrom;
|
||||||
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
|
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
|
||||||
import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN;
|
import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN;
|
||||||
|
|
||||||
@ -8,6 +11,7 @@ import com.grack.nanojson.JsonObject;
|
|||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
@ -51,13 +55,13 @@ public class MediaCCCStreamExtractor extends StreamExtractor {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public DateWrapper getUploadDate() throws ParsingException {
|
public DateWrapper getUploadDate() throws ParsingException {
|
||||||
return new DateWrapper(MediaCCCParsingHelper.parseDateFrom(getTextualUploadDate()));
|
return new DateWrapper(parseDateFrom(getTextualUploadDate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
return data.getString("thumb_url");
|
return getThumbnailsFromStreamItem(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -91,8 +95,8 @@ public class MediaCCCStreamExtractor extends StreamExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
return conferenceData.getString("logo_url");
|
return getImageListFromLogoImageUrl(conferenceData.getString("logo_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,10 +2,16 @@ package org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems;
|
|||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
|
||||||
|
|
||||||
public class MediaCCCConferenceInfoItemExtractor implements ChannelInfoItemExtractor {
|
public class MediaCCCConferenceInfoItemExtractor implements ChannelInfoItemExtractor {
|
||||||
private final JsonObject conference;
|
private final JsonObject conference;
|
||||||
|
|
||||||
@ -43,8 +49,9 @@ public class MediaCCCConferenceInfoItemExtractor implements ChannelInfoItemExtra
|
|||||||
return conference.getString("url");
|
return conference.getString("url");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
return conference.getString("logo_url");
|
return getImageListFromLogoImageUrl(conference.getString("logo_url"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
package org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems;
|
package org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper;
|
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromStreamItem;
|
||||||
|
|
||||||
public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
||||||
private final JsonObject event;
|
private final JsonObject event;
|
||||||
@ -46,12 +51,6 @@ public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor
|
|||||||
return event.getString("conference_url");
|
return event.getString("conference_url");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getUploaderAvatarUrl() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUploaderVerified() throws ParsingException {
|
public boolean isUploaderVerified() throws ParsingException {
|
||||||
return false;
|
return false;
|
||||||
@ -84,8 +83,9 @@ public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor
|
|||||||
+ event.getString("guid");
|
+ event.getString("guid");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
return event.getString("thumb_url");
|
return getThumbnailsFromStreamItem(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package org.schabi.newpipe.extractor.services.peertube;
|
|||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||||
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
@ -14,12 +16,21 @@ import org.schabi.newpipe.extractor.services.peertube.extractors.PeertubeSepiaSt
|
|||||||
import org.schabi.newpipe.extractor.services.peertube.extractors.PeertubeStreamInfoItemExtractor;
|
import org.schabi.newpipe.extractor.services.peertube.extractors.PeertubeStreamInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||||
import org.schabi.newpipe.extractor.utils.Parser;
|
import org.schabi.newpipe.extractor.utils.Parser;
|
||||||
import org.schabi.newpipe.extractor.utils.Utils;
|
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.Image.HEIGHT_UNKNOWN;
|
||||||
|
import static org.schabi.newpipe.extractor.Image.WIDTH_UNKNOWN;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
public final class PeertubeParsingHelper {
|
public final class PeertubeParsingHelper {
|
||||||
public static final String START_KEY = "start";
|
public static final String START_KEY = "start";
|
||||||
@ -32,7 +43,7 @@ public final class PeertubeParsingHelper {
|
|||||||
|
|
||||||
public static void validate(final JsonObject json) throws ContentNotAvailableException {
|
public static void validate(final JsonObject json) throws ContentNotAvailableException {
|
||||||
final String error = json.getString("error");
|
final String error = json.getString("error");
|
||||||
if (!Utils.isBlank(error)) {
|
if (!isBlank(error)) {
|
||||||
throw new ContentNotAvailableException(error);
|
throw new ContentNotAvailableException(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +64,7 @@ public final class PeertubeParsingHelper {
|
|||||||
} catch (final Parser.RegexException e) {
|
} catch (final Parser.RegexException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (Utils.isBlank(prevStart)) {
|
if (isBlank(prevStart)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,4 +139,179 @@ public final class PeertubeParsingHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get avatars from a {@code ownerAccount} or a {@code videoChannel} {@link JsonObject}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the {@code avatars} {@link JsonArray} is present and non null or empty, avatars will be
|
||||||
|
* extracted from this array using {@link #getImagesFromAvatarOrBannerArray(String, JsonArray)}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If that's not the case, an avatar will extracted using the {@code avatar} {@link JsonObject}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that only images for which paths are not null and not empty will be added to the
|
||||||
|
* unmodifiable {@link Image} list returned.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param baseUrl the base URL of the PeerTube instance
|
||||||
|
* @param ownerAccountOrVideoChannelObject the {@code ownerAccount} or {@code videoChannel}
|
||||||
|
* {@link JsonObject}
|
||||||
|
* @return an unmodifiable list of {@link Image}s, which may be empty but never null
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getAvatarsFromOwnerAccountOrVideoChannelObject(
|
||||||
|
@Nonnull final String baseUrl,
|
||||||
|
@Nonnull final JsonObject ownerAccountOrVideoChannelObject) {
|
||||||
|
return getImagesFromAvatarsOrBanners(baseUrl, ownerAccountOrVideoChannelObject,
|
||||||
|
"avatars", "avatar");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get banners from a {@code ownerAccount} or a {@code videoChannel} {@link JsonObject}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the {@code banners} {@link JsonArray} is present and non null or empty, banners will be
|
||||||
|
* extracted from this array using {@link #getImagesFromAvatarOrBannerArray(String, JsonArray)}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If that's not the case, a banner will extracted using the {@code banner} {@link JsonObject}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that only images for which paths are not null and not empty will be added to the
|
||||||
|
* unmodifiable {@link Image} list returned.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param baseUrl the base URL of the PeerTube instance
|
||||||
|
* @param ownerAccountOrVideoChannelObject the {@code ownerAccount} or {@code videoChannel}
|
||||||
|
* {@link JsonObject}
|
||||||
|
* @return an unmodifiable list of {@link Image}s, which may be empty but never null
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getBannersFromAccountOrVideoChannelObject(
|
||||||
|
@Nonnull final String baseUrl,
|
||||||
|
@Nonnull final JsonObject ownerAccountOrVideoChannelObject) {
|
||||||
|
return getImagesFromAvatarsOrBanners(baseUrl, ownerAccountOrVideoChannelObject,
|
||||||
|
"banners", "banner");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get thumbnails from a playlist or a video item {@link JsonObject}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* PeerTube provides two thumbnails in its API: a low one, represented by the value of the
|
||||||
|
* {@code thumbnailPath} key, and a medium one, represented by the value of the
|
||||||
|
* {@code previewPath} key.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If a value is not null or empty, an {@link Image} will be added to the list returned with
|
||||||
|
* the URL to the thumbnail ({@code baseUrl + value}), a height and a width unknown and the
|
||||||
|
* corresponding resolution level (see above).
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param baseUrl the base URL of the PeerTube instance
|
||||||
|
* @param playlistOrVideoItemObject the playlist or the video item {@link JsonObject}, which
|
||||||
|
* must not be null
|
||||||
|
* @return an unmodifiable list of {@link Image}s, which is never null but can be empty
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getThumbnailsFromPlaylistOrVideoItem(
|
||||||
|
@Nonnull final String baseUrl,
|
||||||
|
@Nonnull final JsonObject playlistOrVideoItemObject) {
|
||||||
|
final List<Image> imageList = new ArrayList<>(2);
|
||||||
|
|
||||||
|
final String thumbnailPath = playlistOrVideoItemObject.getString("thumbnailPath");
|
||||||
|
if (!isNullOrEmpty(thumbnailPath)) {
|
||||||
|
imageList.add(new Image(baseUrl + thumbnailPath, HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||||
|
ResolutionLevel.LOW));
|
||||||
|
}
|
||||||
|
|
||||||
|
final String previewPath = playlistOrVideoItemObject.getString("previewPath");
|
||||||
|
if (!isNullOrEmpty(previewPath)) {
|
||||||
|
imageList.add(new Image(baseUrl + previewPath, HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||||
|
ResolutionLevel.MEDIUM));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(imageList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to get avatars and banners from video channels and accounts from given name
|
||||||
|
* keys.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Only images for which paths are not null and not empty will be added to the unmodifiable
|
||||||
|
* {@link Image} list returned and only the width of avatars or banners is provided by the API,
|
||||||
|
* and so is the only image dimension known.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param baseUrl the base URL of the PeerTube instance
|
||||||
|
* @param ownerAccountOrVideoChannelObject the {@code ownerAccount} or {@code videoChannel}
|
||||||
|
* {@link JsonObject}
|
||||||
|
* @param jsonArrayName the key name of the {@link JsonArray} to which
|
||||||
|
* extract all images available ({@code avatars} or
|
||||||
|
* {@code banners})
|
||||||
|
* @param jsonObjectName the key name of the {@link JsonObject} to which
|
||||||
|
* extract a single image ({@code avatar} or
|
||||||
|
* {@code banner}), used as a fallback if the images
|
||||||
|
* {@link JsonArray} is null or empty
|
||||||
|
* @return an unmodifiable list of {@link Image}s, which may be empty but never null
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
private static List<Image> getImagesFromAvatarsOrBanners(
|
||||||
|
@Nonnull final String baseUrl,
|
||||||
|
@Nonnull final JsonObject ownerAccountOrVideoChannelObject,
|
||||||
|
@Nonnull final String jsonArrayName,
|
||||||
|
@Nonnull final String jsonObjectName) {
|
||||||
|
final JsonArray images = ownerAccountOrVideoChannelObject.getArray(jsonArrayName);
|
||||||
|
|
||||||
|
if (!isNullOrEmpty(images)) {
|
||||||
|
return getImagesFromAvatarOrBannerArray(baseUrl, images);
|
||||||
|
}
|
||||||
|
|
||||||
|
final JsonObject image = ownerAccountOrVideoChannelObject.getObject(jsonObjectName);
|
||||||
|
final String path = image.getString("path");
|
||||||
|
if (!isNullOrEmpty(path)) {
|
||||||
|
return List.of(new Image(baseUrl + path, HEIGHT_UNKNOWN,
|
||||||
|
image.getInt("width", WIDTH_UNKNOWN), ResolutionLevel.UNKNOWN));
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get {@link Image}s from an {@code avatars} or a {@code banners} {@link JsonArray}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Only images for which paths are not null and not empty will be added to the
|
||||||
|
* unmodifiable {@link Image} list returned.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that only the width of avatars or banners is provided by the API, and so only is the
|
||||||
|
* only dimension known of images.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param baseUrl the base URL of the PeerTube instance from which the
|
||||||
|
* {@code avatarsOrBannersArray} {@link JsonArray} comes from
|
||||||
|
* @param avatarsOrBannersArray an {@code avatars} or {@code banners} {@link JsonArray}
|
||||||
|
* @return an unmodifiable list of {@link Image}s, which may be empty but never null
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
private static List<Image> getImagesFromAvatarOrBannerArray(
|
||||||
|
@Nonnull final String baseUrl,
|
||||||
|
@Nonnull final JsonArray avatarsOrBannersArray) {
|
||||||
|
return avatarsOrBannersArray.stream()
|
||||||
|
.filter(JsonObject.class::isInstance)
|
||||||
|
.map(JsonObject.class::cast)
|
||||||
|
.filter(image -> !isNullOrEmpty(image.getString("path")))
|
||||||
|
.map(image -> new Image(baseUrl + image.getString("path"), HEIGHT_UNKNOWN,
|
||||||
|
image.getInt("width", WIDTH_UNKNOWN), ResolutionLevel.UNKNOWN))
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import com.grack.nanojson.JsonArray;
|
|||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||||
@ -22,6 +23,9 @@ import javax.annotation.Nullable;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getBannersFromAccountOrVideoChannelObject;
|
||||||
|
|
||||||
public class PeertubeAccountExtractor extends ChannelExtractor {
|
public class PeertubeAccountExtractor extends ChannelExtractor {
|
||||||
private JsonObject json;
|
private JsonObject json;
|
||||||
private final String baseUrl;
|
private final String baseUrl;
|
||||||
@ -33,20 +37,16 @@ public class PeertubeAccountExtractor extends ChannelExtractor {
|
|||||||
this.baseUrl = getBaseUrl();
|
this.baseUrl = getBaseUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getAvatarUrl() {
|
public List<Image> getAvatars() {
|
||||||
String value;
|
return getAvatarsFromOwnerAccountOrVideoChannelObject(baseUrl, json);
|
||||||
try {
|
|
||||||
value = JsonUtils.getString(json, "avatar.path");
|
|
||||||
} catch (final Exception e) {
|
|
||||||
value = "/client/assets/images/default-avatar.png";
|
|
||||||
}
|
|
||||||
return baseUrl + value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getBannerUrl() {
|
public List<Image> getBanners() {
|
||||||
return null;
|
return getBannersFromAccountOrVideoChannelObject(baseUrl, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -99,9 +99,10 @@ public class PeertubeAccountExtractor extends ChannelExtractor {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getParentChannelAvatarUrl() {
|
public List<Image> getParentChannelAvatars() {
|
||||||
return "";
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -3,6 +3,7 @@ package org.schabi.newpipe.extractor.services.peertube.extractors;
|
|||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||||
@ -20,6 +21,9 @@ import javax.annotation.Nullable;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getBannersFromAccountOrVideoChannelObject;
|
||||||
|
|
||||||
public class PeertubeChannelExtractor extends ChannelExtractor {
|
public class PeertubeChannelExtractor extends ChannelExtractor {
|
||||||
private JsonObject json;
|
private JsonObject json;
|
||||||
private final String baseUrl;
|
private final String baseUrl;
|
||||||
@ -30,20 +34,16 @@ public class PeertubeChannelExtractor extends ChannelExtractor {
|
|||||||
this.baseUrl = getBaseUrl();
|
this.baseUrl = getBaseUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getAvatarUrl() {
|
public List<Image> getAvatars() {
|
||||||
String value;
|
return getAvatarsFromOwnerAccountOrVideoChannelObject(baseUrl, json);
|
||||||
try {
|
|
||||||
value = JsonUtils.getString(json, "avatar.path");
|
|
||||||
} catch (final Exception e) {
|
|
||||||
value = "/client/assets/images/default-avatar.png";
|
|
||||||
}
|
|
||||||
return baseUrl + value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getBannerUrl() {
|
public List<Image> getBanners() {
|
||||||
return null;
|
return getBannersFromAccountOrVideoChannelObject(baseUrl, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -72,15 +72,11 @@ public class PeertubeChannelExtractor extends ChannelExtractor {
|
|||||||
return JsonUtils.getString(json, "ownerAccount.url");
|
return JsonUtils.getString(json, "ownerAccount.url");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getParentChannelAvatarUrl() {
|
public List<Image> getParentChannelAvatars() {
|
||||||
String value;
|
return getAvatarsFromOwnerAccountOrVideoChannelObject(
|
||||||
try {
|
baseUrl, json.getObject("ownerAccount"));
|
||||||
value = JsonUtils.getString(json, "ownerAccount.avatar.path");
|
|
||||||
} catch (final Exception e) {
|
|
||||||
value = "/client/assets/images/default-avatar.png";
|
|
||||||
}
|
|
||||||
return baseUrl + value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
package org.schabi.newpipe.extractor.services.peertube.extractors;
|
package org.schabi.newpipe.extractor.services.peertube.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.Comparator;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject;
|
||||||
|
|
||||||
public class PeertubeChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
public class PeertubeChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
||||||
|
|
||||||
final JsonObject item;
|
private final JsonObject item;
|
||||||
final JsonObject uploader;
|
private final String baseUrl;
|
||||||
final String baseUrl;
|
|
||||||
public PeertubeChannelInfoItemExtractor(@Nonnull final JsonObject item,
|
public PeertubeChannelInfoItemExtractor(@Nonnull final JsonObject item,
|
||||||
@Nonnull final String baseUrl) {
|
@Nonnull final String baseUrl) {
|
||||||
this.item = item;
|
this.item = item;
|
||||||
this.uploader = item.getObject("uploader");
|
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,14 +32,10 @@ public class PeertubeChannelInfoItemExtractor implements ChannelInfoItemExtracto
|
|||||||
return item.getString("url");
|
return item.getString("url");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return item.getArray("avatars").stream()
|
return getAvatarsFromOwnerAccountOrVideoChannelObject(baseUrl, item);
|
||||||
.filter(JsonObject.class::isInstance)
|
|
||||||
.map(JsonObject.class::cast)
|
|
||||||
.max(Comparator.comparingInt(avatar -> avatar.getInt("width")))
|
|
||||||
.map(avatar -> baseUrl + avatar.getString("path"))
|
|
||||||
.orElse(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -6,21 +6,24 @@ import com.grack.nanojson.JsonObject;
|
|||||||
import com.grack.nanojson.JsonWriter;
|
import com.grack.nanojson.JsonWriter;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.ServiceList;
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItemExtractor;
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper;
|
|
||||||
import org.schabi.newpipe.extractor.stream.Description;
|
import org.schabi.newpipe.extractor.stream.Description;
|
||||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.peertube.extractors.PeertubeCommentsExtractor.CHILDREN;
|
import static org.schabi.newpipe.extractor.services.peertube.extractors.PeertubeCommentsExtractor.CHILDREN;
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.parseDateFrom;
|
||||||
|
|
||||||
public class PeertubeCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
public class PeertubeCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -52,15 +55,10 @@ public class PeertubeCommentsInfoItemExtractor implements CommentsInfoItemExtrac
|
|||||||
return url + "/" + getCommentId();
|
return url + "/" + getCommentId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
String value;
|
return getUploaderAvatars();
|
||||||
try {
|
|
||||||
value = JsonUtils.getString(item, "account.avatar.path");
|
|
||||||
} catch (final Exception e) {
|
|
||||||
value = "/client/assets/images/default-avatar.png";
|
|
||||||
}
|
|
||||||
return baseUrl + value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -76,7 +74,7 @@ public class PeertubeCommentsInfoItemExtractor implements CommentsInfoItemExtrac
|
|||||||
@Override
|
@Override
|
||||||
public DateWrapper getUploadDate() throws ParsingException {
|
public DateWrapper getUploadDate() throws ParsingException {
|
||||||
final String textualUploadDate = getTextualUploadDate();
|
final String textualUploadDate = getTextualUploadDate();
|
||||||
return new DateWrapper(PeertubeParsingHelper.parseDateFrom(textualUploadDate));
|
return new DateWrapper(parseDateFrom(textualUploadDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -97,15 +95,10 @@ public class PeertubeCommentsInfoItemExtractor implements CommentsInfoItemExtrac
|
|||||||
return Objects.toString(item.getLong("id"), null);
|
return Objects.toString(item.getLong("id"), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
String value;
|
return getAvatarsFromOwnerAccountOrVideoChannelObject(baseUrl, item.getObject("account"));
|
||||||
try {
|
|
||||||
value = JsonUtils.getString(item, "account.avatar.path");
|
|
||||||
} catch (final Exception e) {
|
|
||||||
value = "/client/assets/images/default-avatar.png";
|
|
||||||
}
|
|
||||||
return baseUrl + value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -3,6 +3,7 @@ package org.schabi.newpipe.extractor.services.peertube.extractors;
|
|||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
@ -19,11 +20,14 @@ import org.schabi.newpipe.extractor.utils.Utils;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.COUNT_KEY;
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.COUNT_KEY;
|
||||||
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.ITEMS_PER_PAGE;
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.ITEMS_PER_PAGE;
|
||||||
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.START_KEY;
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.START_KEY;
|
||||||
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.collectItemsFrom;
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.collectItemsFrom;
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getThumbnailsFromPlaylistOrVideoItem;
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
public class PeertubePlaylistExtractor extends PlaylistExtractor {
|
public class PeertubePlaylistExtractor extends PlaylistExtractor {
|
||||||
@ -36,8 +40,8 @@ public class PeertubePlaylistExtractor extends PlaylistExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return getBaseUrl() + playlistInfo.getString("thumbnailPath");
|
return getThumbnailsFromPlaylistOrVideoItem(getBaseUrl(), playlistInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -50,10 +54,11 @@ public class PeertubePlaylistExtractor extends PlaylistExtractor {
|
|||||||
return playlistInfo.getObject("ownerAccount").getString("displayName");
|
return playlistInfo.getObject("ownerAccount").getString("displayName");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() throws ParsingException {
|
public List<Image> getUploaderAvatars() throws ParsingException {
|
||||||
return getBaseUrl()
|
return getAvatarsFromOwnerAccountOrVideoChannelObject(getBaseUrl(),
|
||||||
+ playlistInfo.getObject("ownerAccount").getObject("avatar").getString("path");
|
playlistInfo.getObject("ownerAccount"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -90,9 +95,9 @@ public class PeertubePlaylistExtractor extends PlaylistExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getSubChannelAvatarUrl() throws ParsingException {
|
public List<Image> getSubChannelAvatars() throws ParsingException {
|
||||||
return getBaseUrl()
|
return getAvatarsFromOwnerAccountOrVideoChannelObject(getBaseUrl(),
|
||||||
+ playlistInfo.getObject("videoChannel").getObject("avatar").getString("path");
|
playlistInfo.getObject("videoChannel"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -2,19 +2,22 @@ package org.schabi.newpipe.extractor.services.peertube.extractors;
|
|||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.Description;
|
import org.schabi.newpipe.extractor.stream.Description;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getThumbnailsFromPlaylistOrVideoItem;
|
||||||
|
|
||||||
public class PeertubePlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
public class PeertubePlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||||
|
|
||||||
final JsonObject item;
|
private final JsonObject item;
|
||||||
final JsonObject uploader;
|
private final JsonObject uploader;
|
||||||
final String baseUrl;
|
private final String baseUrl;
|
||||||
|
|
||||||
public PeertubePlaylistInfoItemExtractor(@Nonnull final JsonObject item,
|
public PeertubePlaylistInfoItemExtractor(@Nonnull final JsonObject item,
|
||||||
@Nonnull final String baseUrl) {
|
@Nonnull final String baseUrl) {
|
||||||
@ -33,9 +36,10 @@ public class PeertubePlaylistInfoItemExtractor implements PlaylistInfoItemExtrac
|
|||||||
return item.getString("url");
|
return item.getString("url");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return baseUrl + item.getString("thumbnailPath");
|
return getThumbnailsFromPlaylistOrVideoItem(baseUrl, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.schabi.newpipe.extractor.services.peertube.extractors;
|
package org.schabi.newpipe.extractor.services.peertube.extractors;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getThumbnailsFromPlaylistOrVideoItem;
|
||||||
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
|
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
@ -7,7 +9,7 @@ import com.grack.nanojson.JsonArray;
|
|||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
@ -87,8 +89,8 @@ public class PeertubeStreamExtractor extends StreamExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return baseUrl + JsonUtils.getString(json, "previewPath");
|
return getThumbnailsFromPlaylistOrVideoItem(baseUrl, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -176,14 +178,8 @@ public class PeertubeStreamExtractor extends StreamExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
String value;
|
return getAvatarsFromOwnerAccountOrVideoChannelObject(baseUrl, json.getObject("account"));
|
||||||
try {
|
|
||||||
value = JsonUtils.getString(json, "account.avatar.path");
|
|
||||||
} catch (final Exception e) {
|
|
||||||
value = "/client/assets/images/default-avatar.png";
|
|
||||||
}
|
|
||||||
return baseUrl + value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -200,14 +196,8 @@ public class PeertubeStreamExtractor extends StreamExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getSubChannelAvatarUrl() {
|
public List<Image> getSubChannelAvatars() {
|
||||||
String value;
|
return getAvatarsFromOwnerAccountOrVideoChannelObject(baseUrl, json.getObject("channel"));
|
||||||
try {
|
|
||||||
value = JsonUtils.getString(json, "channel.avatar.path");
|
|
||||||
} catch (final Exception e) {
|
|
||||||
value = "/client/assets/images/default-avatar.png";
|
|
||||||
}
|
|
||||||
return baseUrl + value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
package org.schabi.newpipe.extractor.services.peertube.extractors;
|
package org.schabi.newpipe.extractor.services.peertube.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.ServiceList;
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getAvatarsFromOwnerAccountOrVideoChannelObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.getThumbnailsFromPlaylistOrVideoItem;
|
||||||
|
import static org.schabi.newpipe.extractor.services.peertube.PeertubeParsingHelper.parseDateFrom;
|
||||||
|
|
||||||
public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
||||||
|
|
||||||
@ -27,9 +32,10 @@ public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor
|
|||||||
return ServiceList.PeerTube.getStreamLHFactory().fromId(uuid, baseUrl).getUrl();
|
return ServiceList.PeerTube.getStreamLHFactory().fromId(uuid, baseUrl).getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return baseUrl + JsonUtils.getString(item, "thumbnailPath");
|
return getThumbnailsFromPlaylistOrVideoItem(baseUrl, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -56,14 +62,10 @@ public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor
|
|||||||
.fromId("accounts/" + name + "@" + host, baseUrl).getUrl();
|
.fromId("accounts/" + name + "@" + host, baseUrl).getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
final JsonObject account = item.getObject("account");
|
return getAvatarsFromOwnerAccountOrVideoChannelObject(baseUrl, item.getObject("account"));
|
||||||
if (account.has("avatar") && !account.isNull("avatar")) {
|
|
||||||
return baseUrl + account.getObject("avatar").getString("path");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -89,7 +91,7 @@ public class PeertubeStreamInfoItemExtractor implements StreamInfoItemExtractor
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DateWrapper(PeertubeParsingHelper.parseDateFrom(textualUploadDate));
|
return new DateWrapper(parseDateFrom(textualUploadDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
package org.schabi.newpipe.extractor.services.soundcloud;
|
package org.schabi.newpipe.extractor.services.soundcloud;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.Image.ResolutionLevel.LOW;
|
||||||
|
import static org.schabi.newpipe.extractor.Image.ResolutionLevel.MEDIUM;
|
||||||
|
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
@ -9,6 +15,7 @@ import org.jsoup.nodes.Document;
|
|||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
import org.jsoup.select.Elements;
|
import org.jsoup.select.Elements;
|
||||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemsCollector;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItemsCollector;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
@ -20,12 +27,14 @@ import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudCha
|
|||||||
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudPlaylistInfoItemExtractor;
|
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudPlaylistInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudStreamInfoItemExtractor;
|
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudStreamInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
||||||
|
import org.schabi.newpipe.extractor.utils.ImageSuffix;
|
||||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||||
import org.schabi.newpipe.extractor.utils.Parser;
|
import org.schabi.newpipe.extractor.utils.Parser;
|
||||||
import org.schabi.newpipe.extractor.utils.Parser.RegexException;
|
import org.schabi.newpipe.extractor.utils.Parser.RegexException;
|
||||||
import org.schabi.newpipe.extractor.utils.Utils;
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -35,12 +44,47 @@ import java.time.format.DateTimeParseException;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
|
||||||
|
|
||||||
public final class SoundcloudParsingHelper {
|
public final class SoundcloudParsingHelper {
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
|
// From https://web.archive.org/web/20210214185000/https://developers.soundcloud.com/docs/api/reference#tracks
|
||||||
|
// and researches on images used by the websites
|
||||||
|
// CHECKSTYLE:ON
|
||||||
|
/*
|
||||||
|
SoundCloud avatars and artworks are almost squares
|
||||||
|
|
||||||
|
When we get non-square pictures, all these images variants are still squares, except the
|
||||||
|
original and the crop versions provides images which are respecting aspect ratios.
|
||||||
|
The websites only use the square variants.
|
||||||
|
|
||||||
|
t2400x2400 and t3000x3000 variants also exists, but are not returned as several images are
|
||||||
|
uploaded with a lower size than these variants: in this case, these variants return an upscaled
|
||||||
|
version of the original image.
|
||||||
|
*/
|
||||||
|
private static final List<ImageSuffix> ALBUMS_AND_ARTWORKS_URL_SUFFIXES_AND_RESOLUTIONS =
|
||||||
|
List.of(new ImageSuffix("mini.jpg", 16, 16, LOW),
|
||||||
|
new ImageSuffix("t20x20.jpg", 20, 20, LOW),
|
||||||
|
new ImageSuffix("small.jpg", 32, 32, LOW),
|
||||||
|
new ImageSuffix("badge.jpg", 47, 47, LOW),
|
||||||
|
new ImageSuffix("t50x50.jpg", 50, 50, LOW),
|
||||||
|
new ImageSuffix("t60x60.jpg", 60, 60, LOW),
|
||||||
|
// Seems to work also on avatars, even if it is written to be not the case in
|
||||||
|
// the old API docs
|
||||||
|
new ImageSuffix("t67x67.jpg", 67, 67, LOW),
|
||||||
|
new ImageSuffix("t80x80.jpg", 80, 80, LOW),
|
||||||
|
new ImageSuffix("large.jpg", 100, 100, LOW),
|
||||||
|
new ImageSuffix("t120x120.jpg", 120, 120, LOW),
|
||||||
|
new ImageSuffix("t200x200.jpg", 200, 200, MEDIUM),
|
||||||
|
new ImageSuffix("t240x240.jpg", 240, 240, MEDIUM),
|
||||||
|
new ImageSuffix("t250x250.jpg", 250, 250, MEDIUM),
|
||||||
|
new ImageSuffix("t300x300.jpg", 300, 300, MEDIUM),
|
||||||
|
new ImageSuffix("t500x500.jpg", 500, 500, MEDIUM));
|
||||||
|
|
||||||
|
private static final List<ImageSuffix> VISUALS_URL_SUFFIXES_AND_RESOLUTIONS =
|
||||||
|
List.of(new ImageSuffix("t1240x260.jpg", 1240, 260, MEDIUM),
|
||||||
|
new ImageSuffix("t2480x520.jpg", 2480, 520, MEDIUM));
|
||||||
|
|
||||||
private static String clientId;
|
private static String clientId;
|
||||||
public static final String SOUNDCLOUD_API_V2_URL = "https://api-v2.soundcloud.com/";
|
public static final String SOUNDCLOUD_API_V2_URL = "https://api-v2.soundcloud.com/";
|
||||||
|
|
||||||
@ -366,4 +410,57 @@ public final class SoundcloudParsingHelper {
|
|||||||
public static String getUploaderName(final JsonObject object) {
|
public static String getUploaderName(final JsonObject object) {
|
||||||
return object.getObject("user").getString("username", "");
|
return object.getObject("user").getString("username", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getAllImagesFromTrackObject(@Nonnull final JsonObject trackObject)
|
||||||
|
throws ParsingException {
|
||||||
|
final String artworkUrl = trackObject.getString("artwork_url");
|
||||||
|
if (artworkUrl != null) {
|
||||||
|
return getAllImagesFromArtworkOrAvatarUrl(artworkUrl);
|
||||||
|
}
|
||||||
|
final String avatarUrl = trackObject.getObject("user").getString("avatar_url");
|
||||||
|
if (avatarUrl != null) {
|
||||||
|
return getAllImagesFromArtworkOrAvatarUrl(avatarUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ParsingException("Could not get track or track user's thumbnails");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getAllImagesFromArtworkOrAvatarUrl(
|
||||||
|
@Nullable final String originalArtworkOrAvatarUrl) {
|
||||||
|
if (isNullOrEmpty(originalArtworkOrAvatarUrl)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAllImagesFromImageUrlReturned(
|
||||||
|
// Artwork and avatars are originally returned with the "large" resolution, which
|
||||||
|
// is 100x100
|
||||||
|
originalArtworkOrAvatarUrl.replace("large.jpg", ""),
|
||||||
|
ALBUMS_AND_ARTWORKS_URL_SUFFIXES_AND_RESOLUTIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getAllImagesFromVisualUrl(
|
||||||
|
@Nullable final String originalVisualUrl) {
|
||||||
|
if (isNullOrEmpty(originalVisualUrl)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAllImagesFromImageUrlReturned(
|
||||||
|
// Images are originally returned with the "original" resolution, which may be
|
||||||
|
// huge so don't include it for size purposes
|
||||||
|
originalVisualUrl.replace("original.jpg", ""),
|
||||||
|
VISUALS_URL_SUFFIXES_AND_RESOLUTIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Image> getAllImagesFromImageUrlReturned(
|
||||||
|
@Nonnull final String baseImageUrl,
|
||||||
|
@Nonnull final List<ImageSuffix> imageSuffixes) {
|
||||||
|
return imageSuffixes.stream()
|
||||||
|
.map(imageSuffix -> new Image(baseImageUrl + imageSuffix.getSuffix(),
|
||||||
|
imageSuffix.getHeight(), imageSuffix.getWidth(),
|
||||||
|
imageSuffix.getResolutionLevel()))
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromVisualUrl;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||||
@ -59,15 +62,19 @@ public class SoundcloudChannelExtractor extends ChannelExtractor {
|
|||||||
return user.getString("username");
|
return user.getString("username");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getAvatarUrl() {
|
public List<Image> getAvatars() {
|
||||||
return user.getString("avatar_url");
|
return getAllImagesFromArtworkOrAvatarUrl(user.getString("avatar_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getBannerUrl() {
|
public List<Image> getBanners() {
|
||||||
return user.getObject("visuals").getArray("visuals").getObject(0)
|
return getAllImagesFromVisualUrl(user.getObject("visuals")
|
||||||
.getString("visual_url");
|
.getArray("visuals")
|
||||||
|
.getObject(0)
|
||||||
|
.getString("visual_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -95,9 +102,10 @@ public class SoundcloudChannelExtractor extends ChannelExtractor {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getParentChannelAvatarUrl() {
|
public List<Image> getParentChannelAvatars() {
|
||||||
return "";
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||||
|
|
||||||
public class SoundcloudChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
public class SoundcloudChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
||||||
private final JsonObject itemObject;
|
private final JsonObject itemObject;
|
||||||
|
|
||||||
@ -23,10 +27,10 @@ public class SoundcloudChannelInfoItemExtractor implements ChannelInfoItemExtrac
|
|||||||
return replaceHttpWithHttps(itemObject.getString("permalink_url"));
|
return replaceHttpWithHttps(itemObject.getString("permalink_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
// An avatar URL with a better resolution
|
return getAllImagesFromArtworkOrAvatarUrl(itemObject.getString("avatar_url"));
|
||||||
return itemObject.getString("avatar_url", "").replace("large.jpg", "crop.jpg");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItemExtractor;
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper;
|
|
||||||
import org.schabi.newpipe.extractor.stream.Description;
|
import org.schabi.newpipe.extractor.stream.Description;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDateFrom;
|
||||||
|
|
||||||
public class SoundcloudCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
public class SoundcloudCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
||||||
private final JsonObject json;
|
private final JsonObject json;
|
||||||
private final String url;
|
private final String url;
|
||||||
@ -34,9 +39,10 @@ public class SoundcloudCommentsInfoItemExtractor implements CommentsInfoItemExtr
|
|||||||
return json.getObject("user").getString("username");
|
return json.getObject("user").getString("username");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
return json.getObject("user").getString("avatar_url");
|
return getAllImagesFromArtworkOrAvatarUrl(json.getObject("user").getString("avatar_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -45,7 +51,7 @@ public class SoundcloudCommentsInfoItemExtractor implements CommentsInfoItemExtr
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getStreamPosition() throws ParsingException {
|
public int getStreamPosition() {
|
||||||
return json.getInt("timestamp") / 1000; // convert milliseconds to seconds
|
return json.getInt("timestamp") / 1000; // convert milliseconds to seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +68,7 @@ public class SoundcloudCommentsInfoItemExtractor implements CommentsInfoItemExtr
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public DateWrapper getUploadDate() throws ParsingException {
|
public DateWrapper getUploadDate() throws ParsingException {
|
||||||
return new DateWrapper(SoundcloudParsingHelper.parseDateFrom(getTextualUploadDate()));
|
return new DateWrapper(parseDateFrom(getTextualUploadDate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -75,8 +81,9 @@ public class SoundcloudCommentsInfoItemExtractor implements CommentsInfoItemExtr
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
return json.getObject("user").getString("avatar_url");
|
return getAllImagesFromArtworkOrAvatarUrl(json.getObject("user").getString("avatar_url"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAvatarUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
@ -24,9 +31,6 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|
||||||
|
|
||||||
public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
|
public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
|
||||||
private static final int STREAMS_PER_REQUESTED_PAGE = 15;
|
private static final int STREAMS_PER_REQUESTED_PAGE = 15;
|
||||||
|
|
||||||
@ -68,30 +72,28 @@ public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
String artworkUrl = playlist.getString("artwork_url");
|
final String artworkUrl = playlist.getString("artwork_url");
|
||||||
|
|
||||||
if (artworkUrl == null) {
|
if (!isNullOrEmpty(artworkUrl)) {
|
||||||
// If the thumbnail is null, traverse the items list and get a valid one,
|
return getAllImagesFromArtworkOrAvatarUrl(artworkUrl);
|
||||||
// if it also fails, return null
|
}
|
||||||
|
|
||||||
|
// If the thumbnail is null or empty, traverse the items list and get a valid one
|
||||||
|
// If it also fails, return an empty list
|
||||||
try {
|
try {
|
||||||
final InfoItemsPage<StreamInfoItem> infoItems = getInitialPage();
|
final InfoItemsPage<StreamInfoItem> infoItems = getInitialPage();
|
||||||
|
|
||||||
for (final StreamInfoItem item : infoItems.getItems()) {
|
for (final StreamInfoItem item : infoItems.getItems()) {
|
||||||
artworkUrl = item.getThumbnailUrl();
|
final List<Image> thumbnails = item.getThumbnails();
|
||||||
if (!isNullOrEmpty(artworkUrl)) {
|
if (!isNullOrEmpty(thumbnails)) {
|
||||||
break;
|
return thumbnails;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (artworkUrl == null) {
|
return List.of();
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return artworkUrl.replace("large.jpg", "crop.jpg");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -104,9 +106,10 @@ public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
|
|||||||
return SoundcloudParsingHelper.getUploaderName(playlist);
|
return SoundcloudParsingHelper.getUploaderName(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
return SoundcloudParsingHelper.getAvatarUrl(playlist);
|
return getAllImagesFromArtworkOrAvatarUrl(getAvatarUrl(playlist));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||||
|
|
||||||
public class SoundcloudPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
public class SoundcloudPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||||
private static final String USER_KEY = "user";
|
private static final String USER_KEY = "user";
|
||||||
private static final String AVATAR_URL_KEY = "avatar_url";
|
private static final String AVATAR_URL_KEY = "avatar_url";
|
||||||
@ -28,36 +33,35 @@ public class SoundcloudPlaylistInfoItemExtractor implements PlaylistInfoItemExtr
|
|||||||
return replaceHttpWithHttps(itemObject.getString("permalink_url"));
|
return replaceHttpWithHttps(itemObject.getString("permalink_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
// Over-engineering at its finest
|
// Over-engineering at its finest
|
||||||
if (itemObject.isString(ARTWORK_URL_KEY)) {
|
if (itemObject.isString(ARTWORK_URL_KEY)) {
|
||||||
final String artworkUrl = itemObject.getString(ARTWORK_URL_KEY, "");
|
final String artworkUrl = itemObject.getString(ARTWORK_URL_KEY);
|
||||||
if (!artworkUrl.isEmpty()) {
|
if (!isNullOrEmpty(artworkUrl)) {
|
||||||
// An artwork URL with a better resolution
|
return getAllImagesFromArtworkOrAvatarUrl(artworkUrl);
|
||||||
return artworkUrl.replace("large.jpg", "crop.jpg");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Look for artwork url inside the track list
|
// Look for artwork URL inside the track list
|
||||||
for (final Object track : itemObject.getArray("tracks")) {
|
for (final Object track : itemObject.getArray("tracks")) {
|
||||||
final JsonObject trackObject = (JsonObject) track;
|
final JsonObject trackObject = (JsonObject) track;
|
||||||
|
|
||||||
// First look for track artwork url
|
// First look for track artwork URL
|
||||||
if (trackObject.isString(ARTWORK_URL_KEY)) {
|
if (trackObject.isString(ARTWORK_URL_KEY)) {
|
||||||
final String artworkUrl = trackObject.getString(ARTWORK_URL_KEY, "");
|
final String artworkUrl = trackObject.getString(ARTWORK_URL_KEY);
|
||||||
if (!artworkUrl.isEmpty()) {
|
if (!isNullOrEmpty(artworkUrl)) {
|
||||||
// An artwork URL with a better resolution
|
return getAllImagesFromArtworkOrAvatarUrl(artworkUrl);
|
||||||
return artworkUrl.replace("large.jpg", "crop.jpg");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then look for track creator avatar url
|
// Then look for track creator avatar URL
|
||||||
final JsonObject creator = trackObject.getObject(USER_KEY);
|
final JsonObject creator = trackObject.getObject(USER_KEY);
|
||||||
final String creatorAvatar = creator.getString(AVATAR_URL_KEY, "");
|
final String creatorAvatar = creator.getString(AVATAR_URL_KEY);
|
||||||
if (!creatorAvatar.isEmpty()) {
|
if (!isNullOrEmpty(creatorAvatar)) {
|
||||||
return creatorAvatar;
|
return getAllImagesFromArtworkOrAvatarUrl(creatorAvatar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
@ -65,10 +69,11 @@ public class SoundcloudPlaylistInfoItemExtractor implements PlaylistInfoItemExtr
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Last resort, use user avatar url. If still not found, then throw exception.
|
// Last resort, use user avatar URL. If still not found, then throw an exception.
|
||||||
return itemObject.getObject(USER_KEY).getString(AVATAR_URL_KEY, "");
|
return getAllImagesFromArtworkOrAvatarUrl(
|
||||||
|
itemObject.getObject(USER_KEY).getString(AVATAR_URL_KEY));
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new ParsingException("Failed to extract playlist thumbnail url", e);
|
throw new ParsingException("Failed to extract playlist thumbnails", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,10 @@ package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
|||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
||||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.clientId;
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.clientId;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromTrackObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAvatarUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDateFrom;
|
||||||
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
|
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
|
||||||
import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN;
|
import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN;
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
@ -11,6 +15,7 @@ import com.grack.nanojson.JsonObject;
|
|||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
@ -96,18 +101,13 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public DateWrapper getUploadDate() throws ParsingException {
|
public DateWrapper getUploadDate() throws ParsingException {
|
||||||
return new DateWrapper(SoundcloudParsingHelper.parseDateFrom(track.getString(
|
return new DateWrapper(parseDateFrom(track.getString("created_at")));
|
||||||
"created_at")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
String artworkUrl = track.getString("artwork_url", "");
|
return getAllImagesFromTrackObject(track);
|
||||||
if (artworkUrl.isEmpty()) {
|
|
||||||
artworkUrl = track.getObject("user").getString("avatar_url", "");
|
|
||||||
}
|
|
||||||
return artworkUrl.replace("large.jpg", "crop.jpg");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -155,8 +155,8 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
return SoundcloudParsingHelper.getAvatarUrl(track);
|
return getAllImagesFromArtworkOrAvatarUrl(getAvatarUrl(track));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromArtworkOrAvatarUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.getAllImagesFromTrackObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.parseDateFrom;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||||
|
|
||||||
public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
||||||
|
|
||||||
protected final JsonObject itemObject;
|
private final JsonObject itemObject;
|
||||||
|
|
||||||
public SoundcloudStreamInfoItemExtractor(final JsonObject itemObject) {
|
public SoundcloudStreamInfoItemExtractor(final JsonObject itemObject) {
|
||||||
this.itemObject = itemObject;
|
this.itemObject = itemObject;
|
||||||
@ -45,10 +49,11 @@ public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtracto
|
|||||||
return replaceHttpWithHttps(itemObject.getObject("user").getString("permalink_url"));
|
return replaceHttpWithHttps(itemObject.getObject("user").getString("permalink_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
return null;
|
return getAllImagesFromArtworkOrAvatarUrl(
|
||||||
|
itemObject.getObject("user").getString("avatar_url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -63,7 +68,7 @@ public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtracto
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DateWrapper getUploadDate() throws ParsingException {
|
public DateWrapper getUploadDate() throws ParsingException {
|
||||||
return new DateWrapper(SoundcloudParsingHelper.parseDateFrom(getTextualUploadDate()));
|
return new DateWrapper(parseDateFrom(getTextualUploadDate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -71,13 +76,10 @@ public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtracto
|
|||||||
return itemObject.getLong("playback_count");
|
return itemObject.getLong("playback_count");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
String artworkUrl = itemObject.getString("artwork_url", "");
|
return getAllImagesFromTrackObject(itemObject);
|
||||||
if (artworkUrl.isEmpty()) {
|
|
||||||
artworkUrl = itemObject.getObject("user").getString("avatar_url");
|
|
||||||
}
|
|
||||||
return artworkUrl.replace("large.jpg", "crop.jpg");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -33,6 +33,9 @@ import com.grack.nanojson.JsonParser;
|
|||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
import com.grack.nanojson.JsonWriter;
|
import com.grack.nanojson.JsonWriter;
|
||||||
import org.jsoup.nodes.Entities;
|
import org.jsoup.nodes.Entities;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||||
import org.schabi.newpipe.extractor.MetaInfo;
|
import org.schabi.newpipe.extractor.MetaInfo;
|
||||||
import org.schabi.newpipe.extractor.downloader.Response;
|
import org.schabi.newpipe.extractor.downloader.Response;
|
||||||
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException;
|
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException;
|
||||||
@ -69,6 +72,7 @@ import java.util.Optional;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
@ -1133,17 +1137,61 @@ public final class YoutubeParsingHelper {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getThumbnailUrlFromInfoItem(final JsonObject infoItem)
|
/**
|
||||||
|
* Get thumbnails from a {@link JsonObject} representing a YouTube
|
||||||
|
* {@link org.schabi.newpipe.extractor.InfoItem InfoItem}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Thumbnails are got from the {@code thumbnails} {@link JsonArray} inside the {@code thumbnail}
|
||||||
|
* {@link JsonObject} of the YouTube {@link org.schabi.newpipe.extractor.InfoItem InfoItem},
|
||||||
|
* using {@link #getImagesFromThumbnailsArray(JsonArray)}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param infoItem a YouTube {@link org.schabi.newpipe.extractor.InfoItem InfoItem}
|
||||||
|
* @return an unmodifiable list of {@link Image}s found in the {@code thumbnails}
|
||||||
|
* {@link JsonArray}
|
||||||
|
* @throws ParsingException if an exception occurs when
|
||||||
|
* {@link #getImagesFromThumbnailsArray(JsonArray)} is executed
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getThumbnailsFromInfoItem(@Nonnull final JsonObject infoItem)
|
||||||
throws ParsingException {
|
throws ParsingException {
|
||||||
// TODO: Don't simply get the first item, but look at all thumbnails and their resolution
|
|
||||||
try {
|
try {
|
||||||
return fixThumbnailUrl(infoItem.getObject("thumbnail").getArray("thumbnails")
|
return getImagesFromThumbnailsArray(infoItem.getObject("thumbnail")
|
||||||
.getObject(0).getString("url"));
|
.getArray("thumbnails"));
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new ParsingException("Could not get thumbnail url", e);
|
throw new ParsingException("Could not get thumbnails from InfoItem", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get images from a YouTube {@code thumbnails} {@link JsonArray}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The properties of the {@link Image}s created will be set using the corresponding ones of
|
||||||
|
* thumbnail items.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param thumbnails a YouTube {@code thumbnails} {@link JsonArray}
|
||||||
|
* @return an unmodifiable list of {@link Image}s extracted from the given {@link JsonArray}
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static List<Image> getImagesFromThumbnailsArray(
|
||||||
|
@Nonnull final JsonArray thumbnails) {
|
||||||
|
return thumbnails.stream()
|
||||||
|
.filter(JsonObject.class::isInstance)
|
||||||
|
.map(JsonObject.class::cast)
|
||||||
|
.filter(thumbnail -> !isNullOrEmpty(thumbnail.getString("url")))
|
||||||
|
.map(thumbnail -> {
|
||||||
|
final int height = thumbnail.getInt("height", Image.HEIGHT_UNKNOWN);
|
||||||
|
return new Image(fixThumbnailUrl(thumbnail.getString("url")),
|
||||||
|
height,
|
||||||
|
thumbnail.getInt("width", Image.WIDTH_UNKNOWN),
|
||||||
|
ResolutionLevel.fromHeight(height));
|
||||||
|
})
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public static String getValidJsonResponseBody(@Nonnull final Response response)
|
public static String getValidJsonResponseBody(@Nonnull final Response response)
|
||||||
throws ParsingException, MalformedURLException {
|
throws ParsingException, MalformedURLException {
|
||||||
|
@ -1,3 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Created by Christian Schabesberger on 25.07.16.
|
||||||
|
*
|
||||||
|
* Copyright (C) Christian Schabesberger 2018 <chris.schabesberger@mailbox.org>
|
||||||
|
* YoutubeChannelExtractor.java is part of NewPipe Extractor.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeChannelHelper.getChannelResponse;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeChannelHelper.getChannelResponse;
|
||||||
@ -8,6 +28,7 @@ import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||||
@ -36,26 +57,6 @@ import java.util.stream.Collectors;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/*
|
|
||||||
* Created by Christian Schabesberger on 25.07.16.
|
|
||||||
*
|
|
||||||
* Copyright (C) Christian Schabesberger 2018 <chris.schabesberger@mailbox.org>
|
|
||||||
* YoutubeChannelExtractor.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class YoutubeChannelExtractor extends ChannelExtractor {
|
public class YoutubeChannelExtractor extends ChannelExtractor {
|
||||||
|
|
||||||
private JsonObject jsonResponse;
|
private JsonObject jsonResponse;
|
||||||
@ -190,16 +191,15 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
|
|||||||
.orElseThrow(() -> new ParsingException("Could not get channel name"));
|
.orElseThrow(() -> new ParsingException("Could not get channel name"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getAvatarUrl() throws ParsingException {
|
public List<Image> getAvatars() throws ParsingException {
|
||||||
assertPageFetched();
|
assertPageFetched();
|
||||||
if (channelAgeGateRenderer != null) {
|
if (channelAgeGateRenderer != null) {
|
||||||
return Optional.ofNullable(channelAgeGateRenderer.getObject("avatar")
|
return Optional.ofNullable(channelAgeGateRenderer.getObject("avatar")
|
||||||
.getArray("thumbnails")
|
.getArray("thumbnails"))
|
||||||
.getObject(0)
|
.map(YoutubeParsingHelper::getImagesFromThumbnailsArray)
|
||||||
.getString("url"))
|
.orElseThrow(() -> new ParsingException("Could not get avatars"));
|
||||||
.map(YoutubeParsingHelper::fixThumbnailUrl)
|
|
||||||
.orElseThrow(() -> new ParsingException("Could not get avatar URL"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return channelHeader.map(header -> {
|
return channelHeader.map(header -> {
|
||||||
@ -210,56 +210,37 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
|
|||||||
.getObject("image")
|
.getObject("image")
|
||||||
.getObject("contentPreviewImageViewModel")
|
.getObject("contentPreviewImageViewModel")
|
||||||
.getObject("image")
|
.getObject("image")
|
||||||
.getArray("sources")
|
.getArray("sources");
|
||||||
.getObject(0)
|
|
||||||
.getString("url");
|
|
||||||
|
|
||||||
case INTERACTIVE_TABBED:
|
case INTERACTIVE_TABBED:
|
||||||
return header.json.getObject("boxArt")
|
return header.json.getObject("boxArt")
|
||||||
.getArray("thumbnails")
|
.getArray("thumbnails");
|
||||||
.getObject(0)
|
|
||||||
.getString("url");
|
|
||||||
|
|
||||||
case C4_TABBED:
|
case C4_TABBED:
|
||||||
case CAROUSEL:
|
case CAROUSEL:
|
||||||
default:
|
default:
|
||||||
return header.json.getObject("avatar")
|
return header.json.getObject("avatar")
|
||||||
.getArray("thumbnails")
|
.getArray("thumbnails");
|
||||||
.getObject(0)
|
|
||||||
.getString("url");
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(YoutubeParsingHelper::fixThumbnailUrl)
|
.map(YoutubeParsingHelper::getImagesFromThumbnailsArray)
|
||||||
.orElseThrow(() -> new ParsingException("Could not get avatar URL"));
|
.orElseThrow(() -> new ParsingException("Could not get avatars"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getBannerUrl() throws ParsingException {
|
public List<Image> getBanners() {
|
||||||
assertPageFetched();
|
assertPageFetched();
|
||||||
if (channelAgeGateRenderer != null) {
|
if (channelAgeGateRenderer != null) {
|
||||||
return null;
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelHeader.isPresent()) {
|
|
||||||
final ChannelHeader header = channelHeader.get();
|
|
||||||
if (header.headerType == HeaderType.PAGE) {
|
|
||||||
// No banner is available on pageHeaderRenderer headers
|
// No banner is available on pageHeaderRenderer headers
|
||||||
return null;
|
return channelHeader.filter(header -> header.headerType != HeaderType.PAGE)
|
||||||
}
|
.map(header -> header.json.getObject("banner")
|
||||||
|
.getArray("thumbnails"))
|
||||||
return Optional.ofNullable(header.json.getObject("banner")
|
.map(YoutubeParsingHelper::getImagesFromThumbnailsArray)
|
||||||
.getArray("thumbnails")
|
.orElse(List.of());
|
||||||
.getObject(0)
|
|
||||||
.getString("url"))
|
|
||||||
.filter(url -> !url.contains("s.ytimg.com") && !url.contains("default_banner"))
|
|
||||||
.map(YoutubeParsingHelper::fixThumbnailUrl)
|
|
||||||
// Channels may not have a banner, so no exception should be thrown if no
|
|
||||||
// banner is found
|
|
||||||
// Return null in this case
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -359,9 +340,10 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getParentChannelAvatarUrl() {
|
public List<Image> getParentChannelAvatars() {
|
||||||
return "";
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,7 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Created by Christian Schabesberger on 12.02.17.
|
||||||
|
*
|
||||||
|
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||||
|
* YoutubeChannelInfoItemExtractor.java is part of NewPipe Extractor.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
@ -9,28 +30,11 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
|||||||
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeChannelLinkHandlerFactory;
|
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeChannelLinkHandlerFactory;
|
||||||
import org.schabi.newpipe.extractor.utils.Utils;
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.fixThumbnailUrl;
|
import javax.annotation.Nonnull;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
import java.util.List;
|
||||||
|
|
||||||
/*
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
* Created by Christian Schabesberger on 12.02.17.
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailsFromInfoItem;
|
||||||
*
|
|
||||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
|
||||||
* YoutubeChannelInfoItemExtractor.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class YoutubeChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
public class YoutubeChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
||||||
private final JsonObject channelInfoItem;
|
private final JsonObject channelInfoItem;
|
||||||
@ -53,15 +57,13 @@ public class YoutubeChannelInfoItemExtractor implements ChannelInfoItemExtractor
|
|||||||
this.withHandle = wHandle;
|
this.withHandle = wHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
try {
|
try {
|
||||||
final String url = channelInfoItem.getObject("thumbnail").getArray("thumbnails")
|
return getThumbnailsFromInfoItem(channelInfoItem);
|
||||||
.getObject(0).getString("url");
|
|
||||||
|
|
||||||
return fixThumbnailUrl(url);
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new ParsingException("Could not get thumbnail url", e);
|
throw new ParsingException("Could not get thumbnails", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonArray;
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItemExtractor;
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
@ -11,9 +12,12 @@ import org.schabi.newpipe.extractor.stream.Description;
|
|||||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||||
import org.schabi.newpipe.extractor.utils.Utils;
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.comments.CommentsInfoItem.UNKNOWN_REPLY_COUNT;
|
import static org.schabi.newpipe.extractor.comments.CommentsInfoItem.UNKNOWN_REPLY_COUNT;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
|
|
||||||
public class YoutubeCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
public class YoutubeCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
||||||
@ -42,20 +46,25 @@ public class YoutubeCommentsInfoItemExtractor implements CommentsInfoItemExtract
|
|||||||
return commentRenderer;
|
return commentRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private List<Image> getAuthorThumbnails() throws ParsingException {
|
||||||
|
try {
|
||||||
|
return getImagesFromThumbnailsArray(JsonUtils.getArray(getCommentRenderer(),
|
||||||
|
"authorThumbnail.thumbnails"));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new ParsingException("Could not get author thumbnails", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUrl() throws ParsingException {
|
public String getUrl() throws ParsingException {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
try {
|
return getAuthorThumbnails();
|
||||||
final JsonArray arr = JsonUtils.getArray(getCommentRenderer(),
|
|
||||||
"authorThumbnail.thumbnails");
|
|
||||||
return JsonUtils.getString(arr.getObject(2), "url");
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new ParsingException("Could not get thumbnail url", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -204,15 +213,10 @@ public class YoutubeCommentsInfoItemExtractor implements CommentsInfoItemExtract
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() throws ParsingException {
|
public List<Image> getUploaderAvatars() throws ParsingException {
|
||||||
try {
|
return getAuthorThumbnails();
|
||||||
final JsonArray arr = JsonUtils.getArray(getCommentRenderer(),
|
|
||||||
"authorThumbnail.thumbnails");
|
|
||||||
return JsonUtils.getString(arr.getObject(2), "url");
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new ParsingException("Could not get author thumbnail", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -228,6 +232,7 @@ public class YoutubeCommentsInfoItemExtractor implements CommentsInfoItemExtract
|
|||||||
return getCommentRenderer().has("pinnedCommentBadge");
|
return getCommentRenderer().has("pinnedCommentBadge");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isUploaderVerified() throws ParsingException {
|
public boolean isUploaderVerified() throws ParsingException {
|
||||||
return getCommentRenderer().has("authorCommentBadge");
|
return getCommentRenderer().has("authorCommentBadge");
|
||||||
}
|
}
|
||||||
@ -261,7 +266,7 @@ public class YoutubeCommentsInfoItemExtractor implements CommentsInfoItemExtract
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page getReplies() throws ParsingException {
|
public Page getReplies() {
|
||||||
try {
|
try {
|
||||||
final String id = JsonUtils.getString(
|
final String id = JsonUtils.getString(
|
||||||
JsonUtils.getArray(json, "replies.commentRepliesRenderer.contents")
|
JsonUtils.getArray(json, "replies.commentRepliesRenderer.contents")
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
||||||
|
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class YoutubeFeedInfoItemExtractor implements StreamInfoItemExtractor {
|
public class YoutubeFeedInfoItemExtractor implements StreamInfoItemExtractor {
|
||||||
private final Element entryElement;
|
private final Element entryElement;
|
||||||
@ -51,12 +55,6 @@ public class YoutubeFeedInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
return entryElement.select("author > uri").first().text();
|
return entryElement.select("author > uri").first().text();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getUploaderAvatarUrl() throws ParsingException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUploaderVerified() throws ParsingException {
|
public boolean isUploaderVerified() throws ParsingException {
|
||||||
return false;
|
return false;
|
||||||
@ -89,12 +87,51 @@ public class YoutubeFeedInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
return entryElement.getElementsByTag("link").first().attr("href");
|
return entryElement.getElementsByTag("link").first().attr("href");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() {
|
public List<Image> getThumbnails() {
|
||||||
|
final Element thumbnailElement = entryElement.getElementsByTag("media:thumbnail").first();
|
||||||
|
if (thumbnailElement == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String feedThumbnailUrl = thumbnailElement.attr("url");
|
||||||
|
|
||||||
|
// If the thumbnail URL is empty, it means that no thumbnail is available, return an empty
|
||||||
|
// list in this case
|
||||||
|
if (feedThumbnailUrl.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
// The hqdefault thumbnail has some black bars at the top and at the bottom, while the
|
// The hqdefault thumbnail has some black bars at the top and at the bottom, while the
|
||||||
// mqdefault doesn't, so return the mqdefault one. It should always exist, according to
|
// mqdefault doesn't, so return the mqdefault one. It should always exist, according to
|
||||||
// https://stackoverflow.com/a/20542029/9481500.
|
// https://stackoverflow.com/a/20542029/9481500.
|
||||||
return entryElement.getElementsByTag("media:thumbnail").first().attr("url")
|
final String newFeedThumbnailUrl = feedThumbnailUrl.replace("hqdefault", "mqdefault");
|
||||||
.replace("hqdefault", "mqdefault");
|
|
||||||
|
int height;
|
||||||
|
int width;
|
||||||
|
|
||||||
|
// If the new thumbnail URL is equal to the feed one, it means that a different image
|
||||||
|
// resolution is used on feeds, so use the height and width provided instead of the
|
||||||
|
// mqdefault ones
|
||||||
|
if (newFeedThumbnailUrl.equals(feedThumbnailUrl)) {
|
||||||
|
try {
|
||||||
|
height = Integer.parseInt(thumbnailElement.attr("height"));
|
||||||
|
} catch (final NumberFormatException e) {
|
||||||
|
height = Image.HEIGHT_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
width = Integer.parseInt(thumbnailElement.attr("width"));
|
||||||
|
} catch (final NumberFormatException e) {
|
||||||
|
width = Image.WIDTH_UNKNOWN;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
height = 320;
|
||||||
|
width = 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of(
|
||||||
|
new Image(newFeedThumbnailUrl, height, width, ResolutionLevel.fromHeight(height)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,12 @@ package org.schabi.newpipe.extractor.services.youtube.extractors;
|
|||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.extractPlaylistTypeFromPlaylistUrl;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.extractPlaylistTypeFromPlaylistUrl;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailUrlFromInfoItem;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailsFromInfoItem;
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||||
@ -14,6 +15,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
|||||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class YoutubeMixOrPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
public class YoutubeMixOrPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||||
private final JsonObject mixInfoItem;
|
private final JsonObject mixInfoItem;
|
||||||
@ -40,9 +42,10 @@ public class YoutubeMixOrPlaylistInfoItemExtractor implements PlaylistInfoItemEx
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return getThumbnailUrlFromInfoItem(mixInfoItem);
|
return getThumbnailsFromInfoItem(mixInfoItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -75,7 +78,7 @@ public class YoutubeMixOrPlaylistInfoItemExtractor implements PlaylistInfoItemEx
|
|||||||
return Integer.parseInt(countString);
|
return Integer.parseInt(countString);
|
||||||
} catch (final NumberFormatException ignored) {
|
} catch (final NumberFormatException ignored) {
|
||||||
// un-parsable integer: this is a mix with infinite items and "50+" as count string
|
// un-parsable integer: this is a mix with infinite items and "50+" as count string
|
||||||
// (though youtube music mixes do not necessarily have an infinite count of songs)
|
// (though YouTube Music mixes do not necessarily have an infinite count of songs)
|
||||||
return ListExtractor.ITEM_COUNT_INFINITE;
|
return ListExtractor.ITEM_COUNT_INFINITE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ import com.grack.nanojson.JsonBuilder;
|
|||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonWriter;
|
import com.grack.nanojson.JsonWriter;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
@ -34,6 +36,7 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
|||||||
import org.schabi.newpipe.extractor.stream.Description;
|
import org.schabi.newpipe.extractor.stream.Description;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
||||||
|
import org.schabi.newpipe.extractor.utils.ImageSuffix;
|
||||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -43,6 +46,7 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -53,6 +57,12 @@ import javax.annotation.Nullable;
|
|||||||
* {@code youtube.com/watch?v=videoId&list=playlistId}
|
* {@code youtube.com/watch?v=videoId&list=playlistId}
|
||||||
*/
|
*/
|
||||||
public class YoutubeMixPlaylistExtractor extends PlaylistExtractor {
|
public class YoutubeMixPlaylistExtractor extends PlaylistExtractor {
|
||||||
|
private static final List<ImageSuffix> IMAGE_URL_SUFFIXES_AND_RESOLUTIONS = List.of(
|
||||||
|
// sqdefault and maxresdefault image resolutions are not available on all
|
||||||
|
// videos, so don't add them in the list of available resolutions
|
||||||
|
new ImageSuffix("default.jpg", 90, 120, ResolutionLevel.LOW),
|
||||||
|
new ImageSuffix("mqdefault.jpg", 180, 320, ResolutionLevel.MEDIUM),
|
||||||
|
new ImageSuffix("hqdefault.jpg", 360, 480, ResolutionLevel.MEDIUM));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YouTube identifies mixes based on this cookie. With this information it can generate
|
* YouTube identifies mixes based on this cookie. With this information it can generate
|
||||||
@ -126,18 +136,18 @@ public class YoutubeMixPlaylistExtractor extends PlaylistExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
try {
|
try {
|
||||||
return getThumbnailUrlFromPlaylistId(playlistData.getString("playlistId"));
|
return getThumbnailsFromPlaylistId(playlistData.getString("playlistId"));
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
try {
|
try {
|
||||||
// Fallback to thumbnail of current video. Always the case for channel mix
|
// Fallback to thumbnail of current video. Always the case for channel mixes
|
||||||
return getThumbnailUrlFromVideoId(initialData.getObject("currentVideoEndpoint")
|
return getThumbnailsFromVideoId(initialData.getObject("currentVideoEndpoint")
|
||||||
.getObject("watchEndpoint").getString("videoId"));
|
.getObject("watchEndpoint").getString("videoId"));
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ParsingException("Could not get playlist thumbnail", e);
|
throw new ParsingException("Could not get playlist thumbnails", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,10 +163,11 @@ public class YoutubeMixPlaylistExtractor extends PlaylistExtractor {
|
|||||||
return "YouTube";
|
return "YouTube";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
// YouTube mixes are auto-generated by YouTube
|
// YouTube mixes are auto-generated by YouTube
|
||||||
return "";
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -264,14 +275,19 @@ public class YoutubeMixPlaylistExtractor extends PlaylistExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private String getThumbnailUrlFromPlaylistId(@Nonnull final String playlistId)
|
private List<Image> getThumbnailsFromPlaylistId(@Nonnull final String playlistId)
|
||||||
throws ParsingException {
|
throws ParsingException {
|
||||||
return getThumbnailUrlFromVideoId(YoutubeParsingHelper.extractVideoIdFromMixId(playlistId));
|
return getThumbnailsFromVideoId(YoutubeParsingHelper.extractVideoIdFromMixId(playlistId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private String getThumbnailUrlFromVideoId(final String videoId) {
|
private List<Image> getThumbnailsFromVideoId(@Nonnull final String videoId) {
|
||||||
return "https://i.ytimg.com/vi/" + videoId + "/hqdefault.jpg";
|
final String baseUrl = "https://i.ytimg.com/vi/" + videoId + "/";
|
||||||
|
return IMAGE_URL_SUFFIXES_AND_RESOLUTIONS.stream()
|
||||||
|
.map(imageSuffix -> new Image(baseUrl + imageSuffix.getSuffix(),
|
||||||
|
imageSuffix.getHeight(), imageSuffix.getWidth(),
|
||||||
|
imageSuffix.getResolutionLevel()))
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -0,0 +1,157 @@
|
|||||||
|
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
||||||
|
|
||||||
|
import com.grack.nanojson.JsonArray;
|
||||||
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||||
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.ListExtractor.ITEM_COUNT_MORE_THAN_100;
|
||||||
|
import static org.schabi.newpipe.extractor.ListExtractor.ITEM_COUNT_UNKNOWN;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_ALBUMS;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_PLAYLISTS;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
|
public class YoutubeMusicAlbumOrPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||||
|
private final JsonObject albumOrPlaylistInfoItem;
|
||||||
|
private final JsonArray descriptionElements;
|
||||||
|
private final String searchType;
|
||||||
|
|
||||||
|
public YoutubeMusicAlbumOrPlaylistInfoItemExtractor(final JsonObject albumOrPlaylistInfoItem,
|
||||||
|
final JsonArray descriptionElements,
|
||||||
|
final String searchType) {
|
||||||
|
this.albumOrPlaylistInfoItem = albumOrPlaylistInfoItem;
|
||||||
|
this.descriptionElements = descriptionElements;
|
||||||
|
this.searchType = searchType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
|
try {
|
||||||
|
return getImagesFromThumbnailsArray(
|
||||||
|
albumOrPlaylistInfoItem.getObject("thumbnail")
|
||||||
|
.getObject("musicThumbnailRenderer")
|
||||||
|
.getObject("thumbnail")
|
||||||
|
.getArray("thumbnails"));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new ParsingException("Could not get thumbnails", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() throws ParsingException {
|
||||||
|
final String name = getTextFromObject(albumOrPlaylistInfoItem.getArray("flexColumns")
|
||||||
|
.getObject(0)
|
||||||
|
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
||||||
|
.getObject("text"));
|
||||||
|
|
||||||
|
if (!isNullOrEmpty(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ParsingException("Could not get name");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl() throws ParsingException {
|
||||||
|
String playlistId = albumOrPlaylistInfoItem.getObject("menu")
|
||||||
|
.getObject("menuRenderer")
|
||||||
|
.getArray("items")
|
||||||
|
.getObject(4)
|
||||||
|
.getObject("toggleMenuServiceItemRenderer")
|
||||||
|
.getObject("toggledServiceEndpoint")
|
||||||
|
.getObject("likeEndpoint")
|
||||||
|
.getObject("target")
|
||||||
|
.getString("playlistId");
|
||||||
|
|
||||||
|
if (isNullOrEmpty(playlistId)) {
|
||||||
|
playlistId = albumOrPlaylistInfoItem.getObject("overlay")
|
||||||
|
.getObject("musicItemThumbnailOverlayRenderer")
|
||||||
|
.getObject("content")
|
||||||
|
.getObject("musicPlayButtonRenderer")
|
||||||
|
.getObject("playNavigationEndpoint")
|
||||||
|
.getObject("watchPlaylistEndpoint")
|
||||||
|
.getString("playlistId");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNullOrEmpty(playlistId)) {
|
||||||
|
return "https://music.youtube.com/playlist?list=" + playlistId;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ParsingException("Could not get URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUploaderName() throws ParsingException {
|
||||||
|
final String name;
|
||||||
|
if (searchType.equals(MUSIC_ALBUMS)) {
|
||||||
|
name = descriptionElements.getObject(2).getString("text");
|
||||||
|
} else {
|
||||||
|
name = descriptionElements.getObject(0).getString("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNullOrEmpty(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ParsingException("Could not get uploader name");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getUploaderUrl() throws ParsingException {
|
||||||
|
if (searchType.equals(MUSIC_PLAYLISTS)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final JsonArray items = albumOrPlaylistInfoItem.getObject("menu")
|
||||||
|
.getObject("menuRenderer")
|
||||||
|
.getArray("items");
|
||||||
|
for (final Object item : items) {
|
||||||
|
final JsonObject menuNavigationItemRenderer =
|
||||||
|
((JsonObject) item).getObject("menuNavigationItemRenderer");
|
||||||
|
if (menuNavigationItemRenderer.getObject("icon")
|
||||||
|
.getString("iconType", "")
|
||||||
|
.equals("ARTIST")) {
|
||||||
|
return getUrlFromNavigationEndpoint(
|
||||||
|
menuNavigationItemRenderer.getObject("navigationEndpoint"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ParsingException("Could not get uploader URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUploaderVerified() throws ParsingException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getStreamCount() throws ParsingException {
|
||||||
|
if (searchType.equals(MUSIC_ALBUMS)) {
|
||||||
|
return ITEM_COUNT_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String count = descriptionElements.getObject(2)
|
||||||
|
.getString("text");
|
||||||
|
|
||||||
|
if (!isNullOrEmpty(count)) {
|
||||||
|
if (count.contains("100+")) {
|
||||||
|
return ITEM_COUNT_MORE_THAN_100;
|
||||||
|
} else {
|
||||||
|
return Long.parseLong(Utils.removeNonDigitCharacters(count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ParsingException("Could not get stream count");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
||||||
|
|
||||||
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
import org.schabi.newpipe.extractor.utils.Parser;
|
||||||
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
|
public class YoutubeMusicArtistInfoItemExtractor implements ChannelInfoItemExtractor {
|
||||||
|
private final JsonObject artistInfoItem;
|
||||||
|
|
||||||
|
public YoutubeMusicArtistInfoItemExtractor(final JsonObject artistInfoItem) {
|
||||||
|
this.artistInfoItem = artistInfoItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
|
try {
|
||||||
|
return getImagesFromThumbnailsArray(
|
||||||
|
artistInfoItem.getObject("thumbnail")
|
||||||
|
.getObject("musicThumbnailRenderer")
|
||||||
|
.getObject("thumbnail")
|
||||||
|
.getArray("thumbnails"));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new ParsingException("Could not get thumbnails", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() throws ParsingException {
|
||||||
|
final String name = getTextFromObject(artistInfoItem.getArray("flexColumns")
|
||||||
|
.getObject(0)
|
||||||
|
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
||||||
|
.getObject("text"));
|
||||||
|
if (!isNullOrEmpty(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
throw new ParsingException("Could not get name");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl() throws ParsingException {
|
||||||
|
final String url = getUrlFromNavigationEndpoint(
|
||||||
|
artistInfoItem.getObject("navigationEndpoint"));
|
||||||
|
if (!isNullOrEmpty(url)) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
throw new ParsingException("Could not get URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSubscriberCount() throws ParsingException {
|
||||||
|
final String subscriberCount = getTextFromObject(artistInfoItem.getArray("flexColumns")
|
||||||
|
.getObject(2)
|
||||||
|
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
||||||
|
.getObject("text"));
|
||||||
|
if (!isNullOrEmpty(subscriberCount)) {
|
||||||
|
try {
|
||||||
|
return Utils.mixedNumberWordToLong(subscriberCount);
|
||||||
|
} catch (final Parser.RegexException ignored) {
|
||||||
|
// probably subscriberCount == "No subscribers" or similar
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ParsingException("Could not get subscriber count");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getStreamCount() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVerified() {
|
||||||
|
// An artist on YouTube Music is always verified
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,7 @@
|
|||||||
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.DISABLE_PRETTY_PRINT_PARAMETER;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.DISABLE_PRETTY_PRINT_PARAMETER;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.fixThumbnailUrl;
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint;
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getValidJsonResponseBody;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getValidJsonResponseBody;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getYoutubeMusicHeaders;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getYoutubeMusicHeaders;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_ALBUMS;
|
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_ALBUMS;
|
||||||
@ -29,19 +27,16 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
|
||||||
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
|
|
||||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||||
import org.schabi.newpipe.extractor.utils.Parser;
|
|
||||||
import org.schabi.newpipe.extractor.utils.Utils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -222,7 +217,7 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
|
|||||||
.object("client")
|
.object("client")
|
||||||
.value("clientName", "WEB_REMIX")
|
.value("clientName", "WEB_REMIX")
|
||||||
.value("clientVersion", youtubeMusicKeys[2])
|
.value("clientVersion", youtubeMusicKeys[2])
|
||||||
.value("hl", "en")
|
.value("hl", "en-GB")
|
||||||
.value("gl", getExtractorContentCountry().getCountryCode())
|
.value("gl", getExtractorContentCountry().getCountryCode())
|
||||||
.array("experimentIds").end()
|
.array("experimentIds").end()
|
||||||
.value("experimentsToken", "")
|
.value("experimentsToken", "")
|
||||||
@ -263,316 +258,44 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
|
|||||||
return new InfoItemsPage<>(collector, getNextPageFrom(continuations));
|
return new InfoItemsPage<>(collector, getNextPageFrom(continuations));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("MethodLength")
|
|
||||||
private void collectMusicStreamsFrom(final MultiInfoItemsCollector collector,
|
private void collectMusicStreamsFrom(final MultiInfoItemsCollector collector,
|
||||||
@Nonnull final JsonArray videos) {
|
@Nonnull final JsonArray videos) {
|
||||||
final TimeAgoParser timeAgoParser = getTimeAgoParser();
|
|
||||||
|
|
||||||
for (final Object item : videos) {
|
|
||||||
final JsonObject info = ((JsonObject) item)
|
|
||||||
.getObject("musicResponsiveListItemRenderer", null);
|
|
||||||
if (info != null) {
|
|
||||||
final String displayPolicy = info.getString("musicItemRendererDisplayPolicy",
|
|
||||||
"");
|
|
||||||
if (displayPolicy.equals("MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT")) {
|
|
||||||
continue; // No info about video URL available
|
|
||||||
}
|
|
||||||
|
|
||||||
final JsonObject flexColumnRenderer = info.getArray("flexColumns")
|
|
||||||
.getObject(1)
|
|
||||||
.getObject("musicResponsiveListItemFlexColumnRenderer");
|
|
||||||
final JsonArray descriptionElements = flexColumnRenderer.getObject("text")
|
|
||||||
.getArray("runs");
|
|
||||||
final String searchType = getLinkHandler().getContentFilters().get(0);
|
final String searchType = getLinkHandler().getContentFilters().get(0);
|
||||||
if (searchType.equals(MUSIC_SONGS) || searchType.equals(MUSIC_VIDEOS)) {
|
videos.stream()
|
||||||
collector.commit(new YoutubeStreamInfoItemExtractor(info, timeAgoParser) {
|
.filter(JsonObject.class::isInstance)
|
||||||
@Override
|
.map(JsonObject.class::cast)
|
||||||
public String getUrl() throws ParsingException {
|
.map(item -> item.getObject("musicResponsiveListItemRenderer", null))
|
||||||
final String id = info.getObject("playlistItemData")
|
.filter(Objects::nonNull)
|
||||||
.getString("videoId");
|
.forEachOrdered(infoItem -> {
|
||||||
if (!isNullOrEmpty(id)) {
|
final String displayPolicy = infoItem.getString(
|
||||||
return "https://music.youtube.com/watch?v=" + id;
|
"musicItemRendererDisplayPolicy", "");
|
||||||
}
|
if (displayPolicy.equals("MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT")) {
|
||||||
throw new ParsingException("Could not get url");
|
// No info about URL available
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
final JsonArray descriptionElements = infoItem.getArray("flexColumns")
|
||||||
public String getName() throws ParsingException {
|
|
||||||
final String name = getTextFromObject(info.getArray("flexColumns")
|
|
||||||
.getObject(0)
|
|
||||||
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
|
||||||
.getObject("text"));
|
|
||||||
if (!isNullOrEmpty(name)) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get name");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDuration() throws ParsingException {
|
|
||||||
final String duration = descriptionElements
|
|
||||||
.getObject(descriptionElements.size() - 1)
|
|
||||||
.getString("text");
|
|
||||||
if (!isNullOrEmpty(duration)) {
|
|
||||||
return YoutubeParsingHelper.parseDurationString(duration);
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get duration");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUploaderName() throws ParsingException {
|
|
||||||
final String name = descriptionElements.getObject(0).getString("text");
|
|
||||||
if (!isNullOrEmpty(name)) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get uploader name");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUploaderUrl() throws ParsingException {
|
|
||||||
if (searchType.equals(MUSIC_VIDEOS)) {
|
|
||||||
final JsonArray items = info.getObject("menu")
|
|
||||||
.getObject("menuRenderer")
|
|
||||||
.getArray("items");
|
|
||||||
for (final Object item : items) {
|
|
||||||
final JsonObject menuNavigationItemRenderer =
|
|
||||||
((JsonObject) item).getObject(
|
|
||||||
"menuNavigationItemRenderer");
|
|
||||||
if (menuNavigationItemRenderer.getObject("icon")
|
|
||||||
.getString("iconType", "")
|
|
||||||
.equals("ARTIST")) {
|
|
||||||
return getUrlFromNavigationEndpoint(
|
|
||||||
menuNavigationItemRenderer
|
|
||||||
.getObject("navigationEndpoint"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
final JsonObject navigationEndpointHolder = info
|
|
||||||
.getArray("flexColumns")
|
|
||||||
.getObject(1)
|
.getObject(1)
|
||||||
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
||||||
.getObject("text").getArray("runs").getObject(0);
|
.getObject("text")
|
||||||
|
.getArray("runs");
|
||||||
|
|
||||||
if (!navigationEndpointHolder.has("navigationEndpoint")) {
|
switch (searchType) {
|
||||||
return null;
|
case MUSIC_SONGS:
|
||||||
}
|
case MUSIC_VIDEOS:
|
||||||
|
collector.commit(new YoutubeMusicSongOrVideoInfoItemExtractor(
|
||||||
final String url = getUrlFromNavigationEndpoint(
|
infoItem, descriptionElements, searchType));
|
||||||
navigationEndpointHolder.getObject("navigationEndpoint"));
|
break;
|
||||||
|
case MUSIC_ARTISTS:
|
||||||
if (!isNullOrEmpty(url)) {
|
collector.commit(new YoutubeMusicArtistInfoItemExtractor(infoItem));
|
||||||
return url;
|
break;
|
||||||
}
|
case MUSIC_ALBUMS:
|
||||||
|
case MUSIC_PLAYLISTS:
|
||||||
throw new ParsingException("Could not get uploader URL");
|
collector.commit(new YoutubeMusicAlbumOrPlaylistInfoItemExtractor(
|
||||||
}
|
infoItem, descriptionElements, searchType));
|
||||||
}
|
break;
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTextualUploadDate() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DateWrapper getUploadDate() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getViewCount() throws ParsingException {
|
|
||||||
if (searchType.equals(MUSIC_SONGS)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
final String viewCount = descriptionElements
|
|
||||||
.getObject(descriptionElements.size() - 3)
|
|
||||||
.getString("text");
|
|
||||||
if (!isNullOrEmpty(viewCount)) {
|
|
||||||
try {
|
|
||||||
return Utils.mixedNumberWordToLong(viewCount);
|
|
||||||
} catch (final Parser.RegexException e) {
|
|
||||||
// probably viewCount == "No views" or similar
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get view count");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
|
||||||
try {
|
|
||||||
final JsonArray thumbnails = info.getObject("thumbnail")
|
|
||||||
.getObject("musicThumbnailRenderer")
|
|
||||||
.getObject("thumbnail").getArray("thumbnails");
|
|
||||||
// the last thumbnail is the one with the highest resolution
|
|
||||||
final String url = thumbnails.getObject(thumbnails.size() - 1)
|
|
||||||
.getString("url");
|
|
||||||
|
|
||||||
return fixThumbnailUrl(url);
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new ParsingException("Could not get thumbnail url", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (searchType.equals(MUSIC_ARTISTS)) {
|
|
||||||
collector.commit(new YoutubeChannelInfoItemExtractor(info) {
|
|
||||||
@Override
|
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
|
||||||
try {
|
|
||||||
final JsonArray thumbnails = info.getObject("thumbnail")
|
|
||||||
.getObject("musicThumbnailRenderer")
|
|
||||||
.getObject("thumbnail").getArray("thumbnails");
|
|
||||||
// the last thumbnail is the one with the highest resolution
|
|
||||||
final String url = thumbnails.getObject(thumbnails.size() - 1)
|
|
||||||
.getString("url");
|
|
||||||
|
|
||||||
return fixThumbnailUrl(url);
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new ParsingException("Could not get thumbnail url", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() throws ParsingException {
|
|
||||||
final String name = getTextFromObject(info.getArray("flexColumns")
|
|
||||||
.getObject(0)
|
|
||||||
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
|
||||||
.getObject("text"));
|
|
||||||
if (!isNullOrEmpty(name)) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get name");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUrl() throws ParsingException {
|
|
||||||
final String url = getUrlFromNavigationEndpoint(info
|
|
||||||
.getObject("navigationEndpoint"));
|
|
||||||
if (!isNullOrEmpty(url)) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get url");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getSubscriberCount() throws ParsingException {
|
|
||||||
final String subscriberCount = getTextFromObject(info
|
|
||||||
.getArray("flexColumns").getObject(2)
|
|
||||||
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
|
||||||
.getObject("text"));
|
|
||||||
if (!isNullOrEmpty(subscriberCount)) {
|
|
||||||
try {
|
|
||||||
return Utils.mixedNumberWordToLong(subscriberCount);
|
|
||||||
} catch (final Parser.RegexException ignored) {
|
|
||||||
// probably subscriberCount == "No subscribers" or similar
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get subscriber count");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStreamCount() {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDescription() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (searchType.equals(MUSIC_ALBUMS) || searchType.equals(MUSIC_PLAYLISTS)) {
|
|
||||||
collector.commit(new YoutubePlaylistInfoItemExtractor(info) {
|
|
||||||
@Override
|
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
|
||||||
try {
|
|
||||||
final JsonArray thumbnails = info.getObject("thumbnail")
|
|
||||||
.getObject("musicThumbnailRenderer")
|
|
||||||
.getObject("thumbnail").getArray("thumbnails");
|
|
||||||
// the last thumbnail is the one with the highest resolution
|
|
||||||
final String url = thumbnails.getObject(thumbnails.size() - 1)
|
|
||||||
.getString("url");
|
|
||||||
|
|
||||||
return fixThumbnailUrl(url);
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new ParsingException("Could not get thumbnail url", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() throws ParsingException {
|
|
||||||
final String name = getTextFromObject(info.getArray("flexColumns")
|
|
||||||
.getObject(0)
|
|
||||||
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
|
||||||
.getObject("text"));
|
|
||||||
if (!isNullOrEmpty(name)) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get name");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUrl() throws ParsingException {
|
|
||||||
String playlistId = info.getObject("menu")
|
|
||||||
.getObject("menuRenderer")
|
|
||||||
.getArray("items")
|
|
||||||
.getObject(4)
|
|
||||||
.getObject("toggleMenuServiceItemRenderer")
|
|
||||||
.getObject("toggledServiceEndpoint")
|
|
||||||
.getObject("likeEndpoint")
|
|
||||||
.getObject("target")
|
|
||||||
.getString("playlistId");
|
|
||||||
|
|
||||||
if (isNullOrEmpty(playlistId)) {
|
|
||||||
playlistId = info.getObject("overlay")
|
|
||||||
.getObject("musicItemThumbnailOverlayRenderer")
|
|
||||||
.getObject("content")
|
|
||||||
.getObject("musicPlayButtonRenderer")
|
|
||||||
.getObject("playNavigationEndpoint")
|
|
||||||
.getObject("watchPlaylistEndpoint")
|
|
||||||
.getString("playlistId");
|
|
||||||
}
|
|
||||||
if (!isNullOrEmpty(playlistId)) {
|
|
||||||
return "https://music.youtube.com/playlist?list=" + playlistId;
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get url");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUploaderName() throws ParsingException {
|
|
||||||
final String name;
|
|
||||||
if (searchType.equals(MUSIC_ALBUMS)) {
|
|
||||||
name = descriptionElements.getObject(2).getString("text");
|
|
||||||
} else {
|
|
||||||
name = descriptionElements.getObject(0).getString("text");
|
|
||||||
}
|
|
||||||
if (!isNullOrEmpty(name)) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get uploader name");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStreamCount() throws ParsingException {
|
|
||||||
if (searchType.equals(MUSIC_ALBUMS)) {
|
|
||||||
return ITEM_COUNT_UNKNOWN;
|
|
||||||
}
|
|
||||||
final String count = descriptionElements.getObject(2)
|
|
||||||
.getString("text");
|
|
||||||
if (!isNullOrEmpty(count)) {
|
|
||||||
if (count.contains("100+")) {
|
|
||||||
return ITEM_COUNT_MORE_THAN_100;
|
|
||||||
} else {
|
|
||||||
return Long.parseLong(Utils.removeNonDigitCharacters(count));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new ParsingException("Could not get count");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -0,0 +1,177 @@
|
|||||||
|
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
||||||
|
|
||||||
|
import com.grack.nanojson.JsonArray;
|
||||||
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
|
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
import org.schabi.newpipe.extractor.utils.Parser;
|
||||||
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_SONGS;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_VIDEOS;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
|
public class YoutubeMusicSongOrVideoInfoItemExtractor implements StreamInfoItemExtractor {
|
||||||
|
private final JsonObject songOrVideoInfoItem;
|
||||||
|
private final JsonArray descriptionElements;
|
||||||
|
private final String searchType;
|
||||||
|
|
||||||
|
public YoutubeMusicSongOrVideoInfoItemExtractor(final JsonObject songOrVideoInfoItem,
|
||||||
|
final JsonArray descriptionElements,
|
||||||
|
final String searchType) {
|
||||||
|
this.songOrVideoInfoItem = songOrVideoInfoItem;
|
||||||
|
this.descriptionElements = descriptionElements;
|
||||||
|
this.searchType = searchType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl() throws ParsingException {
|
||||||
|
final String id = songOrVideoInfoItem.getObject("playlistItemData").getString("videoId");
|
||||||
|
if (!isNullOrEmpty(id)) {
|
||||||
|
return "https://music.youtube.com/watch?v=" + id;
|
||||||
|
}
|
||||||
|
throw new ParsingException("Could not get URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() throws ParsingException {
|
||||||
|
final String name = getTextFromObject(songOrVideoInfoItem.getArray("flexColumns")
|
||||||
|
.getObject(0)
|
||||||
|
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
||||||
|
.getObject("text"));
|
||||||
|
if (!isNullOrEmpty(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
throw new ParsingException("Could not get name");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamType getStreamType() {
|
||||||
|
return StreamType.VIDEO_STREAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAd() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDuration() throws ParsingException {
|
||||||
|
final String duration = descriptionElements.getObject(descriptionElements.size() - 1)
|
||||||
|
.getString("text");
|
||||||
|
if (!isNullOrEmpty(duration)) {
|
||||||
|
return YoutubeParsingHelper.parseDurationString(duration);
|
||||||
|
}
|
||||||
|
throw new ParsingException("Could not get duration");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUploaderName() throws ParsingException {
|
||||||
|
final String name = descriptionElements.getObject(0).getString("text");
|
||||||
|
if (!isNullOrEmpty(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
throw new ParsingException("Could not get uploader name");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUploaderUrl() throws ParsingException {
|
||||||
|
if (searchType.equals(MUSIC_VIDEOS)) {
|
||||||
|
final JsonArray items = songOrVideoInfoItem.getObject("menu")
|
||||||
|
.getObject("menuRenderer")
|
||||||
|
.getArray("items");
|
||||||
|
for (final Object item : items) {
|
||||||
|
final JsonObject menuNavigationItemRenderer =
|
||||||
|
((JsonObject) item).getObject("menuNavigationItemRenderer");
|
||||||
|
if (menuNavigationItemRenderer.getObject("icon")
|
||||||
|
.getString("iconType", "")
|
||||||
|
.equals("ARTIST")) {
|
||||||
|
return getUrlFromNavigationEndpoint(
|
||||||
|
menuNavigationItemRenderer.getObject("navigationEndpoint"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
final JsonObject navigationEndpointHolder = songOrVideoInfoItem.getArray("flexColumns")
|
||||||
|
.getObject(1)
|
||||||
|
.getObject("musicResponsiveListItemFlexColumnRenderer")
|
||||||
|
.getObject("text")
|
||||||
|
.getArray("runs")
|
||||||
|
.getObject(0);
|
||||||
|
|
||||||
|
if (!navigationEndpointHolder.has("navigationEndpoint")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String url = getUrlFromNavigationEndpoint(
|
||||||
|
navigationEndpointHolder.getObject("navigationEndpoint"));
|
||||||
|
|
||||||
|
if (!isNullOrEmpty(url)) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ParsingException("Could not get uploader URL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUploaderVerified() {
|
||||||
|
// We don't have the ability to know this information on YouTube Music
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTextualUploadDate() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DateWrapper getUploadDate() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getViewCount() throws ParsingException {
|
||||||
|
if (searchType.equals(MUSIC_SONGS)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
final String viewCount = descriptionElements
|
||||||
|
.getObject(descriptionElements.size() - 3)
|
||||||
|
.getString("text");
|
||||||
|
if (!isNullOrEmpty(viewCount)) {
|
||||||
|
try {
|
||||||
|
return Utils.mixedNumberWordToLong(viewCount);
|
||||||
|
} catch (final Parser.RegexException e) {
|
||||||
|
// probably viewCount == "No views" or similar
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ParsingException("Could not get view count");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
|
try {
|
||||||
|
return getImagesFromThumbnailsArray(
|
||||||
|
songOrVideoInfoItem.getObject("thumbnail")
|
||||||
|
.getObject("musicThumbnailRenderer")
|
||||||
|
.getObject("thumbnail")
|
||||||
|
.getArray("thumbnails"));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new ParsingException("Could not get thumbnails", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,10 +3,10 @@ package org.schabi.newpipe.extractor.services.youtube.extractors;
|
|||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.DISABLE_PRETTY_PRINT_PARAMETER;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.DISABLE_PRETTY_PRINT_PARAMETER;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.YOUTUBEI_V1_URL;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.YOUTUBEI_V1_URL;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.extractPlaylistTypeFromPlaylistUrl;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.extractPlaylistTypeFromPlaylistUrl;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.fixThumbnailUrl;
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonPostResponse;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonPostResponse;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getKey;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getKey;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareDesktopJsonBuilder;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareDesktopJsonBuilder;
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
@ -15,6 +15,7 @@ import com.grack.nanojson.JsonArray;
|
|||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonWriter;
|
import com.grack.nanojson.JsonWriter;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
@ -33,6 +34,7 @@ import org.schabi.newpipe.extractor.utils.Utils;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -160,39 +162,35 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
String url;
|
final JsonArray playlistMetadataThumbnailsArray;
|
||||||
if (isNewPlaylistInterface) {
|
if (isNewPlaylistInterface) {
|
||||||
url = getPlaylistHeader().getObject("playlistHeaderBanner")
|
playlistMetadataThumbnailsArray = getPlaylistHeader().getObject("playlistHeaderBanner")
|
||||||
.getObject("heroPlaylistThumbnailRenderer")
|
.getObject("heroPlaylistThumbnailRenderer")
|
||||||
.getObject("thumbnail")
|
.getObject("thumbnail")
|
||||||
.getArray("thumbnails")
|
.getArray("thumbnails");
|
||||||
.getObject(0)
|
|
||||||
.getString("url");
|
|
||||||
} else {
|
} else {
|
||||||
url = getPlaylistInfo().getObject("thumbnailRenderer")
|
playlistMetadataThumbnailsArray = playlistInfo.getObject("thumbnailRenderer")
|
||||||
.getObject("playlistVideoThumbnailRenderer")
|
.getObject("playlistVideoThumbnailRenderer")
|
||||||
.getObject("thumbnail")
|
.getObject("thumbnail")
|
||||||
.getArray("thumbnails")
|
.getArray("thumbnails");
|
||||||
.getObject(0)
|
}
|
||||||
.getString("url");
|
|
||||||
|
if (!isNullOrEmpty(playlistMetadataThumbnailsArray)) {
|
||||||
|
return getImagesFromThumbnailsArray(playlistMetadataThumbnailsArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This data structure is returned in both layouts
|
// This data structure is returned in both layouts
|
||||||
if (isNullOrEmpty(url)) {
|
final JsonArray microFormatThumbnailsArray = browseResponse.getObject("microformat")
|
||||||
url = browseResponse.getObject("microformat")
|
|
||||||
.getObject("microformatDataRenderer")
|
.getObject("microformatDataRenderer")
|
||||||
.getObject("thumbnail")
|
.getObject("thumbnail")
|
||||||
.getArray("thumbnails")
|
.getArray("thumbnails");
|
||||||
.getObject(0)
|
|
||||||
.getString("url");
|
|
||||||
|
|
||||||
if (isNullOrEmpty(url)) {
|
if (!isNullOrEmpty(microFormatThumbnailsArray)) {
|
||||||
throw new ParsingException("Could not get playlist thumbnail");
|
return getImagesFromThumbnailsArray(microFormatThumbnailsArray);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fixThumbnailUrl(url);
|
throw new ParsingException("Could not get playlist thumbnails");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -220,23 +218,19 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() throws ParsingException {
|
public List<Image> getUploaderAvatars() throws ParsingException {
|
||||||
if (isNewPlaylistInterface) {
|
if (isNewPlaylistInterface) {
|
||||||
// The new playlist interface doesn't provide an uploader avatar
|
// The new playlist interface doesn't provide an uploader avatar
|
||||||
return "";
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final String url = getUploaderInfo()
|
return getImagesFromThumbnailsArray(getUploaderInfo().getObject("thumbnail")
|
||||||
.getObject("thumbnail")
|
.getArray("thumbnails"));
|
||||||
.getArray("thumbnails")
|
|
||||||
.getObject(0)
|
|
||||||
.getString("url");
|
|
||||||
|
|
||||||
return fixThumbnailUrl(url);
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new ParsingException("Could not get playlist uploader avatar", e);
|
throw new ParsingException("Could not get playlist uploader avatars", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,17 +2,21 @@ package org.schabi.newpipe.extractor.services.youtube.extractors;
|
|||||||
|
|
||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubePlaylistLinkHandlerFactory;
|
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubePlaylistLinkHandlerFactory;
|
||||||
import org.schabi.newpipe.extractor.utils.Utils;
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.fixThumbnailUrl;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromObject;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromObject;
|
||||||
|
|
||||||
|
|
||||||
public class YoutubePlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
public class YoutubePlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||||
private final JsonObject playlistInfoItem;
|
private final JsonObject playlistInfoItem;
|
||||||
|
|
||||||
@ -20,8 +24,9 @@ public class YoutubePlaylistInfoItemExtractor implements PlaylistInfoItemExtract
|
|||||||
this.playlistInfoItem = playlistInfoItem;
|
this.playlistInfoItem = playlistInfoItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
try {
|
try {
|
||||||
JsonArray thumbnails = playlistInfoItem.getArray("thumbnails")
|
JsonArray thumbnails = playlistInfoItem.getArray("thumbnails")
|
||||||
.getObject(0)
|
.getObject(0)
|
||||||
@ -31,9 +36,9 @@ public class YoutubePlaylistInfoItemExtractor implements PlaylistInfoItemExtract
|
|||||||
.getArray("thumbnails");
|
.getArray("thumbnails");
|
||||||
}
|
}
|
||||||
|
|
||||||
return fixThumbnailUrl(thumbnails.getObject(0).getString("url"));
|
return getImagesFromThumbnailsArray(thumbnails);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new ParsingException("Could not get thumbnail url", e);
|
throw new ParsingException("Could not get thumbnails", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
|
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
|
||||||
@ -13,9 +15,11 @@ import javax.annotation.Nonnull;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailUrlFromInfoItem;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailsFromInfoItem;
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link StreamInfoItemExtractor} for YouTube's {@code reelItemRenderers}.
|
* A {@link StreamInfoItemExtractor} for YouTube's {@code reelItemRenderers}.
|
||||||
*
|
*
|
||||||
@ -53,9 +57,10 @@ public class YoutubeReelInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return getThumbnailUrlFromInfoItem(reelInfo);
|
return getThumbnailsFromInfoItem(reelInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -101,7 +106,7 @@ public class YoutubeReelInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isShortFormContent() throws ParsingException {
|
public boolean isShortFormContent() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,12 +127,6 @@ public class YoutubeReelInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getUploaderAvatarUrl() throws ParsingException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUploaderVerified() throws ParsingException {
|
public boolean isUploaderVerified() throws ParsingException {
|
||||||
return false;
|
return false;
|
||||||
|
@ -30,11 +30,12 @@ import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
|
|||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.fixThumbnailUrl;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.fixThumbnailUrl;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.generateContentPlaybackNonce;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.generateContentPlaybackNonce;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.generateTParameter;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.generateTParameter;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getAttributedDescription;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonAndroidPostResponse;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonAndroidPostResponse;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonIosPostResponse;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonIosPostResponse;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonPostResponse;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonPostResponse;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getAttributedDescription;
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareAndroidMobileJsonBuilder;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareAndroidMobileJsonBuilder;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareDesktopJsonBuilder;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareDesktopJsonBuilder;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareIosMobileJsonBuilder;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareIosMobileJsonBuilder;
|
||||||
@ -47,6 +48,7 @@ import com.grack.nanojson.JsonWriter;
|
|||||||
import org.mozilla.javascript.Context;
|
import org.mozilla.javascript.Context;
|
||||||
import org.mozilla.javascript.Function;
|
import org.mozilla.javascript.Function;
|
||||||
import org.mozilla.javascript.ScriptableObject;
|
import org.mozilla.javascript.ScriptableObject;
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.MetaInfo;
|
import org.schabi.newpipe.extractor.MetaInfo;
|
||||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||||
@ -258,23 +260,15 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
assertPageFetched();
|
assertPageFetched();
|
||||||
try {
|
try {
|
||||||
final JsonArray thumbnails = playerResponse
|
return getImagesFromThumbnailsArray(playerResponse.getObject("videoDetails")
|
||||||
.getObject("videoDetails")
|
|
||||||
.getObject("thumbnail")
|
.getObject("thumbnail")
|
||||||
.getArray("thumbnails");
|
.getArray("thumbnails"));
|
||||||
// the last thumbnail is the one with the highest resolution
|
|
||||||
final String url = thumbnails
|
|
||||||
.getObject(thumbnails.size() - 1)
|
|
||||||
.getString("url");
|
|
||||||
|
|
||||||
return fixThumbnailUrl(url);
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new ParsingException("Could not get thumbnail url");
|
throw new ParsingException("Could not get thumbnails");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -552,26 +546,20 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() throws ParsingException {
|
public List<Image> getUploaderAvatars() throws ParsingException {
|
||||||
assertPageFetched();
|
assertPageFetched();
|
||||||
|
|
||||||
final String url = getVideoSecondaryInfoRenderer()
|
final List<Image> imageList = getImagesFromThumbnailsArray(
|
||||||
.getObject("owner")
|
getVideoSecondaryInfoRenderer().getObject("owner")
|
||||||
.getObject("videoOwnerRenderer")
|
.getObject("videoOwnerRenderer")
|
||||||
.getObject("thumbnail")
|
.getObject("thumbnail")
|
||||||
.getArray("thumbnails")
|
.getArray("thumbnails"));
|
||||||
.getObject(0)
|
|
||||||
.getString("url");
|
|
||||||
|
|
||||||
if (isNullOrEmpty(url)) {
|
if (imageList.isEmpty() && ageLimit == NO_AGE_LIMIT) {
|
||||||
if (ageLimit == NO_AGE_LIMIT) {
|
throw new ParsingException("Could not get uploader avatars");
|
||||||
throw new ParsingException("Could not get uploader avatar URL");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return imageList;
|
||||||
}
|
|
||||||
|
|
||||||
return fixThumbnailUrl(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,7 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
|
* YoutubeStreamInfoItemExtractor.java is part of NewPipe Extractor.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
package org.schabi.newpipe.extractor.services.youtube.extractors;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailsFromInfoItem;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray;
|
||||||
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint;
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonArray;
|
import com.grack.nanojson.JsonArray;
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
|
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
|
||||||
@ -15,35 +41,14 @@ import org.schabi.newpipe.extractor.utils.Utils;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailUrlFromInfoItem;
|
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint;
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
|
||||||
* YoutubeStreamInfoItemExtractor.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
||||||
|
|
||||||
private static final Pattern ACCESSIBILITY_DATA_VIEW_COUNT_REGEX =
|
private static final Pattern ACCESSIBILITY_DATA_VIEW_COUNT_REGEX =
|
||||||
@ -215,21 +220,22 @@ public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getUploaderAvatarUrl() throws ParsingException {
|
public List<Image> getUploaderAvatars() throws ParsingException {
|
||||||
if (videoInfo.has("channelThumbnailSupportedRenderers")) {
|
if (videoInfo.has("channelThumbnailSupportedRenderers")) {
|
||||||
return JsonUtils.getArray(videoInfo, "channelThumbnailSupportedRenderers"
|
return getImagesFromThumbnailsArray(JsonUtils.getArray(videoInfo,
|
||||||
+ ".channelThumbnailWithLinkRenderer.thumbnail.thumbnails")
|
// CHECKSTYLE:OFF
|
||||||
.getObject(0).getString("url");
|
"channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail.thumbnails"));
|
||||||
|
// CHECKSTYLE:ON
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoInfo.has("channelThumbnail")) {
|
if (videoInfo.has("channelThumbnail")) {
|
||||||
return JsonUtils.getArray(videoInfo, "channelThumbnail.thumbnails")
|
return getImagesFromThumbnailsArray(
|
||||||
.getObject(0).getString("url");
|
JsonUtils.getArray(videoInfo, "channelThumbnail.thumbnails"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -371,9 +377,10 @@ public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
videoInfoTitleAccessibilityData)));
|
videoInfoTitleAccessibilityData)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getThumbnailUrl() throws ParsingException {
|
public List<Image> getThumbnails() throws ParsingException {
|
||||||
return getThumbnailUrlFromInfoItem(videoInfo);
|
return getThumbnailsFromInfoItem(videoInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPremium() {
|
private boolean isPremium() {
|
||||||
@ -409,10 +416,10 @@ public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public String getShortDescription() throws ParsingException {
|
public String getShortDescription() throws ParsingException {
|
||||||
|
|
||||||
if (videoInfo.has("detailedMetadataSnippets")) {
|
if (videoInfo.has("detailedMetadataSnippets")) {
|
||||||
return getTextFromObject(videoInfo.getArray("detailedMetadataSnippets")
|
return getTextFromObject(videoInfo.getArray("detailedMetadataSnippets")
|
||||||
.getObject(0).getObject("snippetText"));
|
.getObject(0)
|
||||||
|
.getObject("snippetText"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoInfo.has("descriptionSnippet")) {
|
if (videoInfo.has("descriptionSnippet")) {
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
package org.schabi.newpipe.extractor.stream;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by Christian Schabesberger on 10.08.18.
|
* Created by Christian Schabesberger on 10.08.18.
|
||||||
*
|
*
|
||||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
* StreamExtractor.java is part of NewPipe.
|
* StreamExtractor.java is part of NewPipe Extractor.
|
||||||
*
|
*
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package org.schabi.newpipe.extractor.stream;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
||||||
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
||||||
@ -87,13 +88,12 @@ public abstract class StreamExtractor extends Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will return the url to the thumbnail of the stream. Try to return the medium resolution
|
* This will return the thumbnails of the stream.
|
||||||
* here.
|
|
||||||
*
|
*
|
||||||
* @return The url of the thumbnail.
|
* @return the thumbnails of the stream
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public abstract String getThumbnailUrl() throws ParsingException;
|
public abstract List<Image> getThumbnails() throws ParsingException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the stream description.
|
* This is the stream description.
|
||||||
@ -208,14 +208,18 @@ public abstract class StreamExtractor extends Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The url to the image file/profile picture/avatar of the creator/uploader of the stream.
|
* The image files/profile pictures/avatars of the creator/uploader of the stream.
|
||||||
* If the url is not available you can return an empty String.
|
|
||||||
*
|
*
|
||||||
* @return The url of the image file of the uploader or an empty String
|
* <p>
|
||||||
|
* If they are not available in the stream on specific cases, you must return an empty list for
|
||||||
|
* these ones, like it is made by default.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return the avatars of the sub-channel of the stream or an empty list (default)
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public String getUploaderAvatarUrl() throws ParsingException {
|
public List<Image> getUploaderAvatars() throws ParsingException {
|
||||||
return "";
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -243,14 +247,23 @@ public abstract class StreamExtractor extends Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The url to the image file/profile picture/avatar of the sub-channel of the stream.
|
* The avatars of the sub-channel of the stream.
|
||||||
* If the url is not available you can return an empty String.
|
|
||||||
*
|
*
|
||||||
* @return The url of the image file of the sub-channel or an empty String
|
* <p>
|
||||||
|
* If they are not available in the stream on specific cases, you must return an empty list for
|
||||||
|
* these ones, like it is made by default.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the concept of sub-channels doesn't apply to the stream's service, keep the default
|
||||||
|
* implementation.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return the avatars of the sub-channel of the stream or an empty list (default)
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public String getSubChannelAvatarUrl() throws ParsingException {
|
public List<Image> getSubChannelAvatars() throws ParsingException {
|
||||||
return "";
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,26 +1,3 @@
|
|||||||
package org.schabi.newpipe.extractor.stream;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.Info;
|
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
|
||||||
import org.schabi.newpipe.extractor.MetaInfo;
|
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
|
||||||
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by Christian Schabesberger on 26.08.15.
|
* Created by Christian Schabesberger on 26.08.15.
|
||||||
*
|
*
|
||||||
@ -41,6 +18,28 @@ import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|||||||
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package org.schabi.newpipe.extractor.stream;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.Info;
|
||||||
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
|
import org.schabi.newpipe.extractor.MetaInfo;
|
||||||
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
|
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Info object for opened contents, i.e. the content ready to play.
|
* Info object for opened contents, i.e. the content ready to play.
|
||||||
*/
|
*/
|
||||||
@ -106,9 +105,7 @@ public class StreamInfo extends Info {
|
|||||||
// Important data, without it the content can't be displayed.
|
// Important data, without it the content can't be displayed.
|
||||||
// If one of these is not available, the frontend will receive an exception directly.
|
// If one of these is not available, the frontend will receive an exception directly.
|
||||||
|
|
||||||
final int serviceId = extractor.getServiceId();
|
|
||||||
final String url = extractor.getUrl();
|
final String url = extractor.getUrl();
|
||||||
final String originalUrl = extractor.getOriginalUrl();
|
|
||||||
final StreamType streamType = extractor.getStreamType();
|
final StreamType streamType = extractor.getStreamType();
|
||||||
final String id = extractor.getId();
|
final String id = extractor.getId();
|
||||||
final String name = extractor.getName();
|
final String name = extractor.getName();
|
||||||
@ -148,7 +145,6 @@ public class StreamInfo extends Info {
|
|||||||
streamInfo.addError(new ExtractionException("Couldn't get HLS manifest", e));
|
streamInfo.addError(new ExtractionException("Couldn't get HLS manifest", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load and extract audio */
|
|
||||||
try {
|
try {
|
||||||
streamInfo.setAudioStreams(extractor.getAudioStreams());
|
streamInfo.setAudioStreams(extractor.getAudioStreams());
|
||||||
} catch (final ContentNotSupportedException e) {
|
} catch (final ContentNotSupportedException e) {
|
||||||
@ -157,31 +153,18 @@ public class StreamInfo extends Info {
|
|||||||
streamInfo.addError(new ExtractionException("Couldn't get audio streams", e));
|
streamInfo.addError(new ExtractionException("Couldn't get audio streams", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Extract video stream url */
|
|
||||||
try {
|
try {
|
||||||
streamInfo.setVideoStreams(extractor.getVideoStreams());
|
streamInfo.setVideoStreams(extractor.getVideoStreams());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
streamInfo.addError(new ExtractionException("Couldn't get video streams", e));
|
streamInfo.addError(new ExtractionException("Couldn't get video streams", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Extract video only stream url */
|
|
||||||
try {
|
try {
|
||||||
streamInfo.setVideoOnlyStreams(extractor.getVideoOnlyStreams());
|
streamInfo.setVideoOnlyStreams(extractor.getVideoOnlyStreams());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
streamInfo.addError(new ExtractionException("Couldn't get video only streams", e));
|
streamInfo.addError(new ExtractionException("Couldn't get video only streams", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lists can be null if an exception was thrown during extraction
|
|
||||||
if (streamInfo.getVideoStreams() == null) {
|
|
||||||
streamInfo.setVideoStreams(Collections.emptyList());
|
|
||||||
}
|
|
||||||
if (streamInfo.getVideoOnlyStreams() == null) {
|
|
||||||
streamInfo.setVideoOnlyStreams(Collections.emptyList());
|
|
||||||
}
|
|
||||||
if (streamInfo.getAudioStreams() == null) {
|
|
||||||
streamInfo.setAudioStreams(Collections.emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Either audio or video has to be available, otherwise we didn't get a stream (since
|
// Either audio or video has to be available, otherwise we didn't get a stream (since
|
||||||
// videoOnly are optional, they don't count).
|
// videoOnly are optional, they don't count).
|
||||||
if ((streamInfo.videoStreams.isEmpty()) && (streamInfo.audioStreams.isEmpty())) {
|
if ((streamInfo.videoStreams.isEmpty()) && (streamInfo.audioStreams.isEmpty())) {
|
||||||
@ -199,7 +182,7 @@ public class StreamInfo extends Info {
|
|||||||
// so the frontend can afterwards check where errors happened.
|
// so the frontend can afterwards check where errors happened.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
streamInfo.setThumbnailUrl(extractor.getThumbnailUrl());
|
streamInfo.setThumbnails(extractor.getThumbnails());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
streamInfo.addError(e);
|
streamInfo.addError(e);
|
||||||
}
|
}
|
||||||
@ -219,7 +202,7 @@ public class StreamInfo extends Info {
|
|||||||
streamInfo.addError(e);
|
streamInfo.addError(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
streamInfo.setUploaderAvatarUrl(extractor.getUploaderAvatarUrl());
|
streamInfo.setUploaderAvatars(extractor.getUploaderAvatars());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
streamInfo.addError(e);
|
streamInfo.addError(e);
|
||||||
}
|
}
|
||||||
@ -245,7 +228,7 @@ public class StreamInfo extends Info {
|
|||||||
streamInfo.addError(e);
|
streamInfo.addError(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
streamInfo.setSubChannelAvatarUrl(extractor.getSubChannelAvatarUrl());
|
streamInfo.setSubChannelAvatars(extractor.getSubChannelAvatars());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
streamInfo.addError(e);
|
streamInfo.addError(e);
|
||||||
}
|
}
|
||||||
@ -353,7 +336,8 @@ public class StreamInfo extends Info {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private StreamType streamType;
|
private StreamType streamType;
|
||||||
private String thumbnailUrl = "";
|
@Nonnull
|
||||||
|
private List<Image> thumbnails = List.of();
|
||||||
private String textualUploadDate;
|
private String textualUploadDate;
|
||||||
private DateWrapper uploadDate;
|
private DateWrapper uploadDate;
|
||||||
private long duration = -1;
|
private long duration = -1;
|
||||||
@ -366,24 +350,26 @@ public class StreamInfo extends Info {
|
|||||||
|
|
||||||
private String uploaderName = "";
|
private String uploaderName = "";
|
||||||
private String uploaderUrl = "";
|
private String uploaderUrl = "";
|
||||||
private String uploaderAvatarUrl = "";
|
@Nonnull
|
||||||
|
private List<Image> uploaderAvatars = List.of();
|
||||||
private boolean uploaderVerified = false;
|
private boolean uploaderVerified = false;
|
||||||
private long uploaderSubscriberCount = -1;
|
private long uploaderSubscriberCount = -1;
|
||||||
|
|
||||||
private String subChannelName = "";
|
private String subChannelName = "";
|
||||||
private String subChannelUrl = "";
|
private String subChannelUrl = "";
|
||||||
private String subChannelAvatarUrl = "";
|
@Nonnull
|
||||||
|
private List<Image> subChannelAvatars = List.of();
|
||||||
|
|
||||||
private List<VideoStream> videoStreams = new ArrayList<>();
|
private List<VideoStream> videoStreams = List.of();
|
||||||
private List<AudioStream> audioStreams = new ArrayList<>();
|
private List<AudioStream> audioStreams = List.of();
|
||||||
private List<VideoStream> videoOnlyStreams = new ArrayList<>();
|
private List<VideoStream> videoOnlyStreams = List.of();
|
||||||
|
|
||||||
private String dashMpdUrl = "";
|
private String dashMpdUrl = "";
|
||||||
private String hlsUrl = "";
|
private String hlsUrl = "";
|
||||||
private List<InfoItem> relatedItems = new ArrayList<>();
|
private List<InfoItem> relatedItems = List.of();
|
||||||
|
|
||||||
private long startPosition = 0;
|
private long startPosition = 0;
|
||||||
private List<SubtitlesStream> subtitles = new ArrayList<>();
|
private List<SubtitlesStream> subtitles = List.of();
|
||||||
|
|
||||||
private String host = "";
|
private String host = "";
|
||||||
private StreamExtractor.Privacy privacy;
|
private StreamExtractor.Privacy privacy;
|
||||||
@ -391,15 +377,15 @@ public class StreamInfo extends Info {
|
|||||||
private String licence = "";
|
private String licence = "";
|
||||||
private String supportInfo = "";
|
private String supportInfo = "";
|
||||||
private Locale language = null;
|
private Locale language = null;
|
||||||
private List<String> tags = new ArrayList<>();
|
private List<String> tags = List.of();
|
||||||
private List<StreamSegment> streamSegments = new ArrayList<>();
|
private List<StreamSegment> streamSegments = List.of();
|
||||||
private List<MetaInfo> metaInfo = new ArrayList<>();
|
private List<MetaInfo> metaInfo = List.of();
|
||||||
private boolean shortFormContent = false;
|
private boolean shortFormContent = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preview frames, e.g. for the storyboard / seekbar thumbnail preview
|
* Preview frames, e.g. for the storyboard / seekbar thumbnail preview
|
||||||
*/
|
*/
|
||||||
private List<Frameset> previewFrames = Collections.emptyList();
|
private List<Frameset> previewFrames = List.of();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the stream type
|
* Get the stream type
|
||||||
@ -419,12 +405,13 @@ public class StreamInfo extends Info {
|
|||||||
*
|
*
|
||||||
* @return the thumbnail url as a string
|
* @return the thumbnail url as a string
|
||||||
*/
|
*/
|
||||||
public String getThumbnailUrl() {
|
@Nonnull
|
||||||
return thumbnailUrl;
|
public List<Image> getThumbnails() {
|
||||||
|
return thumbnails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setThumbnailUrl(final String thumbnailUrl) {
|
public void setThumbnails(@Nonnull final List<Image> thumbnails) {
|
||||||
this.thumbnailUrl = thumbnailUrl;
|
this.thumbnails = thumbnails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTextualUploadDate() {
|
public String getTextualUploadDate() {
|
||||||
@ -522,12 +509,13 @@ public class StreamInfo extends Info {
|
|||||||
this.uploaderUrl = uploaderUrl;
|
this.uploaderUrl = uploaderUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUploaderAvatarUrl() {
|
@Nonnull
|
||||||
return uploaderAvatarUrl;
|
public List<Image> getUploaderAvatars() {
|
||||||
|
return uploaderAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUploaderAvatarUrl(final String uploaderAvatarUrl) {
|
public void setUploaderAvatars(@Nonnull final List<Image> uploaderAvatars) {
|
||||||
this.uploaderAvatarUrl = uploaderAvatarUrl;
|
this.uploaderAvatars = uploaderAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUploaderVerified() {
|
public boolean isUploaderVerified() {
|
||||||
@ -562,12 +550,13 @@ public class StreamInfo extends Info {
|
|||||||
this.subChannelUrl = subChannelUrl;
|
this.subChannelUrl = subChannelUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSubChannelAvatarUrl() {
|
@Nonnull
|
||||||
return subChannelAvatarUrl;
|
public List<Image> getSubChannelAvatars() {
|
||||||
|
return subChannelAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSubChannelAvatarUrl(final String subChannelAvatarUrl) {
|
public void setSubChannelAvatars(@Nonnull final List<Image> subChannelAvatars) {
|
||||||
this.subChannelAvatarUrl = subChannelAvatarUrl;
|
this.subChannelAvatars = subChannelAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<VideoStream> getVideoStreams() {
|
public List<VideoStream> getVideoStreams() {
|
||||||
|
@ -1,32 +1,35 @@
|
|||||||
package org.schabi.newpipe.extractor.stream;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by Christian Schabesberger on 26.08.15.
|
* Created by Christian Schabesberger on 26.08.15.
|
||||||
*
|
*
|
||||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
* StreamInfoItem.java is part of NewPipe.
|
* StreamInfoItem.java is part of NewPipe Extractor.
|
||||||
*
|
*
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package org.schabi.newpipe.extractor.stream;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Info object for previews of unopened videos, eg search results, related videos
|
* Info object for previews of unopened videos, e.g. search results, related videos.
|
||||||
*/
|
*/
|
||||||
public class StreamInfoItem extends InfoItem {
|
public class StreamInfoItem extends InfoItem {
|
||||||
private final StreamType streamType;
|
private final StreamType streamType;
|
||||||
@ -40,7 +43,8 @@ public class StreamInfoItem extends InfoItem {
|
|||||||
private long duration = -1;
|
private long duration = -1;
|
||||||
|
|
||||||
private String uploaderUrl = null;
|
private String uploaderUrl = null;
|
||||||
private String uploaderAvatarUrl = null;
|
@Nonnull
|
||||||
|
private List<Image> uploaderAvatars = List.of();
|
||||||
private boolean uploaderVerified = false;
|
private boolean uploaderVerified = false;
|
||||||
private boolean shortFormContent = false;
|
private boolean shortFormContent = false;
|
||||||
|
|
||||||
@ -88,13 +92,13 @@ public class StreamInfoItem extends InfoItem {
|
|||||||
this.uploaderUrl = uploaderUrl;
|
this.uploaderUrl = uploaderUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nonnull
|
||||||
public String getUploaderAvatarUrl() {
|
public List<Image> getUploaderAvatars() {
|
||||||
return uploaderAvatarUrl;
|
return uploaderAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUploaderAvatarUrl(final String uploaderAvatarUrl) {
|
public void setUploaderAvatars(@Nonnull final List<Image> uploaderAvatars) {
|
||||||
this.uploaderAvatarUrl = uploaderAvatarUrl;
|
this.uploaderAvatars = uploaderAvatars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getShortDescription() {
|
public String getShortDescription() {
|
||||||
@ -152,7 +156,7 @@ public class StreamInfoItem extends InfoItem {
|
|||||||
+ ", serviceId=" + getServiceId()
|
+ ", serviceId=" + getServiceId()
|
||||||
+ ", url='" + getUrl() + '\''
|
+ ", url='" + getUrl() + '\''
|
||||||
+ ", name='" + getName() + '\''
|
+ ", name='" + getName() + '\''
|
||||||
+ ", thumbnailUrl='" + getThumbnailUrl() + '\''
|
+ ", thumbnails='" + getThumbnails() + '\''
|
||||||
+ ", uploaderVerified='" + isUploaderVerified() + '\''
|
+ ", uploaderVerified='" + isUploaderVerified() + '\''
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,41 @@
|
|||||||
package org.schabi.newpipe.extractor.stream;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
|
||||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by Christian Schabesberger on 28.02.16.
|
* Created by Christian Schabesberger on 28.02.16.
|
||||||
*
|
*
|
||||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
* StreamInfoItemExtractor.java is part of NewPipe.
|
* StreamInfoItemExtractor.java is part of NewPipe Extractor.
|
||||||
*
|
*
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public interface StreamInfoItemExtractor extends InfoItemExtractor {
|
package org.schabi.newpipe.extractor.stream;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface StreamInfoItemExtractor extends InfoItemExtractor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the stream type
|
* Get the stream type
|
||||||
*
|
*
|
||||||
* @return the stream type
|
* @return the stream type
|
||||||
* @throws ParsingException thrown if there is an error in the extraction
|
* @throws ParsingException if there is an error in the extraction
|
||||||
*/
|
*/
|
||||||
StreamType getStreamType() throws ParsingException;
|
StreamType getStreamType() throws ParsingException;
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ public interface StreamInfoItemExtractor extends InfoItemExtractor {
|
|||||||
* Check if the stream is an ad.
|
* Check if the stream is an ad.
|
||||||
*
|
*
|
||||||
* @return {@code true} if the stream is an ad.
|
* @return {@code true} if the stream is an ad.
|
||||||
* @throws ParsingException thrown if there is an error in the extraction
|
* @throws ParsingException if there is an error in the extraction
|
||||||
*/
|
*/
|
||||||
boolean isAd() throws ParsingException;
|
boolean isAd() throws ParsingException;
|
||||||
|
|
||||||
@ -49,7 +51,7 @@ public interface StreamInfoItemExtractor extends InfoItemExtractor {
|
|||||||
* Get the stream duration in seconds
|
* Get the stream duration in seconds
|
||||||
*
|
*
|
||||||
* @return the stream duration in seconds
|
* @return the stream duration in seconds
|
||||||
* @throws ParsingException thrown if there is an error in the extraction
|
* @throws ParsingException if there is an error in the extraction
|
||||||
*/
|
*/
|
||||||
long getDuration() throws ParsingException;
|
long getDuration() throws ParsingException;
|
||||||
|
|
||||||
@ -57,7 +59,7 @@ public interface StreamInfoItemExtractor extends InfoItemExtractor {
|
|||||||
* Parses the number of views
|
* Parses the number of views
|
||||||
*
|
*
|
||||||
* @return the number of views or -1 for live streams
|
* @return the number of views or -1 for live streams
|
||||||
* @throws ParsingException thrown if there is an error in the extraction
|
* @throws ParsingException if there is an error in the extraction
|
||||||
*/
|
*/
|
||||||
long getViewCount() throws ParsingException;
|
long getViewCount() throws ParsingException;
|
||||||
|
|
||||||
@ -65,27 +67,29 @@ public interface StreamInfoItemExtractor extends InfoItemExtractor {
|
|||||||
* Get the uploader name
|
* Get the uploader name
|
||||||
*
|
*
|
||||||
* @return the uploader name
|
* @return the uploader name
|
||||||
* @throws ParsingException if parsing fails
|
* @throws ParsingException if there is an error in the extraction
|
||||||
*/
|
*/
|
||||||
String getUploaderName() throws ParsingException;
|
String getUploaderName() throws ParsingException;
|
||||||
|
|
||||||
String getUploaderUrl() throws ParsingException;
|
String getUploaderUrl() throws ParsingException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the uploader's avatar
|
* Get the uploader avatars.
|
||||||
*
|
*
|
||||||
* @return The uploader's avatar url or {@code null} if not provided by the service.
|
* @return the uploader avatars or an empty list if not provided by the service
|
||||||
* @throws ParsingException if there is an error in the extraction
|
* @throws ParsingException if there is an error in the extraction
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nonnull
|
||||||
String getUploaderAvatarUrl() throws ParsingException;
|
default List<Image> getUploaderAvatars() throws ParsingException {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the uploader has been verified by the service's provider.
|
* Whether the uploader has been verified by the service's provider.
|
||||||
* If there is no verification implemented, return <code>false</code>.
|
* If there is no verification implemented, return <code>false</code>.
|
||||||
*
|
*
|
||||||
* @return whether the uploader has been verified by the service's provider
|
* @return whether the uploader has been verified by the service's provider
|
||||||
* @throws ParsingException
|
* @throws ParsingException if there is an error in the extraction
|
||||||
*/
|
*/
|
||||||
boolean isUploaderVerified() throws ParsingException;
|
boolean isUploaderVerified() throws ParsingException;
|
||||||
|
|
||||||
@ -109,8 +113,8 @@ public interface StreamInfoItemExtractor extends InfoItemExtractor {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @return The date and time (can be approximated) this item was uploaded or {@code null}.
|
* @return The date and time (can be approximated) this item was uploaded or {@code null}.
|
||||||
* @throws ParsingException if there is an error in the extraction
|
* @throws ParsingException if there is an error in the extraction or the extracted date
|
||||||
* or the extracted date couldn't be parsed.
|
* couldn't be parsed
|
||||||
* @see #getTextualUploadDate()
|
* @see #getTextualUploadDate()
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -137,6 +141,7 @@ public interface StreamInfoItemExtractor extends InfoItemExtractor {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @return whether the stream is a short-form content
|
* @return whether the stream is a short-form content
|
||||||
|
* @throws ParsingException if there is an error in the extraction
|
||||||
*/
|
*/
|
||||||
default boolean isShortFormContent() throws ParsingException {
|
default boolean isShortFormContent() throws ParsingException {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,3 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Created by Christian Schabesberger on 28.02.16.
|
||||||
|
*
|
||||||
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
|
* StreamInfoItemsCollector.java is part of NewPipe Extractor.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.schabi.newpipe.extractor.stream;
|
package org.schabi.newpipe.extractor.stream;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
||||||
@ -6,26 +26,6 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
|||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
/*
|
|
||||||
* Created by Christian Schabesberger on 28.02.16.
|
|
||||||
*
|
|
||||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
|
||||||
* StreamInfoItemsCollector.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class StreamInfoItemsCollector
|
public class StreamInfoItemsCollector
|
||||||
extends InfoItemsCollector<StreamInfoItem, StreamInfoItemExtractor> {
|
extends InfoItemsCollector<StreamInfoItem, StreamInfoItemExtractor> {
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ public class StreamInfoItemsCollector
|
|||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
resultItem.setThumbnailUrl(extractor.getThumbnailUrl());
|
resultItem.setThumbnails(extractor.getThumbnails());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ public class StreamInfoItemsCollector
|
|||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
resultItem.setUploaderAvatarUrl(extractor.getUploaderAvatarUrl());
|
resultItem.setUploaderAvatars(extractor.getUploaderAvatars());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
@ -111,8 +111,7 @@ public class StreamInfoItemsCollector
|
|||||||
public void commit(final StreamInfoItemExtractor extractor) {
|
public void commit(final StreamInfoItemExtractor extractor) {
|
||||||
try {
|
try {
|
||||||
addItem(extract(extractor));
|
addItem(extract(extractor));
|
||||||
} catch (final FoundAdException ae) {
|
} catch (final FoundAdException ignored) {
|
||||||
//System.out.println("AD_WARNING: " + ae.getMessage());
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
addError(e);
|
addError(e);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
package org.schabi.newpipe.extractor.utils;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializable class representing a suffix (including its format extension, such as {@code .jpg})
|
||||||
|
* which needs to be added to get an image/thumbnail URL with its corresponding height, width and
|
||||||
|
* estimated resolution level.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This class is used to construct {@link org.schabi.newpipe.extractor.Image Image}
|
||||||
|
* instances from a single base URL/path, in order to get all or most image resolutions provided,
|
||||||
|
* depending of the service and the resolutions provided.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that this class is not intended to be used externally and so should only be used when
|
||||||
|
* interfacing with the extractor.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class ImageSuffix implements Serializable {
|
||||||
|
@Nonnull
|
||||||
|
private final String suffix;
|
||||||
|
private final int height;
|
||||||
|
private final int width;
|
||||||
|
@Nonnull
|
||||||
|
private final ResolutionLevel resolutionLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ImageSuffix} instance.
|
||||||
|
*
|
||||||
|
* @param suffix the suffix string
|
||||||
|
* @param height the height corresponding to the image suffix
|
||||||
|
* @param width the width corresponding to the image suffix
|
||||||
|
* @param estimatedResolutionLevel the {@link ResolutionLevel} of the image suffix, which must
|
||||||
|
* not be null
|
||||||
|
* @throws NullPointerException if {@code estimatedResolutionLevel} is {@code null}
|
||||||
|
*/
|
||||||
|
public ImageSuffix(@Nonnull final String suffix,
|
||||||
|
final int height,
|
||||||
|
final int width,
|
||||||
|
@Nonnull final ResolutionLevel estimatedResolutionLevel)
|
||||||
|
throws NullPointerException {
|
||||||
|
this.suffix = suffix;
|
||||||
|
this.height = height;
|
||||||
|
this.width = width;
|
||||||
|
this.resolutionLevel = Objects.requireNonNull(estimatedResolutionLevel,
|
||||||
|
"estimatedResolutionLevel is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the suffix which needs to be appended to get the full image URL
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public String getSuffix() {
|
||||||
|
return suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the height corresponding to the image suffix, which may be unknown
|
||||||
|
*/
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the width corresponding to the image suffix, which may be unknown
|
||||||
|
*/
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the estimated {@link ResolutionLevel} of the suffix, which is never null.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public ResolutionLevel getResolutionLevel() {
|
||||||
|
return resolutionLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a string representation of this {@link ImageSuffix} instance.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The representation will be in the following format, where {@code suffix}, {@code height},
|
||||||
|
* {@code width} and {@code resolutionLevel} represent the corresponding properties:
|
||||||
|
* <br>
|
||||||
|
* <br>
|
||||||
|
* {@code ImageSuffix {url=url, height=height, width=width, resolutionLevel=resolutionLevel}'}
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return a string representation of this {@link ImageSuffix} instance
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ImageSuffix {" + "suffix=" + suffix + ", height=" + height + ", width="
|
||||||
|
+ width + ", resolutionLevel=" + resolutionLevel + "}";
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import java.net.MalformedURLException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -60,6 +61,16 @@ public class ExtractorAsserts {
|
|||||||
assertFalse(stringToCheck.isEmpty(), message);
|
assertFalse(stringToCheck.isEmpty(), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void assertNotEmpty(@Nullable final Collection<?> collectionToCheck) {
|
||||||
|
assertNotEmpty(null, collectionToCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertNotEmpty(@Nullable final String message,
|
||||||
|
@Nullable final Collection<?> collectionToCheck) {
|
||||||
|
assertNotNull(collectionToCheck);
|
||||||
|
assertFalse(collectionToCheck.isEmpty(), message);
|
||||||
|
}
|
||||||
|
|
||||||
public static void assertEmpty(String stringToCheck) {
|
public static void assertEmpty(String stringToCheck) {
|
||||||
assertEmpty(null, stringToCheck);
|
assertEmpty(null, stringToCheck);
|
||||||
}
|
}
|
||||||
@ -70,6 +81,12 @@ public class ExtractorAsserts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void assertEmpty(@Nullable final Collection<?> collectionToCheck) {
|
||||||
|
if (collectionToCheck != null) {
|
||||||
|
assertTrue(collectionToCheck.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void assertNotBlank(String stringToCheck) {
|
public static void assertNotBlank(String stringToCheck) {
|
||||||
assertNotBlank(stringToCheck, null);
|
assertNotBlank(stringToCheck, null);
|
||||||
}
|
}
|
||||||
@ -160,4 +177,50 @@ public class ExtractorAsserts {
|
|||||||
.forEach(expectedTab -> assertTrue(tabSet.contains(expectedTab),
|
.forEach(expectedTab -> assertTrue(tabSet.contains(expectedTab),
|
||||||
"Missing " + expectedTab + " tab (got " + tabSet + ")"));
|
"Missing " + expectedTab + " tab (got " + tabSet + ")"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void assertContainsImageUrlInImageCollection(
|
||||||
|
@Nullable final String exceptedImageUrlContained,
|
||||||
|
@Nullable final Collection<Image> imageCollection) {
|
||||||
|
assertNotNull(exceptedImageUrlContained, "exceptedImageUrlContained is null");
|
||||||
|
assertNotNull(imageCollection, "imageCollection is null");
|
||||||
|
assertTrue(imageCollection.stream().anyMatch(image ->
|
||||||
|
image.getUrl().equals(exceptedImageUrlContained)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertContainsOnlyEquivalentImages(
|
||||||
|
@Nullable final Collection<Image> firstImageCollection,
|
||||||
|
@Nullable final Collection<Image> secondImageCollection) {
|
||||||
|
assertNotNull(firstImageCollection);
|
||||||
|
assertNotNull(secondImageCollection);
|
||||||
|
assertEquals(firstImageCollection.size(), secondImageCollection.size());
|
||||||
|
|
||||||
|
firstImageCollection.forEach(exceptedImage ->
|
||||||
|
assertTrue(secondImageCollection.stream().anyMatch(image ->
|
||||||
|
exceptedImage.getUrl().equals(image.getUrl())
|
||||||
|
&& exceptedImage.getHeight() == image.getHeight()
|
||||||
|
&& exceptedImage.getWidth() == image.getWidth())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertNotOnlyContainsEquivalentImages(
|
||||||
|
@Nullable final Collection<Image> firstImageCollection,
|
||||||
|
@Nullable final Collection<Image> secondImageCollection) {
|
||||||
|
assertNotNull(firstImageCollection);
|
||||||
|
assertNotNull(secondImageCollection);
|
||||||
|
|
||||||
|
if (secondImageCollection.size() != firstImageCollection.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Image unexpectedImage : firstImageCollection) {
|
||||||
|
for (final Image image : secondImageCollection) {
|
||||||
|
if (!image.getUrl().equals(unexpectedImage.getUrl())
|
||||||
|
|| image.getHeight() != unexpectedImage.getHeight()
|
||||||
|
|| image.getWidth() != unexpectedImage.getWidth()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssertionError("All excepted images have an equivalent in the image list");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ public interface BaseChannelExtractorTest extends BaseExtractorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testDescription() throws Exception;
|
void testDescription() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testAvatarUrl() throws Exception;
|
void testAvatars() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testBannerUrl() throws Exception;
|
void testBanners() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testFeedUrl() throws Exception;
|
void testFeedUrl() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
|
@ -4,13 +4,13 @@ import org.junit.jupiter.api.Test;
|
|||||||
|
|
||||||
public interface BasePlaylistExtractorTest extends BaseListExtractorTest {
|
public interface BasePlaylistExtractorTest extends BaseListExtractorTest {
|
||||||
@Test
|
@Test
|
||||||
void testThumbnailUrl() throws Exception;
|
void testThumbnails() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testBannerUrl() throws Exception;
|
void testBanners() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testUploaderName() throws Exception;
|
void testUploaderName() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testUploaderAvatarUrl() throws Exception;
|
void testUploaderAvatars() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testStreamCount() throws Exception;
|
void testStreamCount() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.extractor.services;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public interface BaseStreamExtractorTest extends BaseExtractorTest {
|
public interface BaseStreamExtractorTest extends BaseExtractorTest {
|
||||||
@Test
|
@Test
|
||||||
void testStreamType() throws Exception;
|
void testStreamType() throws Exception;
|
||||||
@ -10,7 +11,7 @@ public interface BaseStreamExtractorTest extends BaseExtractorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testUploaderUrl() throws Exception;
|
void testUploaderUrl() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testUploaderAvatarUrl() throws Exception;
|
void testUploaderAvatars() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testSubscriberCount() throws Exception;
|
void testSubscriberCount() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
@ -18,9 +19,9 @@ public interface BaseStreamExtractorTest extends BaseExtractorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testSubChannelUrl() throws Exception;
|
void testSubChannelUrl() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testSubChannelAvatarUrl() throws Exception;
|
void testSubChannelAvatars() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testThumbnailUrl() throws Exception;
|
void testThumbnails() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
void testDescription() throws Exception;
|
void testDescription() throws Exception;
|
||||||
@Test
|
@Test
|
||||||
|
@ -29,10 +29,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertGreaterOrEqual;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmpty;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEqualsOrderIndependent;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEqualsOrderIndependent;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertGreaterOrEqual;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsValidUrl;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsValidUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestImageCollection;
|
||||||
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestListOfItems;
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestListOfItems;
|
||||||
import static org.schabi.newpipe.extractor.stream.StreamExtractor.UNKNOWN_SUBSCRIBER_COUNT;
|
import static org.schabi.newpipe.extractor.stream.StreamExtractor.UNKNOWN_SUBSCRIBER_COUNT;
|
||||||
|
|
||||||
@ -98,8 +100,8 @@ public abstract class DefaultStreamExtractorTest extends DefaultExtractorTest<St
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testUploaderAvatarUrl() throws Exception {
|
public void testUploaderAvatars() throws Exception {
|
||||||
assertIsSecureUrl(extractor().getUploaderAvatarUrl());
|
defaultTestImageCollection(extractor().getUploaderAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -137,20 +139,20 @@ public abstract class DefaultStreamExtractorTest extends DefaultExtractorTest<St
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testSubChannelAvatarUrl() throws Exception {
|
public void testSubChannelAvatars() throws Exception {
|
||||||
if (expectedSubChannelName().isEmpty() && expectedSubChannelUrl().isEmpty()) {
|
if (expectedSubChannelName().isEmpty() && expectedSubChannelUrl().isEmpty()) {
|
||||||
// this stream has no subchannel
|
// this stream has no subchannel
|
||||||
assertEquals("", extractor().getSubChannelAvatarUrl());
|
assertEmpty(extractor().getSubChannelAvatars());
|
||||||
} else {
|
} else {
|
||||||
// this stream has a subchannel
|
// this stream has a subchannel
|
||||||
assertIsSecureUrl(extractor().getSubChannelAvatarUrl());
|
defaultTestImageCollection(extractor().getSubChannelAvatars());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testThumbnailUrl() throws Exception {
|
public void testThumbnails() throws Exception {
|
||||||
assertIsSecureUrl(extractor().getThumbnailUrl());
|
defaultTestImageCollection(extractor().getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.schabi.newpipe.extractor.services;
|
package org.schabi.newpipe.extractor.services;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
@ -10,12 +11,22 @@ import org.schabi.newpipe.extractor.localization.DateWrapper;
|
|||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.*;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmptyErrors;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertGreaterOrEqual;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertNotEmpty;
|
||||||
import static org.schabi.newpipe.extractor.StreamingService.LinkType;
|
import static org.schabi.newpipe.extractor.StreamingService.LinkType;
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
@ -28,9 +39,9 @@ public final class DefaultTests {
|
|||||||
for (InfoItem item : itemsList) {
|
for (InfoItem item : itemsList) {
|
||||||
assertIsSecureUrl(item.getUrl());
|
assertIsSecureUrl(item.getUrl());
|
||||||
|
|
||||||
final String thumbnailUrl = item.getThumbnailUrl();
|
final List<Image> thumbnails = item.getThumbnails();
|
||||||
if (!isNullOrEmpty(thumbnailUrl)) {
|
if (!isNullOrEmpty(thumbnails)) {
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
defaultTestImageCollection(thumbnails);
|
||||||
}
|
}
|
||||||
assertNotNull(item.getInfoType(), "InfoItem type not set: " + item);
|
assertNotNull(item.getInfoType(), "InfoItem type not set: " + item);
|
||||||
assertEquals(expectedService.getServiceId(), item.getServiceId(), "Unexpected item service id");
|
assertEquals(expectedService.getServiceId(), item.getServiceId(), "Unexpected item service id");
|
||||||
@ -44,9 +55,9 @@ public final class DefaultTests {
|
|||||||
assertExpectedLinkType(expectedService, uploaderUrl, LinkType.CHANNEL);
|
assertExpectedLinkType(expectedService, uploaderUrl, LinkType.CHANNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String uploaderAvatarUrl = streamInfoItem.getUploaderAvatarUrl();
|
final List<Image> uploaderAvatars = streamInfoItem.getUploaderAvatars();
|
||||||
if (!isNullOrEmpty(uploaderAvatarUrl)) {
|
if (!isNullOrEmpty(uploaderAvatars)) {
|
||||||
assertIsSecureUrl(uploaderAvatarUrl);
|
defaultTestImageCollection(uploaderAvatars);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertExpectedLinkType(expectedService, streamInfoItem.getUrl(), LinkType.STREAM);
|
assertExpectedLinkType(expectedService, streamInfoItem.getUrl(), LinkType.STREAM);
|
||||||
@ -134,4 +145,16 @@ public final class DefaultTests {
|
|||||||
final ListExtractor.InfoItemsPage<? extends InfoItem> page = newExtractor.getPage(nextPage);
|
final ListExtractor.InfoItemsPage<? extends InfoItem> page = newExtractor.getPage(nextPage);
|
||||||
defaultTestListOfItems(extractor.getService(), page.getItems(), page.getErrors());
|
defaultTestListOfItems(extractor.getService(), page.getItems(), page.getErrors());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void defaultTestImageCollection(
|
||||||
|
@Nullable final Collection<Image> imageCollection) {
|
||||||
|
assertNotNull(imageCollection);
|
||||||
|
imageCollection.forEach(image -> {
|
||||||
|
assertIsSecureUrl(image.getUrl());
|
||||||
|
assertGreaterOrEqual(Image.HEIGHT_UNKNOWN, image.getHeight(),
|
||||||
|
"Unexpected image height: " + image.getHeight());
|
||||||
|
assertGreaterOrEqual(Image.WIDTH_UNKNOWN, image.getWidth(),
|
||||||
|
"Unexpected image width: " + image.getWidth());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,10 @@ import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
|||||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||||
import org.schabi.newpipe.extractor.services.BaseChannelExtractorTest;
|
import org.schabi.newpipe.extractor.services.BaseChannelExtractorTest;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.Bandcamp;
|
import static org.schabi.newpipe.extractor.ServiceList.Bandcamp;
|
||||||
|
|
||||||
@ -31,51 +34,61 @@ public class BandcampChannelExtractorTest implements BaseChannelExtractorTest {
|
|||||||
assertEquals("making music:)", extractor.getDescription());
|
assertEquals("making music:)", extractor.getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testAvatarUrl() throws Exception {
|
public void testAvatars() throws Exception {
|
||||||
assertTrue(extractor.getAvatarUrl().contains("://f4.bcbits.com/"), "unexpected avatar URL");
|
BandcampTestUtils.testImages(extractor.getAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testBannerUrl() throws Exception {
|
public void testBanners() throws Exception {
|
||||||
assertTrue(extractor.getBannerUrl().contains("://f4.bcbits.com/"), "unexpected banner URL");
|
BandcampTestUtils.testImages(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testFeedUrl() throws Exception {
|
public void testFeedUrl() throws Exception {
|
||||||
assertNull(extractor.getFeedUrl());
|
assertNull(extractor.getFeedUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testSubscriberCount() throws Exception {
|
public void testSubscriberCount() throws Exception {
|
||||||
assertEquals(-1, extractor.getSubscriberCount());
|
assertEquals(-1, extractor.getSubscriberCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testVerified() throws Exception {
|
public void testVerified() throws Exception {
|
||||||
assertFalse(extractor.isVerified());
|
assertFalse(extractor.isVerified());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testServiceId() {
|
public void testServiceId() {
|
||||||
assertEquals(Bandcamp.getServiceId(), extractor.getServiceId());
|
assertEquals(Bandcamp.getServiceId(), extractor.getServiceId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testName() throws Exception {
|
public void testName() throws Exception {
|
||||||
assertEquals("toupie", extractor.getName());
|
assertEquals("toupie", extractor.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testId() throws Exception {
|
public void testId() throws Exception {
|
||||||
assertEquals("2450875064", extractor.getId());
|
assertEquals("2450875064", extractor.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testUrl() throws Exception {
|
public void testUrl() throws Exception {
|
||||||
assertEquals("https://toupie.bandcamp.com", extractor.getUrl());
|
assertEquals("https://toupie.bandcamp.com", extractor.getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testOriginalUrl() throws Exception {
|
public void testOriginalUrl() throws Exception {
|
||||||
assertEquals("https://toupie.bandcamp.com", extractor.getUrl());
|
assertEquals("https://toupie.bandcamp.com", extractor.getUrl());
|
||||||
|
@ -30,22 +30,22 @@ public class BandcampCommentsExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hasComments() throws IOException, ExtractionException {
|
void hasComments() throws IOException, ExtractionException {
|
||||||
assertTrue(extractor.getInitialPage().getItems().size() >= 3);
|
assertTrue(extractor.getInitialPage().getItems().size() >= 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommentsAllData() throws IOException, ExtractionException {
|
void testGetCommentsAllData() throws IOException, ExtractionException {
|
||||||
ListExtractor.InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
ListExtractor.InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
||||||
assertTrue(comments.hasNextPage());
|
assertTrue(comments.hasNextPage());
|
||||||
|
|
||||||
DefaultTests.defaultTestListOfItems(Bandcamp, comments.getItems(), comments.getErrors());
|
DefaultTests.defaultTestListOfItems(Bandcamp, comments.getItems(), comments.getErrors());
|
||||||
for (CommentsInfoItem c : comments.getItems()) {
|
for (final CommentsInfoItem c : comments.getItems()) {
|
||||||
assertFalse(Utils.isBlank(c.getUploaderName()));
|
assertFalse(Utils.isBlank(c.getUploaderName()));
|
||||||
assertFalse(Utils.isBlank(c.getUploaderAvatarUrl()));
|
BandcampTestUtils.testImages(c.getUploaderAvatars());
|
||||||
assertFalse(Utils.isBlank(c.getCommentText().getContent()));
|
assertFalse(Utils.isBlank(c.getCommentText().getContent()));
|
||||||
assertFalse(Utils.isBlank(c.getName()));
|
assertFalse(Utils.isBlank(c.getName()));
|
||||||
assertFalse(Utils.isBlank(c.getThumbnailUrl()));
|
BandcampTestUtils.testImages(c.getThumbnails());
|
||||||
assertFalse(Utils.isBlank(c.getUrl()));
|
assertFalse(Utils.isBlank(c.getUrl()));
|
||||||
assertEquals(-1, c.getLikeCount());
|
assertEquals(-1, c.getLikeCount());
|
||||||
assertTrue(Utils.isBlank(c.getTextualLikeCount()));
|
assertTrue(Utils.isBlank(c.getTextualLikeCount()));
|
||||||
|
@ -19,8 +19,17 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertContains;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertContains;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertContainsOnlyEquivalentImages;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmpty;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertNotOnlyContainsEquivalentImages;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.Bandcamp;
|
import static org.schabi.newpipe.extractor.ServiceList.Bandcamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,7 +46,7 @@ public class BandcampPlaylistExtractorTest {
|
|||||||
* Test whether playlists contain the correct amount of items
|
* Test whether playlists contain the correct amount of items
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCount() throws ExtractionException, IOException {
|
void testCount() throws ExtractionException, IOException {
|
||||||
final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://macbenson.bandcamp.com/album/coming-of-age");
|
final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://macbenson.bandcamp.com/album/coming-of-age");
|
||||||
extractor.fetchPage();
|
extractor.fetchPage();
|
||||||
|
|
||||||
@ -48,13 +57,13 @@ public class BandcampPlaylistExtractorTest {
|
|||||||
* Tests whether different stream thumbnails (track covers) get loaded correctly
|
* Tests whether different stream thumbnails (track covers) get loaded correctly
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDifferentTrackCovers() throws ExtractionException, IOException {
|
void testDifferentTrackCovers() throws ExtractionException, IOException {
|
||||||
final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://zachbensonarchive.bandcamp.com/album/results-of-boredom");
|
final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://zachbensonarchive.bandcamp.com/album/results-of-boredom");
|
||||||
extractor.fetchPage();
|
extractor.fetchPage();
|
||||||
|
|
||||||
final List<StreamInfoItem> l = extractor.getInitialPage().getItems();
|
final List<StreamInfoItem> l = extractor.getInitialPage().getItems();
|
||||||
assertEquals(extractor.getThumbnailUrl(), l.get(0).getThumbnailUrl());
|
assertContainsOnlyEquivalentImages(extractor.getThumbnails(), l.get(0).getThumbnails());
|
||||||
assertNotEquals(extractor.getThumbnailUrl(), l.get(5).getThumbnailUrl());
|
assertNotOnlyContainsEquivalentImages(extractor.getThumbnails(), l.get(5).getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,23 +71,23 @@ public class BandcampPlaylistExtractorTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@Timeout(10)
|
@Timeout(10)
|
||||||
public void testDifferentTrackCoversDuration() throws ExtractionException, IOException {
|
void testDifferentTrackCoversDuration() throws ExtractionException, IOException {
|
||||||
final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://infiniteammo.bandcamp.com/album/night-in-the-woods-vol-1-at-the-end-of-everything");
|
final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://infiniteammo.bandcamp.com/album/night-in-the-woods-vol-1-at-the-end-of-everything");
|
||||||
extractor.fetchPage();
|
extractor.fetchPage();
|
||||||
|
|
||||||
/* All tracks in this album have the same cover art, but I don't know any albums with more than 10 tracks
|
/* All tracks on this album have the same cover art, but I don't know any albums with more
|
||||||
* that has at least one track with a cover art different from the rest.
|
* than 10 tracks that has at least one track with a cover art different from the rest.
|
||||||
*/
|
*/
|
||||||
final List<StreamInfoItem> l = extractor.getInitialPage().getItems();
|
final List<StreamInfoItem> l = extractor.getInitialPage().getItems();
|
||||||
assertEquals(extractor.getThumbnailUrl(), l.get(0).getThumbnailUrl());
|
assertContainsOnlyEquivalentImages(extractor.getThumbnails(), l.get(0).getThumbnails());
|
||||||
assertEquals(extractor.getThumbnailUrl(), l.get(5).getThumbnailUrl());
|
assertContainsOnlyEquivalentImages(extractor.getThumbnails(), l.get(5).getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test playlists with locked content
|
* Test playlists with locked content
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testLockedContent() throws ExtractionException, IOException {
|
void testLockedContent() throws ExtractionException {
|
||||||
final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://billwurtz.bandcamp.com/album/high-enough");
|
final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://billwurtz.bandcamp.com/album/high-enough");
|
||||||
|
|
||||||
assertThrows(ContentNotAvailableException.class, extractor::fetchPage);
|
assertThrows(ContentNotAvailableException.class, extractor::fetchPage);
|
||||||
@ -88,12 +97,11 @@ public class BandcampPlaylistExtractorTest {
|
|||||||
* Test playlist with just one track
|
* Test playlist with just one track
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testSingleStreamPlaylist() throws ExtractionException, IOException {
|
void testSingleStreamPlaylist() throws ExtractionException, IOException {
|
||||||
final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://zachjohnson1.bandcamp.com/album/endless");
|
final PlaylistExtractor extractor = Bandcamp.getPlaylistExtractor("https://zachjohnson1.bandcamp.com/album/endless");
|
||||||
extractor.fetchPage();
|
extractor.fetchPage();
|
||||||
|
|
||||||
assertEquals(1, extractor.getStreamCount());
|
assertEquals(1, extractor.getStreamCount());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ComingOfAge implements BasePlaylistExtractorTest {
|
public static class ComingOfAge implements BasePlaylistExtractorTest {
|
||||||
@ -108,17 +116,17 @@ public class BandcampPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testThumbnailUrl() throws ParsingException {
|
public void testThumbnails() throws ParsingException {
|
||||||
assertTrue(extractor.getThumbnailUrl().contains("f4.bcbits.com/img"));
|
BandcampTestUtils.testImages(extractor.getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws ParsingException {
|
public void testBanners() throws ParsingException {
|
||||||
assertEquals("", extractor.getBannerUrl());
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderUrl() throws ParsingException {
|
void testUploaderUrl() throws ParsingException {
|
||||||
assertTrue(extractor.getUploaderUrl().contains("macbenson.bandcamp.com"));
|
assertTrue(extractor.getUploaderUrl().contains("macbenson.bandcamp.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,8 +136,8 @@ public class BandcampPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderAvatarUrl() throws ParsingException {
|
public void testUploaderAvatars() throws ParsingException {
|
||||||
assertTrue(extractor.getUploaderAvatarUrl().contains("f4.bcbits.com/img"));
|
BandcampTestUtils.testImages(extractor.getUploaderAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -147,13 +155,14 @@ public class BandcampPlaylistExtractorTest {
|
|||||||
assertContains("all rights reserved", description.getContent()); // license
|
assertContains("all rights reserved", description.getContent()); // license
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testUploaderVerified() throws Exception {
|
public void testUploaderVerified() throws Exception {
|
||||||
assertFalse(extractor.isUploaderVerified());
|
assertFalse(extractor.isUploaderVerified());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInitialPage() throws IOException, ExtractionException {
|
void testInitialPage() throws IOException, ExtractionException {
|
||||||
assertNotNull(extractor.getInitialPage().getItems().get(0));
|
assertNotNull(extractor.getInitialPage().getItems().get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +192,7 @@ public class BandcampPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNextPageUrl() throws IOException, ExtractionException {
|
void testNextPageUrl() throws IOException, ExtractionException {
|
||||||
assertNull(extractor.getPage(extractor.getInitialPage().getNextPage()));
|
assertNull(extractor.getPage(extractor.getInitialPage().getNextPage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,12 +3,14 @@ package org.schabi.newpipe.extractor.services.bandcamp;
|
|||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.schabi.newpipe.downloader.DownloaderTestImpl;
|
import org.schabi.newpipe.downloader.DownloaderTestImpl;
|
||||||
|
import org.schabi.newpipe.extractor.ExtractorAsserts;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest;
|
import org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest;
|
||||||
|
import org.schabi.newpipe.extractor.services.DefaultTests;
|
||||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioStreamExtractor;
|
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioStreamExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
@ -36,7 +38,7 @@ public class BandcampRadioStreamExtractorTest extends DefaultStreamExtractorTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGettingCorrectStreamExtractor() throws ExtractionException {
|
void testGettingCorrectStreamExtractor() throws ExtractionException {
|
||||||
assertTrue(Bandcamp.getStreamExtractor("https://bandcamp.com/?show=3") instanceof BandcampRadioStreamExtractor);
|
assertTrue(Bandcamp.getStreamExtractor("https://bandcamp.com/?show=3") instanceof BandcampRadioStreamExtractor);
|
||||||
assertFalse(Bandcamp.getStreamExtractor("https://zachbenson.bandcamp.com/track/deflated")
|
assertFalse(Bandcamp.getStreamExtractor("https://zachbenson.bandcamp.com/track/deflated")
|
||||||
instanceof BandcampRadioStreamExtractor);
|
instanceof BandcampRadioStreamExtractor);
|
||||||
@ -57,15 +59,16 @@ public class BandcampRadioStreamExtractorTest extends DefaultStreamExtractorTest
|
|||||||
@Override public int expectedStreamSegmentsCount() { return 30; }
|
@Override public int expectedStreamSegmentsCount() { return 30; }
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUploaderUrl() {
|
void testGetUploaderUrl() {
|
||||||
assertThrows(ContentNotSupportedException.class, extractor::getUploaderUrl);
|
assertThrows(ContentNotSupportedException.class, extractor::getUploaderUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testUploaderUrl() throws Exception {
|
public void testUploaderUrl() {
|
||||||
assertThrows(ContentNotSupportedException.class, super::testUploaderUrl);
|
assertThrows(ContentNotSupportedException.class, super::testUploaderUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String expectedUploaderUrl() { return null; }
|
@Override public String expectedUploaderUrl() { return null; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -93,16 +96,19 @@ public class BandcampRadioStreamExtractorTest extends DefaultStreamExtractorTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetThumbnailUrl() throws ParsingException {
|
void testGetThumbnails() throws ParsingException {
|
||||||
assertTrue(extractor.getThumbnailUrl().contains("bcbits.com/img"));
|
BandcampTestUtils.testImages(extractor.getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUploaderAvatarUrl() throws ParsingException {
|
void testGetUploaderAvatars() throws ParsingException {
|
||||||
assertTrue(extractor.getUploaderAvatarUrl().contains("bandcamp-button"));
|
DefaultTests.defaultTestImageCollection(extractor.getUploaderAvatars());
|
||||||
|
extractor.getUploaderAvatars().forEach(image ->
|
||||||
|
ExtractorAsserts.assertContains("bandcamp-button", image.getUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testGetAudioStreams() throws ExtractionException, IOException {
|
@Test
|
||||||
|
void testGetAudioStreams() throws ExtractionException, IOException {
|
||||||
assertEquals(1, extractor.getAudioStreams().size());
|
assertEquals(1, extractor.getAudioStreams().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
package org.schabi.newpipe.extractor.services.bandcamp;
|
package org.schabi.newpipe.extractor.services.bandcamp;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.Bandcamp;
|
import static org.schabi.newpipe.extractor.ServiceList.Bandcamp;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
@ -50,8 +49,7 @@ public class BandcampSearchExtractorTest {
|
|||||||
// The track by Zach Benson should be the first result, no?
|
// The track by Zach Benson should be the first result, no?
|
||||||
assertEquals("Best Friend's Basement", bestFriendsBasement.getName());
|
assertEquals("Best Friend's Basement", bestFriendsBasement.getName());
|
||||||
assertEquals("Zach Benson", bestFriendsBasement.getUploaderName());
|
assertEquals("Zach Benson", bestFriendsBasement.getUploaderName());
|
||||||
assertTrue(bestFriendsBasement.getThumbnailUrl().endsWith(".jpg"));
|
BandcampTestUtils.testImages(bestFriendsBasement.getThumbnails());
|
||||||
assertTrue(bestFriendsBasement.getThumbnailUrl().contains("f4.bcbits.com/img/"));
|
|
||||||
assertEquals(InfoItem.InfoType.STREAM, bestFriendsBasement.getInfoType());
|
assertEquals(InfoItem.InfoType.STREAM, bestFriendsBasement.getInfoType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,10 +64,8 @@ public class BandcampSearchExtractorTest {
|
|||||||
|
|
||||||
// C418's artist profile should be the first result, no?
|
// C418's artist profile should be the first result, no?
|
||||||
assertEquals("C418", c418.getName());
|
assertEquals("C418", c418.getName());
|
||||||
assertTrue(c418.getThumbnailUrl().endsWith(".jpg"));
|
BandcampTestUtils.testImages(c418.getThumbnails());
|
||||||
assertTrue(c418.getThumbnailUrl().contains("f4.bcbits.com/img/"));
|
|
||||||
assertEquals("https://c418.bandcamp.com", c418.getUrl());
|
assertEquals("https://c418.bandcamp.com", c418.getUrl());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,9 +78,9 @@ public class BandcampSearchExtractorTest {
|
|||||||
|
|
||||||
// Minecraft volume alpha should be the first result, no?
|
// Minecraft volume alpha should be the first result, no?
|
||||||
assertEquals("Minecraft - Volume Alpha", minecraft.getName());
|
assertEquals("Minecraft - Volume Alpha", minecraft.getName());
|
||||||
assertTrue(minecraft.getThumbnailUrl().endsWith(".jpg"));
|
BandcampTestUtils.testImages(minecraft.getThumbnails());
|
||||||
assertTrue(minecraft.getThumbnailUrl().contains("f4.bcbits.com/img/"));
|
assertEquals(
|
||||||
assertEquals("https://c418.bandcamp.com/album/minecraft-volume-alpha",
|
"https://c418.bandcamp.com/album/minecraft-volume-alpha",
|
||||||
minecraft.getUrl());
|
minecraft.getUrl());
|
||||||
|
|
||||||
// Verify that playlist tracks counts get extracted correctly
|
// Verify that playlist tracks counts get extracted correctly
|
||||||
|
@ -20,7 +20,6 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.Bandcamp;
|
import static org.schabi.newpipe.extractor.ServiceList.Bandcamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -150,13 +149,12 @@ public class BandcampStreamExtractorTest extends DefaultStreamExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testArtistProfilePicture() throws Exception {
|
void testArtistProfilePictures() {
|
||||||
final String url = extractor().getUploaderAvatarUrl();
|
BandcampTestUtils.testImages(extractor.getUploaderAvatars());
|
||||||
assertTrue(url.contains("://f4.bcbits.com/img/") && url.endsWith(".jpg"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTranslateIdsToUrl() throws ParsingException {
|
void testTranslateIdsToUrl() throws ParsingException {
|
||||||
// To add tests: look at website's source, search for `band_id` and `item_id`
|
// To add tests: look at website's source, search for `band_id` and `item_id`
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"https://teaganbear.bandcamp.com/track/just-for-the-halibut-creative-commons-attribution",
|
"https://teaganbear.bandcamp.com/track/just-for-the-halibut-creative-commons-attribution",
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package org.schabi.newpipe.extractor.services.bandcamp;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.services.DefaultTests;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for Bandcamp tests.
|
||||||
|
*/
|
||||||
|
public final class BandcampTestUtils {
|
||||||
|
private BandcampTestUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that Bandcamp images of a {@link Collection} respect
|
||||||
|
* {@link DefaultTests#defaultTestImageCollection(Collection) default requirements}, contain
|
||||||
|
* the string {@code f4.bcbits.com/img} in their URL and end with {@code .jpg} or {@code .png}.
|
||||||
|
*
|
||||||
|
* @param images a Bandcamp {@link Image} {@link Collection}
|
||||||
|
*/
|
||||||
|
public static void testImages(@Nullable final Collection<Image> images) {
|
||||||
|
DefaultTests.defaultTestImageCollection(images);
|
||||||
|
// Disable NPE warning because if the collection is null, an AssertionError would be thrown
|
||||||
|
// by DefaultTests.defaultTestImageCollection
|
||||||
|
//noinspection DataFlowIssue
|
||||||
|
assertTrue(images.stream()
|
||||||
|
.allMatch(image -> image.getUrl().contains("f4.bcbits.com/img")
|
||||||
|
&& (image.getUrl().endsWith(".jpg") || image.getUrl().endsWith(".png"))));
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCConfer
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertContainsImageUrlInImageCollection;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.MediaCCC;
|
import static org.schabi.newpipe.extractor.ServiceList.MediaCCC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,27 +31,29 @@ public class MediaCCCConferenceExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testName() throws Exception {
|
void testName() throws Exception {
|
||||||
assertEquals("FrOSCon 2017", extractor.getName());
|
assertEquals("FrOSCon 2017", extractor.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUrl() throws Exception {
|
void testGetUrl() throws Exception {
|
||||||
assertEquals("https://media.ccc.de/c/froscon2017", extractor.getUrl());
|
assertEquals("https://media.ccc.de/c/froscon2017", extractor.getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetOriginalUrl() throws Exception {
|
void testGetOriginalUrl() throws Exception {
|
||||||
assertEquals("https://media.ccc.de/c/froscon2017", extractor.getOriginalUrl());
|
assertEquals("https://media.ccc.de/c/froscon2017", extractor.getOriginalUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetThumbnailUrl() throws Exception {
|
void testGetThumbnails() {
|
||||||
assertEquals("https://static.media.ccc.de/media/events/froscon/2017/logo.png", extractor.getAvatarUrl());
|
assertContainsImageUrlInImageCollection(
|
||||||
|
"https://static.media.ccc.de/media/events/froscon/2017/logo.png",
|
||||||
|
extractor.getAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetInitalPage() throws Exception {
|
void testGetInitalPage() throws Exception {
|
||||||
assertEquals(97, tabExtractor.getInitialPage().getItems().size());
|
assertEquals(97, tabExtractor.getInitialPage().getItems().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,27 +73,29 @@ public class MediaCCCConferenceExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testName() throws Exception {
|
void testName() throws Exception {
|
||||||
assertEquals("Open Source Conference Albania 2019", extractor.getName());
|
assertEquals("Open Source Conference Albania 2019", extractor.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUrl() throws Exception {
|
void testGetUrl() throws Exception {
|
||||||
assertEquals("https://media.ccc.de/c/oscal19", extractor.getUrl());
|
assertEquals("https://media.ccc.de/c/oscal19", extractor.getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetOriginalUrl() throws Exception {
|
void testGetOriginalUrl() throws Exception {
|
||||||
assertEquals("https://media.ccc.de/c/oscal19", extractor.getOriginalUrl());
|
assertEquals("https://media.ccc.de/c/oscal19", extractor.getOriginalUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetThumbnailUrl() throws Exception {
|
void testGetThumbnailUrl() {
|
||||||
assertEquals("https://static.media.ccc.de/media/events/oscal/2019/oscal-19.png", extractor.getAvatarUrl());
|
assertContainsImageUrlInImageCollection(
|
||||||
|
"https://static.media.ccc.de/media/events/oscal/2019/oscal-19.png",
|
||||||
|
extractor.getAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetInitalPage() throws Exception {
|
void testGetInitalPage() throws Exception {
|
||||||
assertTrue(tabExtractor.getInitialPage().getItems().size() >= 21);
|
assertTrue(tabExtractor.getInitialPage().getItems().size() >= 21);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertContainsImageUrlInImageCollection;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.MediaCCC;
|
import static org.schabi.newpipe.extractor.ServiceList.MediaCCC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,16 +67,20 @@ public class MediaCCCStreamExtractorTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Test
|
@Test
|
||||||
public void testThumbnailUrl() throws Exception {
|
public void testThumbnails() throws Exception {
|
||||||
super.testThumbnailUrl();
|
super.testThumbnails();
|
||||||
assertEquals("https://static.media.ccc.de/media/events/gpn/gpn18/105-hd.jpg", extractor.getThumbnailUrl());
|
assertContainsImageUrlInImageCollection(
|
||||||
|
"https://static.media.ccc.de/media/events/gpn/gpn18/105-hd_preview.jpg",
|
||||||
|
extractor.getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderAvatarUrl() throws Exception {
|
public void testUploaderAvatars() throws Exception {
|
||||||
super.testUploaderAvatarUrl();
|
super.testUploaderAvatars();
|
||||||
assertEquals("https://static.media.ccc.de/media/events/gpn/gpn18/logo.png", extractor.getUploaderAvatarUrl());
|
assertContainsImageUrlInImageCollection(
|
||||||
|
"https://static.media.ccc.de/media/events/gpn/gpn18/logo.png",
|
||||||
|
extractor.getUploaderAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -140,16 +145,20 @@ public class MediaCCCStreamExtractorTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Test
|
@Test
|
||||||
public void testThumbnailUrl() throws Exception {
|
public void testThumbnails() throws Exception {
|
||||||
super.testThumbnailUrl();
|
super.testThumbnails();
|
||||||
assertEquals("https://static.media.ccc.de/media/congress/2019/10565-hd.jpg", extractor.getThumbnailUrl());
|
assertContainsImageUrlInImageCollection(
|
||||||
|
"https://static.media.ccc.de/media/congress/2019/10565-hd_preview.jpg",
|
||||||
|
extractor.getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderAvatarUrl() throws Exception {
|
public void testUploaderAvatars() throws Exception {
|
||||||
super.testUploaderAvatarUrl();
|
super.testUploaderAvatars();
|
||||||
assertEquals("https://static.media.ccc.de/media/congress/2019/logo.png", extractor.getUploaderAvatarUrl());
|
assertContainsImageUrlInImageCollection(
|
||||||
|
"https://static.media.ccc.de/media/congress/2019/logo.png",
|
||||||
|
extractor.getUploaderAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,9 +15,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmpty;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.PeerTube;
|
import static org.schabi.newpipe.extractor.ServiceList.PeerTube;
|
||||||
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestImageCollection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@link PeertubeAccountExtractor}
|
* Test for {@link PeertubeAccountExtractor}
|
||||||
@ -76,13 +77,13 @@ public class PeertubeAccountExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() throws ParsingException {
|
public void testAvatars() {
|
||||||
assertIsSecureUrl(extractor.getAvatarUrl());
|
defaultTestImageCollection(extractor.getAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() {
|
public void testBanners() {
|
||||||
assertNull(extractor.getBannerUrl());
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -95,6 +96,7 @@ public class PeertubeAccountExtractorTest {
|
|||||||
ExtractorAsserts.assertGreaterOrEqual(700, extractor.getSubscriberCount());
|
ExtractorAsserts.assertGreaterOrEqual(700, extractor.getSubscriberCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testVerified() throws Exception {
|
public void testVerified() throws Exception {
|
||||||
assertFalse(extractor.isVerified());
|
assertFalse(extractor.isVerified());
|
||||||
@ -160,18 +162,18 @@ public class PeertubeAccountExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDescription() throws ParsingException {
|
public void testDescription() {
|
||||||
assertNotNull(extractor.getDescription());
|
assertNotNull(extractor.getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() throws ParsingException {
|
public void testAvatars() {
|
||||||
assertIsSecureUrl(extractor.getAvatarUrl());
|
defaultTestImageCollection(extractor.getAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws ParsingException {
|
public void testBanners() {
|
||||||
assertNull(extractor.getBannerUrl());
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -184,6 +186,7 @@ public class PeertubeAccountExtractorTest {
|
|||||||
ExtractorAsserts.assertGreaterOrEqual(100, extractor.getSubscriberCount());
|
ExtractorAsserts.assertGreaterOrEqual(100, extractor.getSubscriberCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testVerified() throws Exception {
|
public void testVerified() throws Exception {
|
||||||
assertFalse(extractor.isVerified());
|
assertFalse(extractor.isVerified());
|
||||||
|
@ -3,7 +3,6 @@ package org.schabi.newpipe.extractor.services.peertube;
|
|||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.schabi.newpipe.downloader.DownloaderTestImpl;
|
import org.schabi.newpipe.downloader.DownloaderTestImpl;
|
||||||
import org.schabi.newpipe.extractor.ExtractorAsserts;
|
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
@ -13,11 +12,12 @@ import org.schabi.newpipe.extractor.services.peertube.extractors.PeertubeChannel
|
|||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmpty;
|
||||||
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertGreaterOrEqual;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.PeerTube;
|
import static org.schabi.newpipe.extractor.ServiceList.PeerTube;
|
||||||
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestImageCollection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@link PeertubeChannelExtractor}
|
* Test for {@link PeertubeChannelExtractor}
|
||||||
@ -71,33 +71,33 @@ public class PeertubeChannelExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDescription() throws ParsingException {
|
public void testDescription() {
|
||||||
assertNotNull(extractor.getDescription());
|
assertNotNull(extractor.getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParentChannelName() throws ParsingException {
|
void testParentChannelName() throws ParsingException {
|
||||||
assertEquals("lqdn", extractor.getParentChannelName());
|
assertEquals("lqdn", extractor.getParentChannelName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParentChannelUrl() throws ParsingException {
|
void testParentChannelUrl() throws ParsingException {
|
||||||
assertEquals("https://video.lqdn.fr/accounts/lqdn", extractor.getParentChannelUrl());
|
assertEquals("https://video.lqdn.fr/accounts/lqdn", extractor.getParentChannelUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParentChannelAvatarUrl() throws ParsingException {
|
void testParentChannelAvatarUrl() {
|
||||||
assertIsSecureUrl(extractor.getParentChannelAvatarUrl());
|
defaultTestImageCollection(extractor.getParentChannelAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() throws ParsingException {
|
public void testAvatars() {
|
||||||
assertIsSecureUrl(extractor.getAvatarUrl());
|
defaultTestImageCollection(extractor.getAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws ParsingException {
|
public void testBanners() {
|
||||||
assertNull(extractor.getBannerUrl());
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -106,10 +106,11 @@ public class PeertubeChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSubscriberCount() throws ParsingException {
|
public void testSubscriberCount() {
|
||||||
ExtractorAsserts.assertGreaterOrEqual(230, extractor.getSubscriberCount());
|
assertGreaterOrEqual(230, extractor.getSubscriberCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testVerified() throws Exception {
|
public void testVerified() throws Exception {
|
||||||
assertFalse(extractor.isVerified());
|
assertFalse(extractor.isVerified());
|
||||||
@ -176,33 +177,33 @@ public class PeertubeChannelExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDescription() throws ParsingException {
|
public void testDescription() {
|
||||||
assertNotNull(extractor.getDescription());
|
assertNotNull(extractor.getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParentChannelName() throws ParsingException {
|
void testParentChannelName() throws ParsingException {
|
||||||
assertEquals("nathan", extractor.getParentChannelName());
|
assertEquals("nathan", extractor.getParentChannelName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParentChannelUrl() throws ParsingException {
|
void testParentChannelUrl() throws ParsingException {
|
||||||
assertEquals("https://skeptikon.fr/accounts/nathan", extractor.getParentChannelUrl());
|
assertEquals("https://skeptikon.fr/accounts/nathan", extractor.getParentChannelUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParentChannelAvatarUrl() throws ParsingException {
|
void testParentChannelAvatars() {
|
||||||
assertIsSecureUrl(extractor.getParentChannelAvatarUrl());
|
defaultTestImageCollection(extractor.getParentChannelAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() throws ParsingException {
|
public void testAvatars() {
|
||||||
assertIsSecureUrl(extractor.getAvatarUrl());
|
defaultTestImageCollection(extractor.getAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws ParsingException {
|
public void testBanners() throws ParsingException {
|
||||||
assertNull(extractor.getBannerUrl());
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -211,10 +212,11 @@ public class PeertubeChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSubscriberCount() throws ParsingException {
|
public void testSubscriberCount() {
|
||||||
ExtractorAsserts.assertGreaterOrEqual(700, extractor.getSubscriberCount());
|
assertGreaterOrEqual(700, extractor.getSubscriberCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testVerified() throws Exception {
|
public void testVerified() throws Exception {
|
||||||
assertFalse(extractor.isVerified());
|
assertFalse(extractor.isVerified());
|
||||||
|
@ -18,6 +18,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.PeerTube;
|
import static org.schabi.newpipe.extractor.ServiceList.PeerTube;
|
||||||
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestImageCollection;
|
||||||
|
|
||||||
public class PeertubeCommentsExtractorTest {
|
public class PeertubeCommentsExtractorTest {
|
||||||
public static class Default {
|
public static class Default {
|
||||||
@ -73,12 +74,12 @@ public class PeertubeCommentsExtractorTest {
|
|||||||
.forEach(commentsInfoItem -> {
|
.forEach(commentsInfoItem -> {
|
||||||
assertFalse(Utils.isBlank(commentsInfoItem.getUploaderUrl()));
|
assertFalse(Utils.isBlank(commentsInfoItem.getUploaderUrl()));
|
||||||
assertFalse(Utils.isBlank(commentsInfoItem.getUploaderName()));
|
assertFalse(Utils.isBlank(commentsInfoItem.getUploaderName()));
|
||||||
assertFalse(Utils.isBlank(commentsInfoItem.getUploaderAvatarUrl()));
|
defaultTestImageCollection(commentsInfoItem.getUploaderAvatars());
|
||||||
assertFalse(Utils.isBlank(commentsInfoItem.getCommentId()));
|
assertFalse(Utils.isBlank(commentsInfoItem.getCommentId()));
|
||||||
assertFalse(Utils.isBlank(commentsInfoItem.getCommentText().getContent()));
|
assertFalse(Utils.isBlank(commentsInfoItem.getCommentText().getContent()));
|
||||||
assertFalse(Utils.isBlank(commentsInfoItem.getName()));
|
assertFalse(Utils.isBlank(commentsInfoItem.getName()));
|
||||||
assertFalse(Utils.isBlank(commentsInfoItem.getTextualUploadDate()));
|
assertFalse(Utils.isBlank(commentsInfoItem.getTextualUploadDate()));
|
||||||
assertFalse(Utils.isBlank(commentsInfoItem.getThumbnailUrl()));
|
defaultTestImageCollection(commentsInfoItem.getThumbnails());
|
||||||
assertFalse(Utils.isBlank(commentsInfoItem.getUrl()));
|
assertFalse(Utils.isBlank(commentsInfoItem.getUrl()));
|
||||||
assertEquals(-1, commentsInfoItem.getLikeCount());
|
assertEquals(-1, commentsInfoItem.getLikeCount());
|
||||||
assertTrue(Utils.isBlank(commentsInfoItem.getTextualLikeCount()));
|
assertTrue(Utils.isBlank(commentsInfoItem.getTextualLikeCount()));
|
||||||
|
@ -2,9 +2,9 @@ package org.schabi.newpipe.extractor.services.peertube;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.PeerTube;
|
import static org.schabi.newpipe.extractor.ServiceList.PeerTube;
|
||||||
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestImageCollection;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.schabi.newpipe.downloader.DownloaderTestImpl;
|
import org.schabi.newpipe.downloader.DownloaderTestImpl;
|
||||||
import org.schabi.newpipe.extractor.ExtractorAsserts;
|
import org.schabi.newpipe.extractor.ExtractorAsserts;
|
||||||
@ -31,11 +31,8 @@ public class PeertubePlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled("URL changes with every request")
|
void testGetThumbnails() throws ParsingException {
|
||||||
void testGetThumbnailUrl() throws ParsingException {
|
defaultTestImageCollection(extractor.getThumbnails());
|
||||||
assertEquals(
|
|
||||||
"https://framatube.org/static/thumbnails/playlist-96b0ee2b-a5a7-4794-8769-58d8ccb79ab7.jpg",
|
|
||||||
extractor.getThumbnailUrl());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -44,10 +41,8 @@ public class PeertubePlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetUploaderAvatarUrl() throws ParsingException {
|
void testGetUploaderAvatars() throws ParsingException {
|
||||||
assertEquals(
|
defaultTestImageCollection(extractor.getUploaderAvatars());
|
||||||
"https://framatube.org/lazy-static/avatars/c6801ff9-cb49-42e6-b2db-3db623248115.jpg",
|
|
||||||
extractor.getUploaderAvatarUrl());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -76,10 +71,8 @@ public class PeertubePlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetSubChannelAvatarUrl() throws ParsingException {
|
void testGetSubChannelAvatars() throws ParsingException {
|
||||||
assertEquals(
|
defaultTestImageCollection(extractor.getSubChannelAvatars());
|
||||||
"https://framatube.org/lazy-static/avatars/e801ccce-8694-4309-b0ab-e6f0e552ef77.png",
|
|
||||||
extractor.getSubChannelAvatarUrl());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,13 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
|||||||
import org.schabi.newpipe.extractor.services.BaseChannelExtractorTest;
|
import org.schabi.newpipe.extractor.services.BaseChannelExtractorTest;
|
||||||
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudChannelExtractor;
|
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudChannelExtractor;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmpty;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmpty;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
|
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
|
||||||
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestImageCollection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@link SoundcloudChannelExtractor}
|
* Test for {@link SoundcloudChannelExtractor}
|
||||||
@ -69,13 +71,13 @@ public class SoundcloudChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() {
|
public void testAvatars() {
|
||||||
assertIsSecureUrl(extractor.getAvatarUrl());
|
defaultTestImageCollection(extractor.getAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() {
|
public void testBanners() {
|
||||||
assertIsSecureUrl(extractor.getBannerUrl());
|
defaultTestImageCollection(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -157,13 +159,13 @@ public class SoundcloudChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() {
|
public void testAvatars() {
|
||||||
assertIsSecureUrl(extractor.getAvatarUrl());
|
defaultTestImageCollection(extractor.getAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() {
|
public void testBanners() {
|
||||||
assertIsSecureUrl(extractor.getBannerUrl());
|
defaultTestImageCollection(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -13,11 +13,18 @@ import org.schabi.newpipe.extractor.services.BasePlaylistExtractorTest;
|
|||||||
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudPlaylistExtractor;
|
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudPlaylistExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmpty;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmpty;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
|
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
|
||||||
import static org.schabi.newpipe.extractor.services.DefaultTests.*;
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestGetPageInNewExtractor;
|
||||||
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestImageCollection;
|
||||||
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestListOfItems;
|
||||||
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestMoreItems;
|
||||||
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestRelatedItems;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@link PlaylistExtractor}
|
* Test for {@link PlaylistExtractor}
|
||||||
@ -82,14 +89,14 @@ public class SoundcloudPlaylistExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testThumbnailUrl() {
|
public void testThumbnails() {
|
||||||
assertIsSecureUrl(extractor.getThumbnailUrl());
|
defaultTestImageCollection(extractor.getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws ParsingException {
|
public void testBanners() throws ParsingException {
|
||||||
// SoundCloud playlists do not have a banner
|
// SoundCloud playlists do not have a banner
|
||||||
assertEmpty(extractor.getBannerUrl());
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -105,8 +112,8 @@ public class SoundcloudPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderAvatarUrl() {
|
public void testUploaderAvatars() {
|
||||||
assertIsSecureUrl(extractor.getUploaderAvatarUrl());
|
defaultTestImageCollection(extractor.getUploaderAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -179,14 +186,14 @@ public class SoundcloudPlaylistExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testThumbnailUrl() {
|
public void testThumbnails() {
|
||||||
assertIsSecureUrl(extractor.getThumbnailUrl());
|
defaultTestImageCollection(extractor.getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws ParsingException {
|
public void testBanners() throws ParsingException {
|
||||||
// SoundCloud playlists do not have a banner
|
// SoundCloud playlists do not have a banner
|
||||||
assertEmpty(extractor.getBannerUrl());
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -202,8 +209,8 @@ public class SoundcloudPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderAvatarUrl() {
|
public void testUploaderAvatars() {
|
||||||
assertIsSecureUrl(extractor.getUploaderAvatarUrl());
|
defaultTestImageCollection(extractor.getUploaderAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -291,14 +298,14 @@ public class SoundcloudPlaylistExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testThumbnailUrl() {
|
public void testThumbnails() {
|
||||||
assertIsSecureUrl(extractor.getThumbnailUrl());
|
defaultTestImageCollection(extractor.getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws ParsingException {
|
public void testBanners() throws ParsingException {
|
||||||
// SoundCloud playlists do not have a banner
|
// SoundCloud playlists do not have a banner
|
||||||
assertEmpty(extractor.getBannerUrl());
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -314,8 +321,8 @@ public class SoundcloudPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderAvatarUrl() {
|
public void testUploaderAvatars() {
|
||||||
assertIsSecureUrl(extractor.getUploaderAvatarUrl());
|
defaultTestImageCollection(extractor.getUploaderAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -395,14 +402,14 @@ public class SoundcloudPlaylistExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testThumbnailUrl() {
|
public void testThumbnails() {
|
||||||
assertIsSecureUrl(extractor.getThumbnailUrl());
|
defaultTestImageCollection(extractor.getThumbnails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws ParsingException {
|
public void testBanners() throws ParsingException {
|
||||||
// SoundCloud playlists do not have a banner
|
// SoundCloud playlists do not have a banner
|
||||||
assertEmpty(extractor.getBannerUrl());
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -418,8 +425,8 @@ public class SoundcloudPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderAvatarUrl() {
|
public void testUploaderAvatars() {
|
||||||
assertIsSecureUrl(extractor.getUploaderAvatarUrl());
|
defaultTestImageCollection(extractor.getUploaderAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -2,11 +2,10 @@ package org.schabi.newpipe.extractor.services.youtube;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertContains;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertContains;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertEmpty;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertNotBlank;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertNotBlank;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
|
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
|
||||||
@ -203,17 +202,13 @@ public class YoutubeChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() throws Exception {
|
public void testAvatars() throws Exception {
|
||||||
String avatarUrl = extractor.getAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getAvatars());
|
||||||
assertIsSecureUrl(avatarUrl);
|
|
||||||
assertContains("yt3", avatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws Exception {
|
public void testBanners() throws Exception {
|
||||||
String bannerUrl = extractor.getBannerUrl();
|
YoutubeTestsUtils.testImages(extractor.getBanners());
|
||||||
assertIsSecureUrl(bannerUrl);
|
|
||||||
assertContains("yt3", bannerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -226,6 +221,7 @@ public class YoutubeChannelExtractorTest {
|
|||||||
ExtractorAsserts.assertGreaterOrEqual(4_900_000, extractor.getSubscriberCount());
|
ExtractorAsserts.assertGreaterOrEqual(4_900_000, extractor.getSubscriberCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testVerified() throws Exception {
|
public void testVerified() throws Exception {
|
||||||
assertTrue(extractor.isVerified());
|
assertTrue(extractor.isVerified());
|
||||||
@ -248,7 +244,7 @@ public class YoutubeChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Youtube RED/Premium ad blocking test
|
// YouTube RED/Premium ad blocking test
|
||||||
public static class VSauce implements BaseChannelExtractorTest {
|
public static class VSauce implements BaseChannelExtractorTest {
|
||||||
private static YoutubeChannelExtractor extractor;
|
private static YoutubeChannelExtractor extractor;
|
||||||
|
|
||||||
@ -300,17 +296,13 @@ public class YoutubeChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() throws Exception {
|
public void testAvatars() throws Exception {
|
||||||
String avatarUrl = extractor.getAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getAvatars());
|
||||||
assertIsSecureUrl(avatarUrl);
|
|
||||||
assertContains("yt3", avatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws Exception {
|
public void testBanners() throws Exception {
|
||||||
String bannerUrl = extractor.getBannerUrl();
|
YoutubeTestsUtils.testImages(extractor.getBanners());
|
||||||
assertIsSecureUrl(bannerUrl);
|
|
||||||
assertContains("yt3", bannerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -400,17 +392,13 @@ public class YoutubeChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() throws Exception {
|
public void testAvatars() throws Exception {
|
||||||
String avatarUrl = extractor.getAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getAvatars());
|
||||||
assertIsSecureUrl(avatarUrl);
|
|
||||||
assertContains("yt3", avatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws Exception {
|
public void testBanners() throws Exception {
|
||||||
String bannerUrl = extractor.getBannerUrl();
|
YoutubeTestsUtils.testImages(extractor.getBanners());
|
||||||
assertIsSecureUrl(bannerUrl);
|
|
||||||
assertContains("yt3", bannerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -524,17 +512,13 @@ public class YoutubeChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() throws Exception {
|
public void testAvatars() throws Exception {
|
||||||
String avatarUrl = extractor.getAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getAvatars());
|
||||||
assertIsSecureUrl(avatarUrl);
|
|
||||||
assertContains("yt3", avatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws Exception {
|
public void testBanners() throws Exception {
|
||||||
String bannerUrl = extractor.getBannerUrl();
|
YoutubeTestsUtils.testImages(extractor.getBanners());
|
||||||
assertIsSecureUrl(bannerUrl);
|
|
||||||
assertContains("yt3", bannerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -621,17 +605,13 @@ public class YoutubeChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() throws Exception {
|
public void testAvatars() throws Exception {
|
||||||
String avatarUrl = extractor.getAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getAvatars());
|
||||||
assertIsSecureUrl(avatarUrl);
|
|
||||||
assertContains("yt3", avatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws Exception {
|
public void testBanners() throws Exception {
|
||||||
String bannerUrl = extractor.getBannerUrl();
|
YoutubeTestsUtils.testImages(extractor.getBanners());
|
||||||
assertIsSecureUrl(bannerUrl);
|
|
||||||
assertContains("yt3", bannerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -689,7 +669,7 @@ public class YoutubeChannelExtractorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testName() throws Exception {
|
public void testName() throws Exception {
|
||||||
assertEquals(extractor.getName(), "Coachella");
|
assertEquals("Coachella", extractor.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -718,16 +698,14 @@ public class YoutubeChannelExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvatarUrl() throws Exception {
|
public void testAvatars() throws Exception {
|
||||||
String avatarUrl = extractor.getAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getAvatars());
|
||||||
assertIsSecureUrl(avatarUrl);
|
|
||||||
assertContains("yt3", avatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBannerUrl() throws Exception {
|
public void testBanners() {
|
||||||
// CarouselHeaderRenders do not contain a banner
|
// A CarouselHeaderRenderer doesn't contain a banner
|
||||||
assertNull(extractor.getBannerUrl());
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -795,17 +773,15 @@ public class YoutubeChannelExtractorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testAvatarUrl() throws Exception {
|
public void testAvatars() throws Exception {
|
||||||
final String avatarUrl = extractor.getAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getAvatars());
|
||||||
assertIsSecureUrl(avatarUrl);
|
|
||||||
assertContains("yt3", avatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testBannerUrl() throws Exception {
|
public void testBanners() throws Exception {
|
||||||
// Banners cannot be extracted from age-restricted channels
|
// Banners cannot be extracted from age-restricted channels
|
||||||
assertTrue(isNullOrEmpty(extractor.getBannerUrl()));
|
assertEmpty(extractor.getBanners());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -913,18 +889,14 @@ public class YoutubeChannelExtractorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testAvatarUrl() throws Exception {
|
public void testAvatars() throws Exception {
|
||||||
final String avatarUrl = extractor.getAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getAvatars());
|
||||||
assertIsSecureUrl(avatarUrl);
|
|
||||||
assertContains("yt3", avatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testBannerUrl() throws Exception {
|
public void testBanners() throws Exception {
|
||||||
final String bannerUrl = extractor.getBannerUrl();
|
YoutubeTestsUtils.testImages(extractor.getBanners());
|
||||||
assertIsSecureUrl(bannerUrl);
|
|
||||||
assertContains("yt3", bannerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -49,7 +49,7 @@ public class YoutubeCommentsExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetComments() throws IOException, ExtractionException {
|
void testGetComments() throws IOException, ExtractionException {
|
||||||
assertTrue(getCommentsHelper(extractor));
|
assertTrue(getCommentsHelper(extractor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,11 +66,11 @@ public class YoutubeCommentsExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommentsFromCommentsInfo() throws IOException, ExtractionException {
|
void testGetCommentsFromCommentsInfo() throws IOException, ExtractionException {
|
||||||
assertTrue(getCommentsFromCommentsInfoHelper(url));
|
assertTrue(getCommentsFromCommentsInfoHelper(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getCommentsFromCommentsInfoHelper(String url) throws IOException, ExtractionException {
|
private boolean getCommentsFromCommentsInfoHelper(final String url) throws IOException, ExtractionException {
|
||||||
final CommentsInfo commentsInfo = CommentsInfo.getInfo(url);
|
final CommentsInfo commentsInfo = CommentsInfo.getInfo(url);
|
||||||
|
|
||||||
assertEquals("Comments", commentsInfo.getName());
|
assertEquals("Comments", commentsInfo.getName());
|
||||||
@ -87,21 +87,21 @@ public class YoutubeCommentsExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommentsAllData() throws IOException, ExtractionException {
|
void testGetCommentsAllData() throws IOException, ExtractionException {
|
||||||
InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
||||||
assertTrue(extractor.getCommentsCount() > 5); // at least 5 comments
|
assertTrue(extractor.getCommentsCount() > 5); // at least 5 comments
|
||||||
|
|
||||||
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
||||||
for (CommentsInfoItem c : comments.getItems()) {
|
for (final CommentsInfoItem c : comments.getItems()) {
|
||||||
assertFalse(Utils.isBlank(c.getUploaderUrl()));
|
assertFalse(Utils.isBlank(c.getUploaderUrl()));
|
||||||
assertFalse(Utils.isBlank(c.getUploaderName()));
|
assertFalse(Utils.isBlank(c.getUploaderName()));
|
||||||
assertFalse(Utils.isBlank(c.getUploaderAvatarUrl()));
|
YoutubeTestsUtils.testImages(c.getUploaderAvatars());
|
||||||
assertFalse(Utils.isBlank(c.getCommentId()));
|
assertFalse(Utils.isBlank(c.getCommentId()));
|
||||||
assertFalse(Utils.isBlank(c.getCommentText().getContent()));
|
assertFalse(Utils.isBlank(c.getCommentText().getContent()));
|
||||||
assertFalse(Utils.isBlank(c.getName()));
|
assertFalse(Utils.isBlank(c.getName()));
|
||||||
assertFalse(Utils.isBlank(c.getTextualUploadDate()));
|
assertFalse(Utils.isBlank(c.getTextualUploadDate()));
|
||||||
assertNotNull(c.getUploadDate());
|
assertNotNull(c.getUploadDate());
|
||||||
assertFalse(Utils.isBlank(c.getThumbnailUrl()));
|
YoutubeTestsUtils.testImages(c.getThumbnails());
|
||||||
assertFalse(Utils.isBlank(c.getUrl()));
|
assertFalse(Utils.isBlank(c.getUrl()));
|
||||||
assertTrue(c.getLikeCount() >= 0);
|
assertTrue(c.getLikeCount() >= 0);
|
||||||
}
|
}
|
||||||
@ -138,19 +138,19 @@ public class YoutubeCommentsExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommentsAllData() throws IOException, ExtractionException {
|
void testGetCommentsAllData() throws IOException, ExtractionException {
|
||||||
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
||||||
|
|
||||||
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
||||||
for (CommentsInfoItem c : comments.getItems()) {
|
for (final CommentsInfoItem c : comments.getItems()) {
|
||||||
assertFalse(Utils.isBlank(c.getUploaderUrl()));
|
assertFalse(Utils.isBlank(c.getUploaderUrl()));
|
||||||
assertFalse(Utils.isBlank(c.getUploaderName()));
|
assertFalse(Utils.isBlank(c.getUploaderName()));
|
||||||
assertFalse(Utils.isBlank(c.getUploaderAvatarUrl()));
|
YoutubeTestsUtils.testImages(c.getUploaderAvatars());
|
||||||
assertFalse(Utils.isBlank(c.getCommentId()));
|
assertFalse(Utils.isBlank(c.getCommentId()));
|
||||||
assertFalse(Utils.isBlank(c.getName()));
|
assertFalse(Utils.isBlank(c.getName()));
|
||||||
assertFalse(Utils.isBlank(c.getTextualUploadDate()));
|
assertFalse(Utils.isBlank(c.getTextualUploadDate()));
|
||||||
assertNotNull(c.getUploadDate());
|
assertNotNull(c.getUploadDate());
|
||||||
assertFalse(Utils.isBlank(c.getThumbnailUrl()));
|
YoutubeTestsUtils.testImages(c.getThumbnails());
|
||||||
assertFalse(Utils.isBlank(c.getUrl()));
|
assertFalse(Utils.isBlank(c.getUrl()));
|
||||||
assertTrue(c.getLikeCount() >= 0);
|
assertTrue(c.getLikeCount() >= 0);
|
||||||
if (c.getCommentId().equals("Ugga_h1-EXdHB3gCoAEC")) { // comment without text
|
if (c.getCommentId().equals("Ugga_h1-EXdHB3gCoAEC")) { // comment without text
|
||||||
@ -177,22 +177,22 @@ public class YoutubeCommentsExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommentsAllData() throws IOException, ExtractionException {
|
void testGetCommentsAllData() throws IOException, ExtractionException {
|
||||||
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
||||||
|
|
||||||
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
||||||
|
|
||||||
boolean heartedByUploader = false;
|
boolean heartedByUploader = false;
|
||||||
|
|
||||||
for (CommentsInfoItem c : comments.getItems()) {
|
for (final CommentsInfoItem c : comments.getItems()) {
|
||||||
assertFalse(Utils.isBlank(c.getUploaderUrl()));
|
assertFalse(Utils.isBlank(c.getUploaderUrl()));
|
||||||
assertFalse(Utils.isBlank(c.getUploaderName()));
|
assertFalse(Utils.isBlank(c.getUploaderName()));
|
||||||
assertFalse(Utils.isBlank(c.getUploaderAvatarUrl()));
|
YoutubeTestsUtils.testImages(c.getUploaderAvatars());
|
||||||
assertFalse(Utils.isBlank(c.getCommentId()));
|
assertFalse(Utils.isBlank(c.getCommentId()));
|
||||||
assertFalse(Utils.isBlank(c.getName()));
|
assertFalse(Utils.isBlank(c.getName()));
|
||||||
assertFalse(Utils.isBlank(c.getTextualUploadDate()));
|
assertFalse(Utils.isBlank(c.getTextualUploadDate()));
|
||||||
assertNotNull(c.getUploadDate());
|
assertNotNull(c.getUploadDate());
|
||||||
assertFalse(Utils.isBlank(c.getThumbnailUrl()));
|
YoutubeTestsUtils.testImages(c.getThumbnails());
|
||||||
assertFalse(Utils.isBlank(c.getUrl()));
|
assertFalse(Utils.isBlank(c.getUrl()));
|
||||||
assertTrue(c.getLikeCount() >= 0);
|
assertTrue(c.getLikeCount() >= 0);
|
||||||
assertFalse(Utils.isBlank(c.getCommentText().getContent()));
|
assertFalse(Utils.isBlank(c.getCommentText().getContent()));
|
||||||
@ -219,20 +219,20 @@ public class YoutubeCommentsExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommentsAllData() throws IOException, ExtractionException {
|
void testGetCommentsAllData() throws IOException, ExtractionException {
|
||||||
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
||||||
|
|
||||||
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
||||||
|
|
||||||
for (CommentsInfoItem c : comments.getItems()) {
|
for (final CommentsInfoItem c : comments.getItems()) {
|
||||||
assertFalse(Utils.isBlank(c.getUploaderUrl()));
|
assertFalse(Utils.isBlank(c.getUploaderUrl()));
|
||||||
assertFalse(Utils.isBlank(c.getUploaderName()));
|
assertFalse(Utils.isBlank(c.getUploaderName()));
|
||||||
assertFalse(Utils.isBlank(c.getUploaderAvatarUrl()));
|
YoutubeTestsUtils.testImages(c.getUploaderAvatars());
|
||||||
assertFalse(Utils.isBlank(c.getCommentId()));
|
assertFalse(Utils.isBlank(c.getCommentId()));
|
||||||
assertFalse(Utils.isBlank(c.getName()));
|
assertFalse(Utils.isBlank(c.getName()));
|
||||||
assertFalse(Utils.isBlank(c.getTextualUploadDate()));
|
assertFalse(Utils.isBlank(c.getTextualUploadDate()));
|
||||||
assertNotNull(c.getUploadDate());
|
assertNotNull(c.getUploadDate());
|
||||||
assertFalse(Utils.isBlank(c.getThumbnailUrl()));
|
YoutubeTestsUtils.testImages(c.getThumbnails());
|
||||||
assertFalse(Utils.isBlank(c.getUrl()));
|
assertFalse(Utils.isBlank(c.getUrl()));
|
||||||
assertTrue(c.getLikeCount() >= 0);
|
assertTrue(c.getLikeCount() >= 0);
|
||||||
assertFalse(Utils.isBlank(c.getCommentText().getContent()));
|
assertFalse(Utils.isBlank(c.getCommentText().getContent()));
|
||||||
@ -260,7 +260,7 @@ public class YoutubeCommentsExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommentsFirst() throws IOException, ExtractionException {
|
void testGetCommentsFirst() throws IOException, ExtractionException {
|
||||||
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
||||||
|
|
||||||
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
||||||
@ -293,7 +293,7 @@ public class YoutubeCommentsExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommentsFirst() throws IOException, ExtractionException {
|
void testGetCommentsFirst() throws IOException, ExtractionException {
|
||||||
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
||||||
|
|
||||||
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
||||||
@ -319,7 +319,7 @@ public class YoutubeCommentsExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCommentsFirstReplies() throws IOException, ExtractionException {
|
void testGetCommentsFirstReplies() throws IOException, ExtractionException {
|
||||||
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
final InfoItemsPage<CommentsInfoItem> comments = extractor.getInitialPage();
|
||||||
|
|
||||||
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
DefaultTests.defaultTestListOfItems(YouTube, comments.getItems(), comments.getErrors());
|
||||||
|
@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
|
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.DISABLE_PRETTY_PRINT_PARAMETER;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.DISABLE_PRETTY_PRINT_PARAMETER;
|
||||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.YOUTUBEI_V1_URL;
|
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.YOUTUBEI_V1_URL;
|
||||||
@ -69,11 +68,10 @@ public class YoutubeMixPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getThumbnailUrl() throws Exception {
|
void getThumbnails() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getThumbnails());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
extractor.getThumbnails().forEach(thumbnail ->
|
||||||
ExtractorAsserts.assertContains("yt", thumbnailUrl);
|
ExtractorAsserts.assertContains(VIDEO_ID, thumbnail.getUrl()));
|
||||||
ExtractorAsserts.assertContains(VIDEO_ID, thumbnailUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -158,11 +156,10 @@ public class YoutubeMixPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getThumbnailUrl() throws Exception {
|
void getThumbnails() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getThumbnails());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
extractor.getThumbnails().forEach(thumbnail ->
|
||||||
ExtractorAsserts.assertContains("yt", thumbnailUrl);
|
ExtractorAsserts.assertContains(VIDEO_ID, thumbnail.getUrl()));
|
||||||
ExtractorAsserts.assertContains(VIDEO_ID, thumbnailUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -248,10 +245,10 @@ public class YoutubeMixPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getThumbnailUrl() throws Exception {
|
void getThumbnails() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getThumbnails());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
extractor.getThumbnails().forEach(thumbnail ->
|
||||||
assertTrue(thumbnailUrl.startsWith("https://i.ytimg.com/vi/" + VIDEO_ID));
|
ExtractorAsserts.assertContains(VIDEO_ID, thumbnail.getUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -366,10 +363,10 @@ public class YoutubeMixPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getThumbnailUrl() throws Exception {
|
void getThumbnails() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getThumbnails());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
extractor.getThumbnails().forEach(thumbnail ->
|
||||||
ExtractorAsserts.assertContains("yt", thumbnailUrl);
|
ExtractorAsserts.assertContains(VIDEO_ID_OF_CHANNEL, thumbnail.getUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -433,11 +430,10 @@ public class YoutubeMixPlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getThumbnailUrl() throws Exception {
|
void getThumbnails() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getThumbnails());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
extractor.getThumbnails().forEach(thumbnail ->
|
||||||
ExtractorAsserts.assertContains("yt", thumbnailUrl);
|
ExtractorAsserts.assertContains(VIDEO_ID, thumbnail.getUrl()));
|
||||||
ExtractorAsserts.assertContains(VIDEO_ID, thumbnailUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -523,10 +519,9 @@ public class YoutubeMixPlaylistExtractorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getThumbnailUrl() throws Exception {
|
void getThumbnailUrl() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getThumbnails());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
extractor.getThumbnails().forEach(thumbnail ->
|
||||||
ExtractorAsserts.assertContains("yt", thumbnailUrl);
|
ExtractorAsserts.assertContains(VIDEO_ID, thumbnail.getUrl()));
|
||||||
ExtractorAsserts.assertContains(VIDEO_ID, thumbnailUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertContains;
|
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertContains;
|
||||||
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
|
|
||||||
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
|
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
|
||||||
import static org.schabi.newpipe.extractor.services.DefaultTests.assertNoMoreItems;
|
import static org.schabi.newpipe.extractor.services.DefaultTests.assertNoMoreItems;
|
||||||
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestGetPageInNewExtractor;
|
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestGetPageInNewExtractor;
|
||||||
@ -121,22 +120,17 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testThumbnailUrl() throws Exception {
|
public void testThumbnails() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getThumbnails());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
|
||||||
ExtractorAsserts.assertContains("yt", thumbnailUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@Test
|
|
||||||
public void testBannerUrl() throws ParsingException {
|
|
||||||
final String bannerUrl = extractor.getBannerUrl();
|
|
||||||
assertIsSecureUrl(bannerUrl);
|
|
||||||
ExtractorAsserts.assertContains("yt", bannerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderUrl() throws Exception {
|
public void testBanners() throws ParsingException {
|
||||||
|
YoutubeTestsUtils.testImages(extractor.getBanners());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUploaderUrl() throws Exception {
|
||||||
assertEquals("https://www.youtube.com/channel/UCs72iRpTEuwV3y6pdWYLgiw", extractor.getUploaderUrl());
|
assertEquals("https://www.youtube.com/channel/UCs72iRpTEuwV3y6pdWYLgiw", extractor.getUploaderUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,9 +141,8 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderAvatarUrl() throws Exception {
|
public void testUploaderAvatars() throws Exception {
|
||||||
final String uploaderAvatarUrl = extractor.getUploaderAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getUploaderAvatars());
|
||||||
ExtractorAsserts.assertContains("yt", uploaderAvatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -157,6 +150,7 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
ExtractorAsserts.assertGreater(100, extractor.getStreamCount());
|
ExtractorAsserts.assertGreater(100, extractor.getStreamCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testUploaderVerified() throws Exception {
|
public void testUploaderVerified() throws Exception {
|
||||||
assertFalse(extractor.isUploaderVerified());
|
assertFalse(extractor.isUploaderVerified());
|
||||||
@ -191,7 +185,7 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetPageInNewExtractor() throws Exception {
|
void testGetPageInNewExtractor() throws Exception {
|
||||||
final PlaylistExtractor newExtractor = YouTube.getPlaylistExtractor(extractor.getUrl());
|
final PlaylistExtractor newExtractor = YouTube.getPlaylistExtractor(extractor.getUrl());
|
||||||
defaultTestGetPageInNewExtractor(extractor, newExtractor);
|
defaultTestGetPageInNewExtractor(extractor, newExtractor);
|
||||||
}
|
}
|
||||||
@ -251,22 +245,17 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testThumbnailUrl() throws Exception {
|
public void testThumbnails() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getThumbnails());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
|
||||||
ExtractorAsserts.assertContains("yt", thumbnailUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@Test
|
|
||||||
public void testBannerUrl() throws ParsingException {
|
|
||||||
final String bannerUrl = extractor.getBannerUrl();
|
|
||||||
assertIsSecureUrl(bannerUrl);
|
|
||||||
ExtractorAsserts.assertContains("yt", bannerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderUrl() throws Exception {
|
public void testBanners() throws ParsingException {
|
||||||
|
YoutubeTestsUtils.testImages(extractor.getBanners());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUploaderUrl() throws Exception {
|
||||||
assertEquals("https://www.youtube.com/channel/UCHSPWoY1J5fbDVbcnyeqwdw", extractor.getUploaderUrl());
|
assertEquals("https://www.youtube.com/channel/UCHSPWoY1J5fbDVbcnyeqwdw", extractor.getUploaderUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,9 +265,8 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderAvatarUrl() throws Exception {
|
public void testUploaderAvatars() throws Exception {
|
||||||
final String uploaderAvatarUrl = extractor.getUploaderAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getUploaderAvatars());
|
||||||
ExtractorAsserts.assertContains("yt", uploaderAvatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -286,9 +274,10 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
ExtractorAsserts.assertGreater(100, extractor.getStreamCount());
|
ExtractorAsserts.assertGreater(100, extractor.getStreamCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testUploaderVerified() throws Exception {
|
public void testUploaderVerified() throws Exception {
|
||||||
assertTrue(extractor.isUploaderVerified());
|
assertFalse(extractor.isUploaderVerified());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -364,22 +353,17 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testThumbnailUrl() throws Exception {
|
public void testThumbnails() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getThumbnails());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
|
||||||
ExtractorAsserts.assertContains("yt", thumbnailUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@Test
|
|
||||||
public void testBannerUrl() throws ParsingException {
|
|
||||||
final String bannerUrl = extractor.getBannerUrl();
|
|
||||||
assertIsSecureUrl(bannerUrl);
|
|
||||||
ExtractorAsserts.assertContains("yt", bannerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderUrl() throws Exception {
|
public void testBanners() throws ParsingException {
|
||||||
|
YoutubeTestsUtils.testImages(extractor.getBanners());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUploaderUrl() throws Exception {
|
||||||
assertEquals("https://www.youtube.com/channel/UCX6b17PVsYBQ0ip5gyeme-Q", extractor.getUploaderUrl());
|
assertEquals("https://www.youtube.com/channel/UCX6b17PVsYBQ0ip5gyeme-Q", extractor.getUploaderUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,9 +374,8 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploaderAvatarUrl() throws Exception {
|
public void testUploaderAvatars() throws Exception {
|
||||||
final String uploaderAvatarUrl = extractor.getUploaderAvatarUrl();
|
YoutubeTestsUtils.testImages(extractor.getUploaderAvatars());
|
||||||
ExtractorAsserts.assertContains("yt", uploaderAvatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -400,9 +383,10 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
ExtractorAsserts.assertGreater(40, extractor.getStreamCount());
|
ExtractorAsserts.assertGreater(40, extractor.getStreamCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testUploaderVerified() throws Exception {
|
public void testUploaderVerified() throws Exception {
|
||||||
assertTrue(extractor.isUploaderVerified());
|
assertFalse(extractor.isUploaderVerified());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -480,18 +464,14 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testThumbnailUrl() throws Exception {
|
public void testThumbnails() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getThumbnails());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
|
||||||
ExtractorAsserts.assertContains("yt", thumbnailUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testBannerUrl() throws Exception {
|
public void testBanners() throws Exception {
|
||||||
final String thumbnailUrl = extractor.getThumbnailUrl();
|
YoutubeTestsUtils.testImages(extractor.getBanners());
|
||||||
assertIsSecureUrl(thumbnailUrl);
|
|
||||||
ExtractorAsserts.assertContains("yt", thumbnailUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -501,10 +481,8 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
public void testUploaderAvatars() throws Exception {
|
||||||
public void testUploaderAvatarUrl() throws Exception {
|
YoutubeTestsUtils.testImages(extractor.getUploaderAvatars());
|
||||||
final String uploaderAvatarUrl = extractor.getUploaderAvatarUrl();
|
|
||||||
ExtractorAsserts.assertContains("yt", uploaderAvatarUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -540,7 +518,7 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoContinuations() throws Exception {
|
void testNoContinuations() throws Exception {
|
||||||
final YoutubePlaylistExtractor extractor = (YoutubePlaylistExtractor) YouTube
|
final YoutubePlaylistExtractor extractor = (YoutubePlaylistExtractor) YouTube
|
||||||
.getPlaylistExtractor(
|
.getPlaylistExtractor(
|
||||||
"https://www.youtube.com/playlist?list=PLXJg25X-OulsVsnvZ7RVtSDW-id9_RzAO");
|
"https://www.youtube.com/playlist?list=PLXJg25X-OulsVsnvZ7RVtSDW-id9_RzAO");
|
||||||
@ -550,7 +528,7 @@ public class YoutubePlaylistExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOnlySingleContinuation() throws Exception {
|
void testOnlySingleContinuation() throws Exception {
|
||||||
final YoutubePlaylistExtractor extractor = (YoutubePlaylistExtractor) YouTube
|
final YoutubePlaylistExtractor extractor = (YoutubePlaylistExtractor) YouTube
|
||||||
.getPlaylistExtractor(
|
.getPlaylistExtractor(
|
||||||
"https://www.youtube.com/playlist?list=PLoumn5BIsUDeGF1vy5Nylf_RJKn5aL_nr");
|
"https://www.youtube.com/playlist?list=PLoumn5BIsUDeGF1vy5Nylf_RJKn5aL_nr");
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user