Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract stream segments for YouTube #479

Merged
merged 3 commits into from
Dec 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
import org.schabi.newpipe.extractor.stream.StreamSegment;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
Expand Down Expand Up @@ -294,4 +295,10 @@ public List<String> getTags() {
public String getSupportInfo() {
return "";
}

@Nonnull
@Override
public List<StreamSegment> getStreamSegments() {
return Collections.emptyList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
import org.schabi.newpipe.extractor.stream.StreamSegment;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
Expand Down Expand Up @@ -302,6 +303,12 @@ public String getSupportInfo() {
}
}

@Nonnull
@Override
public List<StreamSegment> getStreamSegments() {
return Collections.emptyList();
}

private String getRelatedStreamsUrl(final List<String> tags) throws UnsupportedEncodingException {
final String url = baseUrl + PeertubeSearchQueryHandlerFactory.SEARCH_ENDPOINT;
final StringBuilder params = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
import org.schabi.newpipe.extractor.stream.StreamSegment;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
Expand Down Expand Up @@ -320,4 +321,10 @@ public List<String> getTags() {
public String getSupportInfo() {
return "";
}

@Nonnull
@Override
public List<StreamSegment> getStreamSegments() {
return Collections.emptyList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
import org.schabi.newpipe.extractor.stream.StreamSegment;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
Expand Down Expand Up @@ -1061,4 +1062,59 @@ public List<String> getTags() {
public String getSupportInfo() {
return "";
}

@Nonnull
@Override
public List<StreamSegment> getStreamSegments() throws ParsingException {
final ArrayList<StreamSegment> segments = new ArrayList<>();
if (initialData.has("engagementPanels")) {
final JsonArray panels = initialData.getArray("engagementPanels");
JsonArray segmentsArray = null;

// Search for correct panel containing the data
for (int i = 0; i < panels.size(); i++) {
if (panels.getObject(i).getObject("engagementPanelSectionListRenderer")
.getString("panelIdentifier").equals("engagement-panel-macro-markers")) {
segmentsArray = panels.getObject(i).getObject("engagementPanelSectionListRenderer")
.getObject("content").getObject("macroMarkersListRenderer").getArray("contents");
break;
}
}

if (segmentsArray != null) {
final long duration = getLength();
for (final Object object : segmentsArray) {
final JsonObject segmentJson = ((JsonObject) object).getObject("macroMarkersListItemRenderer");

final int startTimeSeconds = segmentJson.getObject("onTap").getObject("watchEndpoint")
.getInt("startTimeSeconds", -1);

if (startTimeSeconds == -1) {
throw new ParsingException("Could not get stream segment start time.");
}
if (startTimeSeconds > duration) {
break;
}

final String title = getTextFromObject(segmentJson.getObject("title"));
if (isNullOrEmpty(title)) {
throw new ParsingException("Could not get stream segment title.");
}

final StreamSegment segment = new StreamSegment(title, startTimeSeconds);
segment.setUrl(getUrl() + "?t=" + startTimeSeconds);
if (segmentJson.has("thumbnail")) {
final JsonArray previewsArray = segmentJson.getObject("thumbnail").getArray("thumbnails");
if (!previewsArray.isEmpty()) {
// Assume that the thumbnail with the highest resolution is at the last position
final String url = previewsArray.getObject(previewsArray.size() - 1).getString("url");
segment.setPreviewUrl(fixThumbnailUrl(url));
}
}
segments.add(segment);
}
}
}
return segments;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -476,4 +476,14 @@ protected long getTimestampSeconds(String regexPattern) throws ParsingException
*/
@Nonnull
public abstract String getSupportInfo() throws ParsingException;

/**
* The list of stream segments by timestamps for the stream.
* If the segment list is not available you can simply return an empty list.
*
* @return The list of segments of the stream or an empty list.
* @throws ParsingException
*/
@Nonnull
public abstract List<StreamSegment> getStreamSegments() throws ParsingException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,11 @@ private static StreamInfo extractOptionalData(StreamInfo streamInfo, StreamExtra
} catch (Exception e) {
streamInfo.addError(e);
}
try {
streamInfo.setStreamSegments(extractor.getStreamSegments());
} catch (Exception e) {
streamInfo.addError(e);
}

streamInfo.setRelatedStreams(ExtractorHelper.getRelatedVideosOrLogError(streamInfo, extractor));

Expand Down Expand Up @@ -373,6 +378,7 @@ private static StreamInfo extractOptionalData(StreamInfo streamInfo, StreamExtra
private String support = "";
private Locale language = null;
private List<String> tags = new ArrayList<>();
private List<StreamSegment> streamSegments = new ArrayList<>();

/**
* Get the stream type
Expand Down Expand Up @@ -670,4 +676,12 @@ public void setSupportInfo(String support) {
public String getSupportInfo() {
return this.support;
}

public List<StreamSegment> getStreamSegments() {
return streamSegments;
}

public void setStreamSegments(List<StreamSegment> streamSegments) {
this.streamSegments = streamSegments;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.schabi.newpipe.extractor.stream;


import javax.annotation.Nullable;
import java.io.Serializable;

public class StreamSegment implements Serializable {
/**
* Title of this segment
*/
private String title;

/**
* Timestamp of the starting point in seconds
*/
private int startTimeSeconds;

/**
* Direct url to this segment. This can be null if the service doesn't provide such function.
*/
@Nullable
public String url;

/**
* Preview url for this segment. This can be null if the service doesn't provide such function
* or there is no resource found.
*/
@Nullable
private String previewUrl = null;

public StreamSegment(String title, int startTimeSeconds) {
this.title = title;
this.startTimeSeconds = startTimeSeconds;
}

public String getTitle() {
return title;
}

public void setTitle(final String title) {
this.title = title;
}

public int getStartTimeSeconds() {
return startTimeSeconds;
}

public void setStartTimeSeconds(final int startTimeSeconds) {
this.startTimeSeconds = startTimeSeconds;
}

@Nullable
public String getUrl() {
return url;
}

public void setUrl(@Nullable final String url) {
this.url = url;
}

@Nullable
public String getPreviewUrl() {
return previewUrl;
}

public void setPreviewUrl(@Nullable final String previewUrl) {
this.previewUrl = previewUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public abstract class DefaultStreamExtractorTest extends DefaultExtractorTest<St
public Locale expectedLanguageInfo() { return null; } // default: no language info available
public List<String> expectedTags() { return Collections.emptyList(); } // default: no tags
public String expectedSupportInfo() { return ""; } // default: no support info available
public int expectedStreamSegmentsCount() { return -1; } // return 0 or greater to test (default is -1 to ignore)

@Test
@Override
Expand Down Expand Up @@ -379,4 +380,11 @@ public void testTags() throws Exception {
public void testSupportInfo() throws Exception {
assertEquals(expectedSupportInfo(), extractor().getSupportInfo());
}

@Test
public void testStreamSegmentsCount() throws Exception {
if (expectedStreamSegmentsCount() >= 0) {
assertEquals(expectedStreamSegmentsCount(), extractor().getStreamSegments().size());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public static void setUp() throws Exception {
@Override public boolean expectedHasSubtitles() { return false; }
@Override public boolean expectedHasFrames() { return false; }
@Override public List<String> expectedTags() { return Arrays.asList("gpn18", "105"); }
@Override public int expectedStreamSegmentsCount() { return 0; }

@Override
@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public void testGetLanguageInformation() throws ParsingException {
@Override public String expectedLicence() { return "Attribution - Share Alike"; }
@Override public Locale expectedLanguageInfo() { return Locale.forLanguageTag("en"); }
@Override public List<String> expectedTags() { return Arrays.asList("framasoft", "peertube"); }
@Override public int expectedStreamSegmentsCount() { return 0; }
}

public static class AgeRestricted extends DefaultStreamExtractorTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public static void setUp() throws Exception {
@Override public boolean expectedHasVideoStreams() { return false; }
@Override public boolean expectedHasSubtitles() { return false; }
@Override public boolean expectedHasFrames() { return false; }
@Override public int expectedStreamSegmentsCount() { return 0; }
}

}
Loading