Skip to content

Commit 22a4151

Browse files
authored
Merge pull request #479 from vkay94/stream-segments
Extract stream segments for YouTube
2 parents e8cc302 + 2ba27b3 commit 22a4151

12 files changed

Lines changed: 274 additions & 1 deletion

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.schabi.newpipe.extractor.stream.Description;
1717
import org.schabi.newpipe.extractor.stream.StreamExtractor;
1818
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
19+
import org.schabi.newpipe.extractor.stream.StreamSegment;
1920
import org.schabi.newpipe.extractor.stream.StreamType;
2021
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
2122
import org.schabi.newpipe.extractor.stream.VideoStream;
@@ -294,4 +295,10 @@ public List<String> getTags() {
294295
public String getSupportInfo() {
295296
return "";
296297
}
298+
299+
@Nonnull
300+
@Override
301+
public List<StreamSegment> getStreamSegments() {
302+
return Collections.emptyList();
303+
}
297304
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/peertube/extractors/PeertubeStreamExtractor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.schabi.newpipe.extractor.stream.Stream;
2424
import org.schabi.newpipe.extractor.stream.StreamExtractor;
2525
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
26+
import org.schabi.newpipe.extractor.stream.StreamSegment;
2627
import org.schabi.newpipe.extractor.stream.StreamType;
2728
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
2829
import org.schabi.newpipe.extractor.stream.VideoStream;
@@ -302,6 +303,12 @@ public String getSupportInfo() {
302303
}
303304
}
304305

306+
@Nonnull
307+
@Override
308+
public List<StreamSegment> getStreamSegments() {
309+
return Collections.emptyList();
310+
}
311+
305312
private String getRelatedStreamsUrl(final List<String> tags) throws UnsupportedEncodingException {
306313
final String url = baseUrl + PeertubeSearchQueryHandlerFactory.SEARCH_ENDPOINT;
307314
final StringBuilder params = new StringBuilder();

extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamExtractor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.schabi.newpipe.extractor.stream.Description;
2121
import org.schabi.newpipe.extractor.stream.StreamExtractor;
2222
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
23+
import org.schabi.newpipe.extractor.stream.StreamSegment;
2324
import org.schabi.newpipe.extractor.stream.StreamType;
2425
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
2526
import org.schabi.newpipe.extractor.stream.VideoStream;
@@ -320,4 +321,10 @@ public List<String> getTags() {
320321
public String getSupportInfo() {
321322
return "";
322323
}
324+
325+
@Nonnull
326+
@Override
327+
public List<StreamSegment> getStreamSegments() {
328+
return Collections.emptyList();
329+
}
323330
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.schabi.newpipe.extractor.stream.StreamExtractor;
3636
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
3737
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
38+
import org.schabi.newpipe.extractor.stream.StreamSegment;
3839
import org.schabi.newpipe.extractor.stream.StreamType;
3940
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
4041
import org.schabi.newpipe.extractor.stream.VideoStream;
@@ -1062,4 +1063,59 @@ public List<String> getTags() {
10621063
public String getSupportInfo() {
10631064
return "";
10641065
}
1066+
1067+
@Nonnull
1068+
@Override
1069+
public List<StreamSegment> getStreamSegments() throws ParsingException {
1070+
final ArrayList<StreamSegment> segments = new ArrayList<>();
1071+
if (initialData.has("engagementPanels")) {
1072+
final JsonArray panels = initialData.getArray("engagementPanels");
1073+
JsonArray segmentsArray = null;
1074+
1075+
// Search for correct panel containing the data
1076+
for (int i = 0; i < panels.size(); i++) {
1077+
if (panels.getObject(i).getObject("engagementPanelSectionListRenderer")
1078+
.getString("panelIdentifier").equals("engagement-panel-macro-markers")) {
1079+
segmentsArray = panels.getObject(i).getObject("engagementPanelSectionListRenderer")
1080+
.getObject("content").getObject("macroMarkersListRenderer").getArray("contents");
1081+
break;
1082+
}
1083+
}
1084+
1085+
if (segmentsArray != null) {
1086+
final long duration = getLength();
1087+
for (final Object object : segmentsArray) {
1088+
final JsonObject segmentJson = ((JsonObject) object).getObject("macroMarkersListItemRenderer");
1089+
1090+
final int startTimeSeconds = segmentJson.getObject("onTap").getObject("watchEndpoint")
1091+
.getInt("startTimeSeconds", -1);
1092+
1093+
if (startTimeSeconds == -1) {
1094+
throw new ParsingException("Could not get stream segment start time.");
1095+
}
1096+
if (startTimeSeconds > duration) {
1097+
break;
1098+
}
1099+
1100+
final String title = getTextFromObject(segmentJson.getObject("title"));
1101+
if (isNullOrEmpty(title)) {
1102+
throw new ParsingException("Could not get stream segment title.");
1103+
}
1104+
1105+
final StreamSegment segment = new StreamSegment(title, startTimeSeconds);
1106+
segment.setUrl(getUrl() + "?t=" + startTimeSeconds);
1107+
if (segmentJson.has("thumbnail")) {
1108+
final JsonArray previewsArray = segmentJson.getObject("thumbnail").getArray("thumbnails");
1109+
if (!previewsArray.isEmpty()) {
1110+
// Assume that the thumbnail with the highest resolution is at the last position
1111+
final String url = previewsArray.getObject(previewsArray.size() - 1).getString("url");
1112+
segment.setPreviewUrl(fixThumbnailUrl(url));
1113+
}
1114+
}
1115+
segments.add(segment);
1116+
}
1117+
}
1118+
}
1119+
return segments;
1120+
}
10651121
}

extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamExtractor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,4 +476,14 @@ protected long getTimestampSeconds(String regexPattern) throws ParsingException
476476
*/
477477
@Nonnull
478478
public abstract String getSupportInfo() throws ParsingException;
479+
480+
/**
481+
* The list of stream segments by timestamps for the stream.
482+
* If the segment list is not available you can simply return an empty list.
483+
*
484+
* @return The list of segments of the stream or an empty list.
485+
* @throws ParsingException
486+
*/
487+
@Nonnull
488+
public abstract List<StreamSegment> getStreamSegments() throws ParsingException;
479489
}

extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,11 @@ private static StreamInfo extractOptionalData(StreamInfo streamInfo, StreamExtra
324324
} catch (Exception e) {
325325
streamInfo.addError(e);
326326
}
327+
try {
328+
streamInfo.setStreamSegments(extractor.getStreamSegments());
329+
} catch (Exception e) {
330+
streamInfo.addError(e);
331+
}
327332

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

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

377383
/**
378384
* Get the stream type
@@ -670,4 +676,12 @@ public void setSupportInfo(String support) {
670676
public String getSupportInfo() {
671677
return this.support;
672678
}
679+
680+
public List<StreamSegment> getStreamSegments() {
681+
return streamSegments;
682+
}
683+
684+
public void setStreamSegments(List<StreamSegment> streamSegments) {
685+
this.streamSegments = streamSegments;
686+
}
673687
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.schabi.newpipe.extractor.stream;
2+
3+
4+
import javax.annotation.Nullable;
5+
import java.io.Serializable;
6+
7+
public class StreamSegment implements Serializable {
8+
/**
9+
* Title of this segment
10+
*/
11+
private String title;
12+
13+
/**
14+
* Timestamp of the starting point in seconds
15+
*/
16+
private int startTimeSeconds;
17+
18+
/**
19+
* Direct url to this segment. This can be null if the service doesn't provide such function.
20+
*/
21+
@Nullable
22+
public String url;
23+
24+
/**
25+
* Preview url for this segment. This can be null if the service doesn't provide such function
26+
* or there is no resource found.
27+
*/
28+
@Nullable
29+
private String previewUrl = null;
30+
31+
public StreamSegment(String title, int startTimeSeconds) {
32+
this.title = title;
33+
this.startTimeSeconds = startTimeSeconds;
34+
}
35+
36+
public String getTitle() {
37+
return title;
38+
}
39+
40+
public void setTitle(final String title) {
41+
this.title = title;
42+
}
43+
44+
public int getStartTimeSeconds() {
45+
return startTimeSeconds;
46+
}
47+
48+
public void setStartTimeSeconds(final int startTimeSeconds) {
49+
this.startTimeSeconds = startTimeSeconds;
50+
}
51+
52+
@Nullable
53+
public String getUrl() {
54+
return url;
55+
}
56+
57+
public void setUrl(@Nullable final String url) {
58+
this.url = url;
59+
}
60+
61+
@Nullable
62+
public String getPreviewUrl() {
63+
return previewUrl;
64+
}
65+
66+
public void setPreviewUrl(@Nullable final String previewUrl) {
67+
this.previewUrl = previewUrl;
68+
}
69+
}

extractor/src/test/java/org/schabi/newpipe/extractor/services/DefaultStreamExtractorTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public abstract class DefaultStreamExtractorTest extends DefaultExtractorTest<St
6666
public Locale expectedLanguageInfo() { return null; } // default: no language info available
6767
public List<String> expectedTags() { return Collections.emptyList(); } // default: no tags
6868
public String expectedSupportInfo() { return ""; } // default: no support info available
69+
public int expectedStreamSegmentsCount() { return -1; } // return 0 or greater to test (default is -1 to ignore)
6970

7071
@Test
7172
@Override
@@ -379,4 +380,11 @@ public void testTags() throws Exception {
379380
public void testSupportInfo() throws Exception {
380381
assertEquals(expectedSupportInfo(), extractor().getSupportInfo());
381382
}
383+
384+
@Test
385+
public void testStreamSegmentsCount() throws Exception {
386+
if (expectedStreamSegmentsCount() >= 0) {
387+
assertEquals(expectedStreamSegmentsCount(), extractor().getStreamSegments().size());
388+
}
389+
}
382390
}

extractor/src/test/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCStreamExtractorTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public static void setUp() throws Exception {
5757
@Override public boolean expectedHasSubtitles() { return false; }
5858
@Override public boolean expectedHasFrames() { return false; }
5959
@Override public List<String> expectedTags() { return Arrays.asList("gpn18", "105"); }
60+
@Override public int expectedStreamSegmentsCount() { return 0; }
6061

6162
@Override
6263
@Test

extractor/src/test/java/org/schabi/newpipe/extractor/services/peertube/PeertubeStreamExtractorTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public void testGetLanguageInformation() throws ParsingException {
9292
@Override public String expectedLicence() { return "Attribution - Share Alike"; }
9393
@Override public Locale expectedLanguageInfo() { return Locale.forLanguageTag("en"); }
9494
@Override public List<String> expectedTags() { return Arrays.asList("framasoft", "peertube"); }
95+
@Override public int expectedStreamSegmentsCount() { return 0; }
9596
}
9697

9798
public static class AgeRestricted extends DefaultStreamExtractorTest {

0 commit comments

Comments
 (0)