diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java index 44e77b01e..0f443d9f1 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java @@ -1035,4 +1035,29 @@ public class YoutubeStreamExtractor extends StreamExtractor { } }; } + + @Nullable + public StreamFrames getFrames() { + try { + final String script = doc.select("#player-api").first().siblingElements().select("script").html(); + int p = script.indexOf("ytplayer.config"); + if (p == -1) { + return null; + } + p = script.indexOf('{', p); + int e = script.indexOf("ytplayer.load", p); + if (e == -1) { + return null; + } + JsonObject jo = JsonParser.object().from(script.substring(p, e - 1)); + final String resp = jo.getObject("args").getString("player_response"); + jo = JsonParser.object().from(resp); + final String[] spec = jo.getObject("storyboards").getObject("playerStoryboardSpecRenderer").getString("spec").split("\\|"); + final String url = spec[0]; + final List opts = Arrays.asList(spec).subList(1, spec.length); + return new StreamFrames(url, opts); + } catch (Exception e) { + return null; + } + } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamFrames.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamFrames.java new file mode 100644 index 000000000..e4eb73319 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamFrames.java @@ -0,0 +1,113 @@ +package org.schabi.newpipe.extractor.stream; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class StreamFrames { + + private final List frames; + + public StreamFrames(String baseUrl, List params) { + frames = new ArrayList<>(params.size()); + for (int i = 0; i < params.size(); i++) { + String param = params.get(i); + final String[] parts = param.split("#"); + frames.add(new Frameset( + baseUrl.replace("$L", String.valueOf(i)).replace("$N", parts[6]) + "&sigh=" + parts[7], + Integer.parseInt(parts[0]), + Integer.parseInt(parts[1]), + Integer.parseInt(parts[2]), + Integer.parseInt(parts[3]), + Integer.parseInt(parts[4]) + )); + } + } + + public int getVariantsCount() { + return frames.size(); + } + + public Frameset getVariant(int index) { + return frames.get(index); + } + + @Nullable + public Frameset getDefaultVariant() { + for (final Frameset f : frames) { + if (f.getUrl().contains("default.jpg")) { + return f; + } + } + return null; + } + + public static class Frameset { + + private String url; + private int frameWidth; + private int frameHeight; + private int totalCount; + private int framesPerPageX; + private int framesPerPageY; + + private Frameset(String url, int frameWidth, int frameHeight, int totalCount, int framesPerPageX, int framesPerPageY) { + this.url = url; + this.totalCount = totalCount; + this.frameWidth = frameWidth; + this.frameHeight = frameHeight; + this.framesPerPageX = framesPerPageX; + this.framesPerPageY = framesPerPageY; + } + + public String getUrl() { + return url; + } + + public String getUrl(int page) { + return url.replace("$M", String.valueOf(page)); + } + + public int getTotalPages() { + if (!url.contains("$M")) { + return 0; + } + return (int) Math.ceil(totalCount / (double) (framesPerPageX * framesPerPageY)); + } + + /** + * @return total count of frames + */ + public int getTotalCount() { + return totalCount; + } + + /** + * @return maximum frames count by x + */ + public int getFramesPerPageX() { + return framesPerPageX; + } + + /** + * @return maximum frames count by y + */ + public int getFramesPerPageY() { + return framesPerPageY; + } + + /** + * @return width of a one frame, in pixels + */ + public int getFrameWidth() { + return frameWidth; + } + + /** + * @return height of a one frame, in pixels + */ + public int getFrameHeight() { + return frameHeight; + } + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorDefaultTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorDefaultTest.java index 9bf0344a1..8da0da0a6 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorDefaultTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorDefaultTest.java @@ -231,4 +231,29 @@ public class YoutubeStreamExtractorDefaultTest { assertFalse(extractor.getDescription().contains("https://youtu.be/U-9tUEOFKNU?list=PL7...")); } } + + public static class FramesTest { + private static YoutubeStreamExtractor extractor; + + @BeforeClass + public static void setUp() throws Exception { + NewPipe.init(Downloader.getInstance(), new Localization("GB", "en")); + extractor = (YoutubeStreamExtractor) YouTube + .getStreamExtractor("https://www.youtube.com/watch?v=HoK9shIJ2xQ"); + extractor.fetchPage(); + } + + @Test + public void testGetFrames() { + final StreamFrames frames = extractor.getFrames(); + assertNotNull(frames); + assertNotNull(frames.getDefaultVariant()); + for (int i=0;i