Skip to content

Commit b9c0d71

Browse files
authored
Merge pull request #190 from nv95/feature/frames
Fetching frames preview from youtube
2 parents 8ab48c6 + d8279f9 commit b9c0d71

4 files changed

Lines changed: 164 additions & 0 deletions

File tree

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,4 +1020,61 @@ public String getThumbnailUrl() throws ParsingException {
10201020
}
10211021
};
10221022
}
1023+
1024+
@Nonnull
1025+
@Override
1026+
public List<Frameset> getFrames() throws ExtractionException {
1027+
try {
1028+
final String script = doc.select("#player-api").first().siblingElements().select("script").html();
1029+
int p = script.indexOf("ytplayer.config");
1030+
if (p == -1) {
1031+
return Collections.emptyList();
1032+
}
1033+
p = script.indexOf('{', p);
1034+
int e = script.indexOf("ytplayer.load", p);
1035+
if (e == -1) {
1036+
return Collections.emptyList();
1037+
}
1038+
JsonObject jo = JsonParser.object().from(script.substring(p, e - 1));
1039+
final String resp = jo.getObject("args").getString("player_response");
1040+
jo = JsonParser.object().from(resp);
1041+
final String[] spec = jo.getObject("storyboards").getObject("playerStoryboardSpecRenderer").getString("spec").split("\\|");
1042+
final String url = spec[0];
1043+
final ArrayList<Frameset> result = new ArrayList<>(spec.length - 1);
1044+
for (int i = 1; i < spec.length; ++i) {
1045+
final String[] parts = spec[i].split("#");
1046+
if (parts.length != 8) {
1047+
continue;
1048+
}
1049+
final int frameWidth = Integer.parseInt(parts[0]);
1050+
final int frameHeight = Integer.parseInt(parts[1]);
1051+
final int totalCount = Integer.parseInt(parts[2]);
1052+
final int framesPerPageX = Integer.parseInt(parts[3]);
1053+
final int framesPerPageY = Integer.parseInt(parts[4]);
1054+
final String baseUrl = url.replace("$L", String.valueOf(i - 1)).replace("$N", parts[6]) + "&sigh=" + parts[7];
1055+
final List<String> urls;
1056+
if (baseUrl.contains("$M")) {
1057+
final int totalPages = (int) Math.ceil(totalCount / (double) (framesPerPageX * framesPerPageY));
1058+
urls = new ArrayList<>(totalPages);
1059+
for (int j = 0; j < totalPages; j++) {
1060+
urls.add(baseUrl.replace("$M", String.valueOf(j)));
1061+
}
1062+
} else {
1063+
urls = Collections.singletonList(baseUrl);
1064+
}
1065+
result.add(new Frameset(
1066+
urls,
1067+
frameWidth,
1068+
frameHeight,
1069+
totalCount,
1070+
framesPerPageX,
1071+
framesPerPageY
1072+
));
1073+
}
1074+
result.trimToSize();
1075+
return result;
1076+
} catch (Exception e) {
1077+
throw new ExtractionException(e);
1078+
}
1079+
}
10231080
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.schabi.newpipe.extractor.stream;
2+
3+
import javax.annotation.Nullable;
4+
import java.util.Collection;
5+
import java.util.List;
6+
7+
public final class Frameset {
8+
9+
private List<String> urls;
10+
private int frameWidth;
11+
private int frameHeight;
12+
private int totalCount;
13+
private int framesPerPageX;
14+
private int framesPerPageY;
15+
16+
public Frameset(List<String> urls, int frameWidth, int frameHeight, int totalCount, int framesPerPageX, int framesPerPageY) {
17+
this.urls = urls;
18+
this.totalCount = totalCount;
19+
this.frameWidth = frameWidth;
20+
this.frameHeight = frameHeight;
21+
this.framesPerPageX = framesPerPageX;
22+
this.framesPerPageY = framesPerPageY;
23+
}
24+
25+
/**
26+
* @return list of urls to images with frames
27+
*/
28+
public List<String> getUrls() {
29+
return urls;
30+
}
31+
32+
/**
33+
* @return total count of frames
34+
*/
35+
public int getTotalCount() {
36+
return totalCount;
37+
}
38+
39+
/**
40+
* @return maximum frames count by x
41+
*/
42+
public int getFramesPerPageX() {
43+
return framesPerPageX;
44+
}
45+
46+
/**
47+
* @return maximum frames count by y
48+
*/
49+
public int getFramesPerPageY() {
50+
return framesPerPageY;
51+
}
52+
53+
/**
54+
* @return width of a one frame, in pixels
55+
*/
56+
public int getFrameWidth() {
57+
return frameWidth;
58+
}
59+
60+
/**
61+
* @return height of a one frame, in pixels
62+
*/
63+
public int getFrameHeight() {
64+
return frameHeight;
65+
}
66+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
import org.schabi.newpipe.extractor.utils.Parser;
3131

3232
import javax.annotation.Nonnull;
33+
import javax.annotation.Nullable;
3334
import java.io.IOException;
35+
import java.util.ArrayList;
36+
import java.util.Collections;
3437
import java.util.List;
3538

3639
/**
@@ -255,6 +258,17 @@ public StreamExtractor(StreamingService service, LinkHandler linkHandler, Locali
255258
*/
256259
public abstract StreamInfoItemsCollector getRelatedStreams() throws IOException, ExtractionException;
257260

261+
/**
262+
* Should return a list of Frameset object that contains preview of stream frames
263+
* @return list of preview frames or empty list if frames preview is not supported or not found for specified stream
264+
* @throws IOException
265+
* @throws ExtractionException
266+
*/
267+
@Nonnull
268+
public List<Frameset> getFrames() throws IOException, ExtractionException {
269+
return Collections.emptyList();
270+
}
271+
258272
/**
259273
* Should analyse the webpage's document and extracts any error message there might be. (e.g. GEMA block)
260274
*

extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorDefaultTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.junit.BeforeClass;
44
import org.junit.Test;
55
import org.schabi.newpipe.Downloader;
6+
import org.schabi.newpipe.extractor.ExtractorAsserts;
67
import org.schabi.newpipe.extractor.MediaFormat;
78
import org.schabi.newpipe.extractor.NewPipe;
89
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@@ -13,6 +14,7 @@
1314
import org.schabi.newpipe.extractor.utils.Utils;
1415

1516
import java.io.IOException;
17+
import java.util.List;
1618

1719
import static org.junit.Assert.*;
1820
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
@@ -231,4 +233,29 @@ public void testGetFullLinksInDescription() throws ParsingException {
231233
assertFalse(extractor.getDescription().contains("https://youtu.be/U-9tUEOFKNU?list=PL7..."));
232234
}
233235
}
236+
237+
public static class FramesTest {
238+
private static YoutubeStreamExtractor extractor;
239+
240+
@BeforeClass
241+
public static void setUp() throws Exception {
242+
NewPipe.init(Downloader.getInstance(), new Localization("GB", "en"));
243+
extractor = (YoutubeStreamExtractor) YouTube
244+
.getStreamExtractor("https://www.youtube.com/watch?v=HoK9shIJ2xQ");
245+
extractor.fetchPage();
246+
}
247+
248+
@Test
249+
public void testGetFrames() throws ExtractionException {
250+
final List<Frameset> frames = extractor.getFrames();
251+
assertNotNull(frames);
252+
assertFalse(frames.isEmpty());
253+
for (final Frameset f : frames) {
254+
for (final String url : f.getUrls()) {
255+
ExtractorAsserts.assertIsValidUrl(url);
256+
ExtractorAsserts.assertIsSecureUrl(url);
257+
}
258+
}
259+
}
260+
}
234261
}

0 commit comments

Comments
 (0)