├── .travis.yml ├── src ├── main │ └── java │ │ └── com │ │ └── sapher │ │ └── youtubedl │ │ ├── DownloadProgressCallback.java │ │ ├── mapper │ │ ├── VideoSubtitle.java │ │ ├── VideoThumbnail.java │ │ ├── HttpHeader.java │ │ ├── VideoFormat.java │ │ └── VideoInfo.java │ │ ├── utils │ │ ├── StreamGobbler.java │ │ └── StreamProcessExtractor.java │ │ ├── YoutubeDLException.java │ │ ├── YoutubeDLResponse.java │ │ ├── YoutubeDLRequest.java │ │ └── YoutubeDL.java └── test │ └── java │ └── com │ └── sapher │ └── youtubedl │ ├── YoutubeDLResponseTest.java │ ├── YoutubeDLTestSuite.java │ ├── YoutubeDLRequestTest.java │ └── YoutubeDLTest.java ├── .gitignore ├── LICENSE ├── pom.xml └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | before_install: 4 | - sudo apt-get install libav-tools 5 | - sudo pip install youtube-dl -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/DownloadProgressCallback.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl; 2 | 3 | public interface DownloadProgressCallback { 4 | void onProgressUpdate(float progress, long etaInSeconds); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/mapper/VideoSubtitle.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl.mapper; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class VideoSubtitle { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/com/sapher/youtubedl/YoutubeDLResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class YoutubeDLResponseTest { 7 | 8 | @Test 9 | public void testTest() { 10 | Assert.assertEquals(true, true); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/mapper/VideoThumbnail.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl.mapper; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class VideoThumbnail { 7 | public String url; 8 | public String id; 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/com/sapher/youtubedl/YoutubeDLTestSuite.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.junit.runners.Suite; 5 | 6 | @RunWith(Suite.class) 7 | @Suite.SuiteClasses({ YoutubeDLTest.class, YoutubeDLRequestTest.class }) 8 | public class YoutubeDLTestSuite { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | .idea 15 | *.iml 16 | 17 | # No target build for github 18 | target/ 19 | 20 | # Gradle 21 | .gradle 22 | gradle -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/mapper/HttpHeader.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl.mapper; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public class HttpHeader { 8 | 9 | @JsonProperty("Accept-Charset") 10 | public String acceptCharset; 11 | @JsonProperty("Accept-Language") 12 | public String acceptLanguage; 13 | @JsonProperty("Accept-Encoding") 14 | public String acceptEncoding; 15 | @JsonProperty("Accept") 16 | public String accept; 17 | @JsonProperty("User-Agent") 18 | public String userAgent; 19 | } -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/utils/StreamGobbler.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl.utils; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | public class StreamGobbler extends Thread { 7 | 8 | private InputStream stream; 9 | private StringBuffer buffer; 10 | 11 | public StreamGobbler(StringBuffer buffer, InputStream stream) { 12 | this.stream = stream; 13 | this.buffer = buffer; 14 | start(); 15 | } 16 | 17 | public void run() { 18 | try { 19 | int nextChar; 20 | while((nextChar = this.stream.read()) != -1) { 21 | this.buffer.append((char) nextChar); 22 | } 23 | } 24 | catch (IOException e) { 25 | 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/mapper/VideoFormat.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl.mapper; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public class VideoFormat { 8 | 9 | public int asr; 10 | public int tbr; 11 | public int abr; 12 | public String format; 13 | @JsonProperty("format_id") 14 | public String formatId; 15 | @JsonProperty("format_note") 16 | public String formatNote; 17 | public String ext; 18 | public int preference; 19 | public String vcodec; 20 | public String acodec; 21 | public int width; 22 | public int height; 23 | public long filesize; 24 | public int fps; 25 | public String url; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/YoutubeDLException.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl; 2 | 3 | /** 4 | * YoutubeDL Exception 5 | */ 6 | public class YoutubeDLException extends Exception { 7 | 8 | /** 9 | * Exception message 10 | */ 11 | private String message; 12 | 13 | /** 14 | * Construct YoutubeDLException with a message 15 | * @param message 16 | */ 17 | public YoutubeDLException(String message) { 18 | this.message = message; 19 | } 20 | 21 | /** 22 | * Construct YoutubeDLException from another exception 23 | * @param e Any exception 24 | */ 25 | public YoutubeDLException(Exception e) { 26 | message = e.getMessage(); 27 | } 28 | 29 | /** 30 | * Get exception message 31 | * @return exception message 32 | */ 33 | @Override 34 | public String getMessage() { 35 | return message; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/sapher/youtubedl/YoutubeDLRequestTest.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class YoutubeDLRequestTest { 7 | 8 | @Test 9 | public void testBuildOptionStandalone() { 10 | 11 | YoutubeDLRequest request = new YoutubeDLRequest(); 12 | request.setOption("help"); 13 | 14 | Assert.assertEquals("--help", request.buildOptions()); 15 | } 16 | 17 | @Test 18 | public void testBuildOptionWithValue() { 19 | 20 | YoutubeDLRequest request = new YoutubeDLRequest(); 21 | request.setOption("password", "1234"); 22 | 23 | Assert.assertEquals("--password 1234", request.buildOptions()); 24 | } 25 | 26 | @Test 27 | public void testBuildChainOptionWithValue() { 28 | 29 | YoutubeDLRequest request = new YoutubeDLRequest(); 30 | request.setOption("password", "1234"); 31 | request.setOption("username", "1234"); 32 | 33 | Assert.assertEquals("--password 1234 --username 1234", request.buildOptions()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/YoutubeDLResponse.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * YoutubeDL response 7 | */ 8 | public class YoutubeDLResponse { 9 | 10 | private Map options; 11 | private String command; 12 | private int exitCode; 13 | private String out; 14 | private String err; 15 | private String directory; 16 | private int elapsedTime; 17 | 18 | public YoutubeDLResponse(String command, Map options, String directory, int exitCode, int elapsedTime, String out, String err) { 19 | this.command = command; 20 | this.options = options; 21 | this.directory = directory; 22 | this.elapsedTime = elapsedTime; 23 | this.exitCode = exitCode; 24 | this.out = out; 25 | this.err = err; 26 | } 27 | 28 | public String getCommand() { 29 | return command; 30 | } 31 | 32 | public int getExitCode() { 33 | return exitCode; 34 | } 35 | 36 | public String getOut() { 37 | return out; 38 | } 39 | 40 | public String getErr() { 41 | return err; 42 | } 43 | 44 | public Map getOptions() { 45 | return options; 46 | } 47 | 48 | public String getDirectory() { 49 | return directory; 50 | } 51 | 52 | public int getElapsedTime() { 53 | return elapsedTime; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/mapper/VideoInfo.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl.mapper; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.ArrayList; 7 | 8 | @JsonIgnoreProperties(ignoreUnknown = true) 9 | public class VideoInfo { 10 | 11 | public String id; 12 | public String fulltitle; 13 | public String title; 14 | @JsonProperty("upload_date") 15 | public String uploadDate; 16 | @JsonProperty("display_id") 17 | public String displayId; 18 | public int duration; 19 | public String description; 20 | public String thumbnail; 21 | public String license; 22 | 23 | @JsonProperty("uploader_id") 24 | public String uploaderId; 25 | public String uploader; 26 | 27 | @JsonProperty("player_url") 28 | public String playerUrl; 29 | @JsonProperty("webpage_url") 30 | public String webpageUrl; 31 | @JsonProperty("webpage_url_basename") 32 | public String webpageUrlBasename; 33 | 34 | public String resolution; 35 | public int width; 36 | public int height; 37 | public String format; 38 | public String ext; 39 | 40 | @JsonProperty("http_headers") 41 | public HttpHeader httpHeader; 42 | public ArrayList categories; 43 | public ArrayList tags; 44 | public ArrayList formats; 45 | public ArrayList thumbnails; 46 | //public ArrayList subtitles; 47 | } 48 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.sapher 8 | youtubedl 9 | 1.1 10 | jar 11 | 12 | YoutubeDL wrapper 13 | Java wrapper for youtube-dl executable 14 | https://github.com/sapher/youtubedl-java 15 | 16 | 17 | 18 | WTFPL 2.0 19 | http://www.wtfpl.net/txt/copying/ 20 | repo 21 | 22 | 23 | 24 | 25 | https://github.com/sapher/youtubedl-java 26 | 27 | 28 | 29 | UTF-8 30 | 2.7.4 31 | 32 | 33 | 34 | 35 | junit 36 | junit 37 | 4.12 38 | 39 | 40 | 41 | com.fasterxml.jackson.core 42 | jackson-annotations 43 | ${jackson.version} 44 | 45 | 46 | 47 | com.fasterxml.jackson.core 48 | jackson-databind 49 | ${jackson.version} 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/utils/StreamProcessExtractor.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl.utils; 2 | 3 | import com.sapher.youtubedl.DownloadProgressCallback; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class StreamProcessExtractor extends Thread { 11 | private static final String GROUP_PERCENT = "percent"; 12 | private static final String GROUP_MINUTES = "minutes"; 13 | private static final String GROUP_SECONDS = "seconds"; 14 | private InputStream stream; 15 | private StringBuffer buffer; 16 | private final DownloadProgressCallback callback; 17 | 18 | private Pattern p = Pattern.compile("\\[download\\]\\s+(?\\d+\\.\\d)% .* ETA (?\\d+):(?\\d+)"); 19 | 20 | public StreamProcessExtractor(StringBuffer buffer, InputStream stream, DownloadProgressCallback callback) { 21 | this.stream = stream; 22 | this.buffer = buffer; 23 | this.callback = callback; 24 | this.start(); 25 | } 26 | 27 | public void run() { 28 | try { 29 | StringBuilder currentLine = new StringBuilder(); 30 | int nextChar; 31 | while ((nextChar = stream.read()) != -1) { 32 | buffer.append((char) nextChar); 33 | if (nextChar == '\r' && callback != null) { 34 | processOutputLine(currentLine.toString()); 35 | currentLine.setLength(0); 36 | continue; 37 | } 38 | currentLine.append((char) nextChar); 39 | } 40 | } catch (IOException ignored) { 41 | } 42 | } 43 | 44 | private void processOutputLine(String line) { 45 | Matcher m = p.matcher(line); 46 | if (m.matches()) { 47 | float progress = Float.parseFloat(m.group(GROUP_PERCENT)); 48 | long eta = convertToSeconds(m.group(GROUP_MINUTES), m.group(GROUP_SECONDS)); 49 | callback.onProgressUpdate(progress, eta); 50 | } 51 | } 52 | 53 | private int convertToSeconds(String minutes, String seconds) { 54 | return Integer.parseInt(minutes) * 60 + Integer.parseInt(seconds); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # youtubedl-java [![Build Status](https://travis-ci.org/sapher/youtubedl-java.svg?branch=master)](https://travis-ci.org/sapher/youtubedl-java) 2 | 3 | A simple java wrapper for [youtube-dl](https://github.com/rg3/youtube-dl) executable 4 | 5 | There's a lot of thing left to do. Parsing output is one of them. Too bad, youtube-dl doesn't output formatted data. 6 | 7 | # Prerequisite 8 | 9 | :warning: Youtube-dl should be installed and available in your `$PATH. 10 | 11 | [How to properly install YoutubeDL executable](https://rg3.github.io/youtube-dl/download.html) 12 | 13 | Otherwise you will get this error : 14 | 15 | `Cannot run program "youtube-dl" (in directory "/Users/my/beautiful/path"): error=2, No such file or directory` 16 | 17 | # Usage 18 | 19 | ## Installation 20 | 21 | You can use jitpack.io to add the library to your project. 22 | 23 | [youtube-dl](https://jitpack.io/#sapher/youtubedl-java) 24 | 25 | ### Gradle 26 | 27 | *Step 1 :* Add jitpack repository to your build file 28 | 29 | ``` 30 | allprojects { 31 | repositories { 32 | maven { url 'https://jitpack.io' } 33 | } 34 | } 35 | ``` 36 | 37 | *Step 2:* Add the dependency 38 | 39 | ``` 40 | dependencies { 41 | compile 'com.github.sapher:youtubedl-java:1.+' 42 | } 43 | ``` 44 | 45 | ## Make request 46 | 47 | ```java 48 | // Video url to download 49 | String videoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"; 50 | 51 | // Destination directory 52 | String directory = System.getProperty("user.home"); 53 | 54 | // Build request 55 | YoutubeDLRequest request = new YoutubeDLRequest(videoUrl, directory); 56 | request.setOption("ignore-errors"); // --ignore-errors 57 | request.setOption("output", "%(id)s"); // --output "%(id)s" 58 | request.setOption("retries", 10); // --retries 10 59 | 60 | // Make request and return response 61 | YoutubeDLResponse response = YoutubeDL.execute(request); 62 | 63 | // Response 64 | String stdOut = response.getOut(); // Executable output 65 | ``` 66 | 67 | You may also specify a callback to get notified about the progress of the download: 68 | 69 | ``` 70 | ... 71 | YoutubeDLResponse response = YoutubeDL.execute(request, new DownloadProgressCallback() { 72 | @Override 73 | public void onProgressUpdate(float progress, long etaInSeconds) { 74 | System.out.println(String.valueOf(progress) + "%"); 75 | } 76 | }); 77 | ``` 78 | # Links 79 | * [Youtube-dl documentation](https://github.com/sapher/youtubedl-java) 80 | -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/YoutubeDLRequest.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * YoutubeDL request 7 | */ 8 | public class YoutubeDLRequest { 9 | 10 | /** 11 | * Executable working directory 12 | */ 13 | private String directory; 14 | 15 | /** 16 | * Video Url 17 | */ 18 | private String url; 19 | 20 | /** 21 | * List of executable options 22 | */ 23 | private Map options = new HashMap(); 24 | 25 | public String getDirectory() { 26 | return directory; 27 | } 28 | 29 | public void setDirectory(String directory) { 30 | this.directory = directory; 31 | } 32 | 33 | public String getUrl() { 34 | return url; 35 | } 36 | 37 | public void setUrl(String url) { 38 | this.url = url; 39 | } 40 | 41 | public Map getOption() { 42 | return options; 43 | } 44 | 45 | public void setOption(String key) { 46 | options.put(key, null); 47 | } 48 | 49 | public void setOption(String key, String value) { 50 | options.put(key, value); 51 | } 52 | 53 | public void setOption(String key, int value) { 54 | options.put(key, String.valueOf(value)); 55 | } 56 | 57 | /** 58 | * Constructor 59 | */ 60 | public YoutubeDLRequest() { 61 | 62 | } 63 | 64 | /** 65 | * Construct a request with a videoUrl 66 | * @param url 67 | */ 68 | public YoutubeDLRequest(String url) { 69 | this.url = url; 70 | } 71 | 72 | /** 73 | * Construct a request with a videoUrl and working directory 74 | * @param url 75 | * @param directory 76 | */ 77 | public YoutubeDLRequest(String url, String directory) { 78 | this.url = url; 79 | this.directory = directory; 80 | } 81 | 82 | /** 83 | * Transform options to a string that the executable will execute 84 | * @return Command string 85 | */ 86 | protected String buildOptions() { 87 | 88 | StringBuilder builder = new StringBuilder(); 89 | 90 | // Set Url 91 | if(url != null) 92 | builder.append(url + " "); 93 | 94 | // Build options strings 95 | Iterator it = options.entrySet().iterator(); 96 | while (it.hasNext()) { 97 | Map.Entry option = (Map.Entry) it.next(); 98 | 99 | String name = (String) option.getKey(); 100 | String value = (String) option.getValue(); 101 | 102 | if(value == null) value = ""; 103 | 104 | String optionFormatted = String.format("--%s %s", name, value).trim(); 105 | builder.append(optionFormatted + " "); 106 | 107 | it.remove(); 108 | } 109 | 110 | return builder.toString().trim(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/java/com/sapher/youtubedl/YoutubeDLTest.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl; 2 | 3 | import com.sapher.youtubedl.mapper.VideoFormat; 4 | import com.sapher.youtubedl.mapper.VideoInfo; 5 | import com.sapher.youtubedl.mapper.VideoThumbnail; 6 | import org.junit.Test; 7 | import org.junit.Assert; 8 | 9 | import java.util.List; 10 | 11 | public class YoutubeDLTest { 12 | 13 | private final static String DIRECTORY = System.getProperty("java.io.tmpdir"); 14 | private final static String VIDEO_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"; 15 | private final static String NONE_EXISTENT_VIDEO_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcZ"; 16 | 17 | /**@Test 18 | public void testUsingOwnExecutablePath() throws YoutubeDLException { 19 | YoutubeDL.setExecutablePath("/usr/bin/youtube-dl"); 20 | Assert.assertNotNull(YoutubeDL.getVersion()); 21 | }**/ 22 | 23 | @Test 24 | public void testGetVersion() throws YoutubeDLException { 25 | Assert.assertNotNull(YoutubeDL.getVersion()); 26 | } 27 | 28 | @Test 29 | public void testElapsedTime() throws YoutubeDLException { 30 | 31 | long startTime = System.nanoTime(); 32 | 33 | YoutubeDLRequest request = new YoutubeDLRequest(); 34 | request.setOption("version"); 35 | YoutubeDLResponse response = YoutubeDL.execute(request); 36 | 37 | int elapsedTime = (int) (System.nanoTime() - startTime); 38 | 39 | Assert.assertTrue(elapsedTime > response.getElapsedTime()); 40 | } 41 | 42 | 43 | @Test 44 | public void testSimulateDownload() throws YoutubeDLException { 45 | 46 | YoutubeDLRequest request = new YoutubeDLRequest(); 47 | request.setUrl(VIDEO_URL); 48 | request.setOption("simulate"); 49 | 50 | YoutubeDLResponse response = YoutubeDL.execute(request); 51 | 52 | Assert.assertEquals("youtube-dl " + VIDEO_URL + " --simulate", response.getCommand()); 53 | } 54 | 55 | @Test 56 | public void testDirectory() throws YoutubeDLException { 57 | 58 | YoutubeDLRequest request = new YoutubeDLRequest(VIDEO_URL, DIRECTORY); 59 | request.setOption("simulate"); 60 | 61 | YoutubeDLResponse response = YoutubeDL.execute(request); 62 | 63 | Assert.assertEquals(DIRECTORY, response.getDirectory()); 64 | } 65 | 66 | @Test 67 | public void testGetVideoInfo() throws YoutubeDLException { 68 | VideoInfo videoInfo = YoutubeDL.getVideoInfo(VIDEO_URL); 69 | Assert.assertNotNull(videoInfo); 70 | } 71 | 72 | @Test 73 | public void testGetFormats() throws YoutubeDLException { 74 | List formats = YoutubeDL.getFormats(VIDEO_URL); 75 | Assert.assertNotNull(formats); 76 | Assert.assertTrue(formats.size() > 0); 77 | } 78 | 79 | @Test 80 | public void testGetThumbnails() throws YoutubeDLException { 81 | List thumbnails = YoutubeDL.getThumbnails(VIDEO_URL); 82 | Assert.assertNotNull(thumbnails); 83 | Assert.assertTrue(thumbnails.size() > 0); 84 | } 85 | 86 | @Test 87 | public void testGetTags() throws YoutubeDLException { 88 | List tags = YoutubeDL.getTags(VIDEO_URL); 89 | Assert.assertNotNull(tags); 90 | Assert.assertTrue(tags.size() > 0); 91 | } 92 | 93 | @Test 94 | public void testGetCategories() throws YoutubeDLException { 95 | List categories = YoutubeDL.getCategories(VIDEO_URL); 96 | Assert.assertNotNull(categories); 97 | Assert.assertTrue(categories.size() > 0); 98 | } 99 | 100 | @Test(expected = YoutubeDLException.class) 101 | public void testFailGetNonExistentVideoInfo() throws YoutubeDLException { 102 | YoutubeDL.getVideoInfo(NONE_EXISTENT_VIDEO_URL); 103 | } 104 | } -------------------------------------------------------------------------------- /src/main/java/com/sapher/youtubedl/YoutubeDL.java: -------------------------------------------------------------------------------- 1 | package com.sapher.youtubedl; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sapher.youtubedl.mapper.VideoFormat; 5 | import com.sapher.youtubedl.mapper.VideoInfo; 6 | import com.sapher.youtubedl.mapper.VideoThumbnail; 7 | import com.sapher.youtubedl.utils.StreamGobbler; 8 | import com.sapher.youtubedl.utils.StreamProcessExtractor; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | *

Provide an interface for youtube-dl executable

18 | * 19 | *

20 | * For more information on youtube-dl, please see 21 | * YoutubeDL Documentation 22 | *

23 | */ 24 | public class YoutubeDL { 25 | 26 | /** 27 | * Youtube-dl executable name 28 | */ 29 | protected static String executablePath = "youtube-dl"; 30 | 31 | /** 32 | * Append executable name to command 33 | * @param command Command string 34 | * @return Command string 35 | */ 36 | protected static String buildCommand(String command) { 37 | return String.format("%s %s", executablePath, command); 38 | } 39 | 40 | /** 41 | * Execute youtube-dl request 42 | * @param request request object 43 | * @return response object 44 | * @throws YoutubeDLException 45 | */ 46 | public static YoutubeDLResponse execute(YoutubeDLRequest request) throws YoutubeDLException { 47 | return execute(request, null); 48 | } 49 | 50 | /** 51 | * Execute youtube-dl request 52 | * @param request request object 53 | * @param callback callback 54 | * @return response object 55 | * @throws YoutubeDLException 56 | */ 57 | public static YoutubeDLResponse execute(YoutubeDLRequest request, DownloadProgressCallback callback) throws YoutubeDLException { 58 | 59 | String command = buildCommand(request.buildOptions()); 60 | String directory = request.getDirectory(); 61 | Map options = request.getOption(); 62 | 63 | YoutubeDLResponse youtubeDLResponse; 64 | Process process; 65 | int exitCode; 66 | StringBuffer outBuffer = new StringBuffer(); //stdout 67 | StringBuffer errBuffer = new StringBuffer(); //stderr 68 | long startTime = System.nanoTime(); 69 | 70 | String[] split = command.split(" "); 71 | 72 | ProcessBuilder processBuilder = new ProcessBuilder(split); 73 | 74 | // Define directory if one is passed 75 | if(directory != null) 76 | processBuilder.directory(new File(directory)); 77 | 78 | try { 79 | process = processBuilder.start(); 80 | } catch (IOException e) { 81 | throw new YoutubeDLException(e); 82 | } 83 | 84 | InputStream outStream = process.getInputStream(); 85 | InputStream errStream = process.getErrorStream(); 86 | 87 | StreamProcessExtractor stdOutProcessor = new StreamProcessExtractor(outBuffer, outStream, callback); 88 | StreamGobbler stdErrProcessor = new StreamGobbler(errBuffer, errStream); 89 | 90 | try { 91 | stdOutProcessor.join(); 92 | stdErrProcessor.join(); 93 | exitCode = process.waitFor(); 94 | } catch (InterruptedException e) { 95 | 96 | // process exited for some reason 97 | throw new YoutubeDLException(e); 98 | } 99 | 100 | String out = outBuffer.toString(); 101 | String err = errBuffer.toString(); 102 | 103 | if(exitCode > 0) { 104 | throw new YoutubeDLException(err); 105 | } 106 | 107 | int elapsedTime = (int) ((System.nanoTime() - startTime) / 1000000); 108 | 109 | youtubeDLResponse = new YoutubeDLResponse(command, options, directory, exitCode , elapsedTime, out, err); 110 | 111 | return youtubeDLResponse; 112 | } 113 | 114 | 115 | /** 116 | * Get youtube-dl executable version 117 | * @return version string 118 | * @throws YoutubeDLException 119 | */ 120 | public static String getVersion() throws YoutubeDLException { 121 | YoutubeDLRequest request = new YoutubeDLRequest(); 122 | request.setOption("version"); 123 | return YoutubeDL.execute(request).getOut(); 124 | } 125 | 126 | /** 127 | * Retrieve all information available on a video 128 | * @param url Video url 129 | * @return Video info 130 | * @throws YoutubeDLException 131 | */ 132 | public static VideoInfo getVideoInfo(String url) throws YoutubeDLException { 133 | 134 | // Build request 135 | YoutubeDLRequest request = new YoutubeDLRequest(url); 136 | request.setOption("dump-json"); 137 | request.setOption("no-playlist"); 138 | YoutubeDLResponse response = YoutubeDL.execute(request); 139 | 140 | // Parse result 141 | ObjectMapper objectMapper = new ObjectMapper(); 142 | VideoInfo videoInfo; 143 | 144 | try { 145 | videoInfo = objectMapper.readValue(response.getOut(), VideoInfo.class); 146 | } catch (IOException e) { 147 | throw new YoutubeDLException("Unable to parse video information: " + e.getMessage()); 148 | } 149 | 150 | return videoInfo; 151 | } 152 | 153 | /** 154 | * List formats 155 | * @param url Video url 156 | * @return list of formats 157 | * @throws YoutubeDLException 158 | */ 159 | public static List getFormats(String url) throws YoutubeDLException { 160 | VideoInfo info = getVideoInfo(url); 161 | return info.formats; 162 | } 163 | 164 | /** 165 | * List thumbnails 166 | * @param url Video url 167 | * @return list of thumbnail 168 | * @throws YoutubeDLException 169 | */ 170 | public static List getThumbnails(String url) throws YoutubeDLException { 171 | VideoInfo info = getVideoInfo(url); 172 | return info.thumbnails; 173 | } 174 | 175 | /** 176 | * List categories 177 | * @param url Video url 178 | * @return list of category 179 | * @throws YoutubeDLException 180 | */ 181 | public static List getCategories(String url) throws YoutubeDLException { 182 | VideoInfo info = getVideoInfo(url); 183 | return info.categories; 184 | } 185 | 186 | /** 187 | * List tags 188 | * @param url Video url 189 | * @return list of tag 190 | * @throws YoutubeDLException 191 | */ 192 | public static List getTags(String url) throws YoutubeDLException { 193 | VideoInfo info = getVideoInfo(url); 194 | return info.tags; 195 | } 196 | 197 | /** 198 | * Get command executable or path to the executable 199 | * @return path string 200 | */ 201 | public static String getExecutablePath(){ 202 | return executablePath; 203 | } 204 | 205 | /** 206 | * Set path to use for the command 207 | * @param path String path to the executable 208 | */ 209 | public static void setExecutablePath(String path){ 210 | executablePath = path; 211 | } 212 | } 213 | --------------------------------------------------------------------------------