Java tutorial
/** * This file is part of ytd2 * * ytd2 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ytd2 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with ytd2. * If not, see <http://www.gnu.org/licenses/>. */ package Default; import java.io.BufferedReader; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.FileNotFoundException; import java.net.UnknownHostException; import java.util.Arrays; import java.util.HashMap; import java.util.Vector; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; /** * http://www.youtube.com/watch?v=Cx6eaVeYXOs 4K (Ultra HD) * http://www.youtube.com/watch?v=9QFK1cLhytY Javatar and .NOT * http://www.youtube.com/watch?v=Mt7zsortIXs 1080p "Lady Java" * http://www.youtube.com/watch?v=WowZLe95WDY Tom Petty And the Heartbreakers - Learning to Fly (with lyrics) * http://www.youtube.com/watch?v=86OfBExGSE0 URZ 720p * http://www.youtube.com/watch?v=cNOP2t9FObw Blade 360 - 480 * http://www.youtube.com/watch?v=HvQBrM_i8bU MZ 1000 Street Fighter * http://www.youtube.com/watch?v=5fB_wIP21_Q KTM 1190 Adventure vs. BMW R 1200GS 1080p (mp4, vorbis - audio) * http://www.youtube.com/watch?v=yVpbFMhOAwE How Linux is build * http://www.youtube.com/watch?v=4XpnKHJAok8 Tech Talk: Linus Torvalds on git * * http://www.youtube.com/watch?v=5nj77mJlzrc BF109 G In lovely memory of my grandpa, who used to fly around the clouds. * http://www.youtube.com/watch?v=I3lq1yQo8OY Showdown: Air Combat - Me-109 http://www.youtube.com/watch?v=yxXBhKJnRR8 * http://www.youtube.com/watch?v=RYXd60D_kgQ Me 262 Flys Again! * http://www.youtube.com/watch?v=6ejc9_yR5oQ Focke Wulf 190 attacks Boeing B 17 in 2009 at Hahnweide * * * https://www.youtube.com/watch?v=yNPECkESPbU Linkin Park feat. Jay-Z - Numb/Encore * https://www.youtube.com/watch?v=XLLuZi012ok The Notorious B.I.G. - Greatest Hits * https://www.youtube.com/watch?v=tAGnKpE4NCI Metallica - Nothing Else Matters [Official Music Video] * https://www.youtube.com/watch?v=JU_6A23NvUg Linkin Park Best Songs Ever * * * technobase.fm / We Are One! * * http://www.youtube.com/watch?v=ZoWB_IYoN60 "An unloaded weapon always shoots the loudest" Col. McQueen - Space 2063 * * ODOT http://sourceforge.net/p/ytd2/bugs/7/ http://www.youtube.com/watch?v=fRVVzXnRsUQ uses RTMPE (http://en.wikipedia.org/wiki/Protected_Streaming), which ytd2 cannot download atm * */ public class YTDownloadThread extends Thread { static int THREAD_COUNT = 0; private boolean noDownload; private int threadNumber = YTDownloadThread.THREAD_COUNT++; // every download thread get its own number private String url = null; // main URL (youtube start web page) private YoutubeUrl youtubeUrl = null; // mail URL (youtube start web page) as object private String title = null; // will be used as filename private String videoUrl = null; // one video web resource private Vector<YoutubeUrl> nextVideoUrl = new Vector<YoutubeUrl>(); // list of URLs from webpage source private String fileName = null; // contains the absolute filename //CookieStore bcs = null; // contains cookies after first HTTP GET private boolean isInterrupted = false; // basically the same as Thread.isInterrupted() private int recursionCount = -1; // counted in downloadone() for the 3 webrequest to one video private String contentType = null; private BufferedReader textReader = null; private BufferedInputStream binaryReader = null; private HttpGet request = null; private CloseableHttpClient httpClient = null; private RequestConfig config = null; private HttpHost proxy = null; private CloseableHttpResponse response = null; public static final String URL_REGEX = "(?i)^(http(s)?://)?[a-z0-9;/\\?@=&\\$\\-_\\.+!*\\'\\(\\),%]+$"; // without ":", plus "%" public YTDownloadThread() { super(); String sv = "thread started: ".concat(this.getMyName()); outputDebugMessage(sv); } public boolean downloadOne(String url) throws IOException { boolean rc = false; boolean rc204 = false; boolean rc302 = false; this.recursionCount++; // stop recursion try { if (url.equals("")) { return (false); } } catch (NullPointerException npe) { return (false); } // if (JFCMainClient.getQuitRequested()) { // return(false); // try to get information about application shutdown // } outputDebugMessage("start."); // TODO GUI option for proxy? // http://wiki.squid-cache.org/ConfigExamples/DynamicContent/YouTube // using local squid to save download time for tests try { this.request = new HttpGet(url); this.request.setConfig(this.config); this.httpClient = HttpClients.createDefault(); } catch (Exception e) { outputDebugMessage(e.getMessage()); } outputDebugMessage("executing request: ".concat(this.request.getRequestLine().toString())); outputDebugMessage("uri: ".concat(this.request.getURI().toString())); outputDebugMessage("using proxy: ".concat(this.getProxy())); try { this.response = this.httpClient.execute(this.request); } catch (ClientProtocolException cpe) { outputDebugMessage(cpe.getMessage()); } catch (UnknownHostException uhe) { output(("error connecting to: ").concat(uhe.getMessage())); outputDebugMessage(uhe.getMessage()); } catch (IOException ioe) { outputDebugMessage(ioe.getMessage()); } catch (IllegalStateException ise) { outputDebugMessage(ise.getMessage()); } try { outputDebugMessage("HTTP response status line:".concat(this.response.getStatusLine().toString())); // abort if HTTP response code is != 200, != 302 and !=204 - wrong URL? if (!(rc = this.response.getStatusLine().toString().toLowerCase().matches("^(http)(.*)200(.*)")) & !(rc204 = this.response.getStatusLine().toString().toLowerCase() .matches("^(http)(.*)204(.*)")) & !(rc302 = this.response.getStatusLine().toString().toLowerCase() .matches("^(http)(.*)302(.*)"))) { outputDebugMessage(this.response.getStatusLine().toString().concat(" ").concat(url)); output(this.response.getStatusLine().toString().concat(" \"").concat(this.title).concat("\"")); return (rc & rc204 & rc302); } if (rc204) { rc = downloadOne(this.nextVideoUrl.get(0).getUrl()); return (rc); } if (rc302) { outputDebugMessage( "location from HTTP Header: ".concat(this.response.getFirstHeader("Location").toString())); } } catch (NullPointerException npe) { // if an IllegalStateException was catched while calling httpclient.execute(httpget) a NPE is caught here because // response.getStatusLine() == null this.videoUrl = null; } HttpEntity entity = null; try { entity = this.response.getEntity(); } catch (NullPointerException npe) { //TODO catch must not be empty } // try to read HTTP response body if (entity != null) { try { if (this.response.getFirstHeader("Content-Type").getValue().toLowerCase() .matches("^text/html(.*)")) { this.textReader = new BufferedReader(new InputStreamReader(entity.getContent())); } else { this.binaryReader = new BufferedInputStream(entity.getContent()); } } catch (IllegalStateException e1) { e1.printStackTrace(); } catch (IOException e1) { e1.printStackTrace(); } try { // test if we got a webpage this.contentType = this.response.getFirstHeader("Content-Type").getValue().toLowerCase(); if (this.contentType.matches("^text/html(.*)")) { rc = saveTextData(); // test if we got the binary content } else if (this.contentType.matches("(audio|video)/(.*)|application/octet-stream")) { // add audio stream URL if necessary if (this.recursionCount == 1) { outputDebugMessage(("last response code==true - download: ") .concat(this.nextVideoUrl.get(0).getYoutubeId())); if (this.nextVideoUrl.get(0).getAudioStreamUrl().equals("")) { outputDebugMessage("download audio stream? no"); } else { // FIXME audio stream has no filename if we add the direct URL to the GUI url list - we should add YTURL objects, not Strings! outputDebugMessage("download audio stream? yes - " .concat(this.nextVideoUrl.get(0).getAudioStreamUrl())); this.youtubeUrl.setTitle(this.getTitle()); //JFCMainClient.addYoutubeUrlToList(this.nextVideoUrl.get(0).getAudioStreamUrl(),this.getTitle(),"AUDIO"); } } // if (JFCMainClient.getNoDownload()) // reportHeaderInfo(); // else saveBinaryData(); } else { // content-type is not video/ rc = false; this.videoUrl = null; } } catch (IOException ex) { try { throw ex; } catch (IOException e) { e.printStackTrace(); } } catch (RuntimeException ex) { try { throw ex; } catch (Exception e) { e.printStackTrace(); } } } //if (entity != null) this.httpClient.close(); outputDebugMessage("done: ".concat(url)); if (this.videoUrl == null) { this.videoUrl = ""; // to prevent NPE } if (this.videoUrl.matches(URL_REGEX)) { // enter recursion - download video resource outputDebugMessage("try to download video from URL: ".concat(this.videoUrl)); rc = downloadOne(this.videoUrl); } else { // no more recursion - html source hase been read // rc==false if video could not downloaded because of some error (like wrong protocol or country restriction) if (!rc) { outputDebugMessage("cannot download video - URL does not seem to be valid or could not be found: " .concat(this.url)); output("there was a problem getting the video URL! perhaps not allowed in your country?!"); output(("consider reporting the URL to author! - ").concat(this.url)); this.videoUrl = null; } } this.videoUrl = null; return (rc); } /* * generate Filename based on youtube video title and content type of MIME header */ public Vector<String> getFileNames(String title, String contentType) { String fileName = title; String fileNameAfterDot = contentType.replaceFirst("audio/|video/|application/", "").replaceAll("x-", ""); fileNameAfterDot = fileNameAfterDot.replaceFirst("octet-stream", "mp4"); fileNameAfterDot = fileNameAfterDot.replaceFirst("audio-stream", "mp3"); Vector<String> rc = new Vector<String>(); rc.add(fileName); rc.add(fileNameAfterDot); return rc; } private int addWEBM_ULTRAHD_Urls(int index, HashMap<String, String> sourceCodeVideoUrls) { int newIndex = index; if (true) { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("272"), this.url, "", sourceCodeVideoUrls.get("140"))); // webm < ultra HD size=3840x2160 type=video/webm;+codecs="vp9" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("271"), this.url, "", sourceCodeVideoUrls.get("140"))); // webm < ultra HD size=2560x1440 type=video/webm;+codecs="vp9 } else { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("271"), this.url, "", sourceCodeVideoUrls.get("140"))); // webm < ultra HD size=2560x1440 type=video/webm;+codecs="vp9 this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("212"), this.url, "", sourceCodeVideoUrls.get("140"))); // webm < ultra HD size=3840x2160 type=video/webm;+codecs="vp9" } return newIndex; } private int addMPEG_ULTRAHD_Urls(int index, HashMap<String, String> sourceCodeVideoUrls) { int newIndex = index; if (true) { //fmtUrlPair[0].equals( "266" )?"2160p mpeg, ": // <4k HD type=video/mp4;+codecs=%22avc1.640033" & size=3840x2160& this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("138"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg ultra HD size=4096x2304 type=video/mp4;+codecs="avc1.640033" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("266"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg ultra HD size=3840x2160 ype=video/mp4;+codecs=%22avc1.640033" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("264"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg ultra HD size=2560x1440 type=video/mp4;+codecs="avc1.640032" } else { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("264"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg ultra HD size=2560x1440 type=video/mp4;+codecs="avc1.640032" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("266"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg ultra HD size=3840x2160 ype=video/mp4;+codecs=%22avc1.640033" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("138"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg ultra HD size=4096x2304 type=video/mp4;+codecs="avc1.640033" } return newIndex; } private int addMPEG_HD_Urls(int index, HashMap<String, String> sourceCodeVideoUrls) { int newIndex = index; // try 3D HD first if 3D is selected if (true) this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("84"), this.url, "3D")); // mpeg 3D full HD // if SDS is on reverse order! - 720p before 1080p for HD and so on if (true) { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("137"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg full HD size=1920x1080 type=video/mp4;+codecs="avc1.640028" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("136"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg full HD size=1280x720 type=video/mp4;+codecs="avc1.4d401f" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("37"), this.url)); // mpeg full HD this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("22"), this.url)); // mpeg half HD quality=hd720 type=video/mp4;+codecs="avc1.64001F%2C+mp4a.40.2" } else { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("136"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg full HD size=1280x720 type=video/mp4;+codecs="avc1.4d401f" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("137"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg full HD size=1920x1080 type=video/mp4;+codecs="avc1.640028" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("22"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg half HD quality=hd720 type=video/mp4;+codecs="avc1.64001F%2C+mp4a.40.2" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("37"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg full HD size=1920x1080 type=video/mp4;+codecs="avc1.640028" } return newIndex; } private int addWBEM_HD_Urls(int index, HashMap<String, String> sourceCodeVideoUrls) { int newIndex = index; // try 3D HD first if 3D is selected if (false) this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("100"), this.url, "3D")); // webm 3D HD if (true) { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("248"), this.url, "", sourceCodeVideoUrls.get("171"))); // webm full HD size=1920x1080 type=video/webm;+codecs="vp9" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("247"), this.url, "", sourceCodeVideoUrls.get("171"))); // webm half HD size=1280x720 type=video/webm;+codecs="vp9" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("46"), this.url)); // webm full HD this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("45"), this.url)); // webm half HD } else { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("46"), this.url)); // webm full HD this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("45"), this.url)); // webm half HD this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("247"), this.url, "", sourceCodeVideoUrls.get("171"))); // webm half HD size=1280x720 type=video/webm;+codecs="vp9" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("248"), this.url, "", sourceCodeVideoUrls.get("171"))); // webm full HD size=1920x1080 type=video/webm;+codecs="vp9" } return newIndex; } private int addWBEM_SD_Urls(int index, HashMap<String, String> sourceCodeVideoUrls) { int newIndex = index; // try 3D first if 3D is selected if (false) this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("102"), this.url, "3D")); // webm 3D SD if (true) { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("244"), this.url, "", sourceCodeVideoUrls.get("171"))); // webm SD size=854x480 type=video/webm;+codecs="vp9" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("243"), this.url, "", sourceCodeVideoUrls.get("171"))); // webm SD size=640x360 type=video/webm;+codecs="vp9" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("44"), this.url)); // webm SD this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("43"), this.url)); // webm SD quality=medium type=video/webm;+codecs="vp8.0%2C+vorbis" } else { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("43"), this.url)); // webm SD quality=medium type=video/webm;+codecs="vp8.0%2C+vorbis" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("44"), this.url)); // webm SD this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("243"), this.url, "", sourceCodeVideoUrls.get("140"))); // webm SD size=640x360 type=video/webm;+codecs="vp9" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("244"), this.url, "", sourceCodeVideoUrls.get("140"))); // webm SD size=854x480 type=video/webm;+codecs="vp9" } return newIndex; } private int addFLV_SD_Urls(int index, HashMap<String, String> sourceCodeVideoUrls) { int newIndex = index; if (true) { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("35"), this.url)); // flv SD this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("34"), this.url)); // flv SD } else { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("34"), this.url)); // flv SD this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("35"), this.url)); // flv SD } return newIndex; } private int addMPEG_SD_Urls(int index, HashMap<String, String> sourceCodeVideoUrls) { int newIndex = index; // try 3D first if "3D" is selected if (false) this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("82"), this.url, "3D")); // mpeg 3D SD if (true) { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("135"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg SD size=854x480 type=video/mp4;+codecs="avc1.4d401e" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("134"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg SD size=640x360 type=video/mp4;+codecs="avc1.4d401e" } else { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("134"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg SD size=640x360 type=video/mp4;+codecs="avc1.4d401e" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("135"), this.url, "", sourceCodeVideoUrls.get("140"))); // mpeg SD size=854x480 type=video/mp4;+codecs="avc1.4d401e" } this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("18"), this.url)); // mpeg SD quality=medium type=video/mp4;+codecs="avc1.42001E%2C+mp4a.40.2" return newIndex; } private int addMPEG_LD_Urls(int index, HashMap<String, String> sourceCodeVideoUrls) { int newIndex = index; if (true) { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("133"), this.url, "LD", sourceCodeVideoUrls.get("140"))); // mpeg LD size=426x240 type=video/mp4;+codecs="avc1.4d4015" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("160"), this.url, "LD", sourceCodeVideoUrls.get("140"))); // mpeg LD size=256x144 type=video/mp4;+codecs="avc1.4d400c" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("36"), this.url, "LD")); // mpeg LD quality=small type=video/3gpp;+codecs="mp4v.20.3%2C+mp4a.40.2" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("17"), this.url, "LD")); // mpeg LD quality=small type=video/3gpp;+codecs="mp4v.20.3%2C+mp4a.40.2" } else { this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("17"), this.url, "LD")); // mpeg LD quality=small type=video/3gpp;+codecs="mp4v.20.3%2C+mp4a.40.2" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("36"), this.url, "LD")); // mpeg LD quality=small type=video/3gpp;+codecs="mp4v.20.3%2C+mp4a.40.2" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("160"), this.url, "LD", sourceCodeVideoUrls.get("140"))); // mpeg LD size=256x144 type=video/mp4;+codecs="avc1.4d400c" this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("133"), this.url, "LD", sourceCodeVideoUrls.get("140"))); // mpeg LD size=426x240 type=video/mp4;+codecs="avc1.4d4015" } return newIndex; } private int addFLV_LD_Urls(int index, HashMap<String, String> sourceCodeVideoUrls) { int newIndex = index; this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("5"), this.url, "LD")); // flv LD quality=small type=video/x-flv return newIndex; } private int addWEBM_LD_Urls(int index, HashMap<String, String> sourceCodeVideoUrls) { int newIndex = index; this.nextVideoUrl.add(newIndex++, new YoutubeUrl(sourceCodeVideoUrls.get("242"), this.url, "LD", sourceCodeVideoUrls.get("171"))); // flv LD size=426x240 type=video/webm;+codecs="vp9" return newIndex; } /** * put URL parts between "&" in a sorted array and add "range=0-1000000000" * * @param source * @param delimiter * @return */ private String sortStringAt(String source, String delimiter) { String sortedUrl = source.replaceFirst("\\?.*", ""); // http://... // FIXME range=0-999999999 lead to a maximum fraction of 1GB (not GiB !), but if the file is greater than 1GB we would have to download the whole video in more smaller parts (like the ytplayer does) // http://www.youtube.com/watch?v=x9WxkcUTGSY 4:14:33 String[] unsortedUrl = source.replaceFirst("http.*\\?", "").concat("&range=0-999999999").split(delimiter); Arrays.sort(unsortedUrl); for (int i = 0; i < unsortedUrl.length - 1; i++) { if (unsortedUrl[i].equals(unsortedUrl[i + 1])) unsortedUrl[i] = ""; } sortedUrl += Arrays.toString(unsortedUrl); sortedUrl = sortedUrl.replaceAll("\\[, ", delimiter).replaceAll(", ", delimiter).replaceAll(",,", delimiter) .replaceAll("]", "").replaceAll(delimiter + delimiter, delimiter); sortedUrl = sortedUrl.replaceFirst("/videoplayback\\[", "/videoplayback?").replaceFirst("/videoplayback&", "/videoplayback?"); return sortedUrl; } private boolean saveTextData() throws IOException { boolean rc = false; // read html lines one by one and search for java script array of video URLs String line = ""; String line0 = ""; String line1 = ""; while (line != null) { line = this.textReader.readLine(); try { // FIXME audio is missing .. video and audio are in separated streams in adaptive_fmts if (this.recursionCount == 0 && line.matches(".*(\"adaptive_fmts\":|\"url_encoded_fmt_stream_map\":).*")) { // behind that two strings are the comma separated video URLs we use rc = true; HashMap<String, String> sourceCodeVideoUrls = new HashMap<String, String>(); line = line.replaceAll(" ", ""); line = line.replace("%25", "%"); line = line.replace("\\u0026", "&"); if (line.contains("\"url_encoded_fmt_stream_map\":\"")) { line1 = StringUtils.substringBetween(line, "\"url_encoded_fmt_stream_map\":\"", "\""); } line0 = ""; if (line.contains("\"adaptive_fmts\":\"")) { line0 = StringUtils.substringBetween(line, "\"adaptive_fmts\":\"", "\""); } if (line0 == null) { line0 = ""; } if (line1 == null) { line1 = ""; } line = line0 + "," + line1; outputDebugMessage( String.format("length sline0 sline1: %d %d", line0.length(), line1.length())); outputDebugMessage(String.format("sline0 (adaptive fmt) %s \n sline1 (url_encoded fmt): %s", line0, line1)); // by anonymous String[] sourceCodeYoutubeUrls = line.split(","); outputDebugMessage( "ssourcecodeuturls.length: ".concat(Integer.toString(sourceCodeYoutubeUrls.length))); String resolutions = "found video URL for resolution: "; for (String url : sourceCodeYoutubeUrls) { // assuming rtmpe is used for all resolutions, if found once - end download if (url.matches(".*conn=rtmpe.*")) { outputDebugMessage("RTMPE found. cannot download this one!"); output("Unable to download video due to unsupported protocol (RTMPE). sry!"); break; } String[] fmtUrlPair = url.split("url=http(s)?", 2); fmtUrlPair[1] = "url=http" + fmtUrlPair[1] + "&" + fmtUrlPair[0]; // grep itag=xz out and use xy as hash key // 2013-02 itag now has up to 3 digits fmtUrlPair[0] = fmtUrlPair[1].substring(fmtUrlPair[1].indexOf("itag=") + 5, fmtUrlPair[1].indexOf("itag=") + 5 + 1 + (fmtUrlPair[1].matches(".*itag=[0-9]{2}.*") ? 1 : 0) + (fmtUrlPair[1].matches(".*itag=[0-9]{3}.*") ? 1 : 0)); if (this.request.getURI().toString().startsWith("https")) { fmtUrlPair[1] = fmtUrlPair[1].replaceFirst("url=http%3A%2F%2F", "https://"); // webpage source code only provides http urls if accessed via wget or ytd2 - the browser does something unknown so google sends back httpS urls within source code! } else { fmtUrlPair[1] = fmtUrlPair[1].replaceFirst("url=http%3A%2F%2F", "http://"); } fmtUrlPair[1] = fmtUrlPair[1].replaceAll("%3F", "?").replaceAll("%2F", "/") .replaceAll("%3B", ";")/*.replaceAll("%2C",",")*/.replaceAll("%3D", "=") .replaceAll("%26", "&").replaceAll("%252C", "%2C").replaceAll("sig=", "signature=") .replaceAll("&s=", "&signature=").replaceAll("\\?s=", "?signature="); // remove duplicate parts between & String sortedUrl = this.sortStringAt(fmtUrlPair[1], "&"); outputDebugMessage(String.format("video tag: %s url: %s", fmtUrlPair[0], sortedUrl)); // fmtUrlPair[1] -> ssortedURL fmtUrlPair[1] = sortedUrl; try { sourceCodeVideoUrls.put(fmtUrlPair[0], fmtUrlPair[1]); // save that URL //debugoutput(String.format( "video url saved with key %s: %s",fmtUrlPair[0],ssourcecodevideourls.get(fmtUrlPair[0]) )); resolutions = resolutions.concat(fmtUrlPair[0].equals("138") ? "2304p mpeg, " : // 4k HD type=video/mp4;+codecs="avc1.640033" & size=4096x2304 fmtUrlPair[0].equals("264") ? "1440p mpeg, " : // <4k HD type=video/mp4;+codecs="avc1.640032" & size=2560x1440 fmtUrlPair[0].equals("266") ? "2160p mpeg, " : // <4k HD type=video/mp4;+codecs=%22avc1.640033" & size=3840x2160& fmtUrlPair[0].equals("271") ? "1440p webm, " : // <4k HD type=video/webm;+codecs=%22vp9" & size=2560x1440 fmtUrlPair[0].equals("272") ? "2160p webm, " : // <4k HD type=video/webm;+codecs="vp9" & size=3840x2160 fmtUrlPair[0].equals("248") ? "1080p mpeg, " : // HD type=video/webm;+codecs="vp9" & size=1920x1080 fmtUrlPair[0].equals("37") ? "1080p mpeg, " : // HD type=video/mp4;+codecs="avc1.64001F,+mp4a.40.2" fmtUrlPair[0].equals("22") ? "720p mpeg, " : // HD type=video/mp4;+codecs="avc1.64001F,+mp4a.40.2" fmtUrlPair[0].equals( "247") ? "1080p mpeg, " : // HD type=video/webm;+codecs="vp9" & size=1280x720 fmtUrlPair[0] .equals("84") ? "1080p 3d mpeg, " : // HD 3D type=video/mp4;+codecs="avc1.64001F,+mp4a.40.2" fmtUrlPair[0] .equals("18") ? "360p mpeg, " : // SD type=video/mp4;+codecs="avc1.42001E,+mp4a.40.2" fmtUrlPair[0] .equals("35") ? "480p flv, " : // SD type=video/x-flv fmtUrlPair[0] .equals("34") ? "360p flv, " : // SD type=video/x-flv fmtUrlPair[0] .equals("82") ? "360p 3d mpeg, " : // SD 3D type=video/mp4;+codecs="avc1.42001E,+mp4a.40.2" fmtUrlPair[0] .equals("36") ? "240p mpeg 3gpp, " : // LD type=video/3gpp;+codecs="mp4v.20.3,+mp4a.40.2" fmtUrlPair[0] .equals("17") ? "114p mpeg 3gpp, " : // LD type=video/3gpp;+codecs="mp4v.20.3,+mp4a.40.2" fmtUrlPair[0] .equals("46") ? "1080p webm, " : // HD type=video/webm;+codecs="vp8.0,+vorbis" fmtUrlPair[0] .equals("45") ? "720p webm, " : // HD type=video/webm;+codecs="vp8.0,+vorbis" fmtUrlPair[0] .equals("100") ? "1080p 3d webm, " : // HD 3D type=video/webm;+codecs="vp8.0,+vorbis" fmtUrlPair[0] .equals("44") ? "480p webm, " : // SD type=video/webm;+codecs="vp8.0,+vorbis" fmtUrlPair[0] .equals("43") ? "360p webm, " : // SD type=video/webm;+codecs="vp8.0,+vorbis" fmtUrlPair[0] .equals("102") ? "360p 3d webm, " : // SD 3D type=video/webm;+codecs="vp8.0,+vorbis" fmtUrlPair[0] .equals("244") ? "480p webm, " : // SD type=video/webm;+codecs="vp9" & size=854x480 fmtUrlPair[0] .equals("5") ? "240p flv, " : // LD type=video/x-flv fmtUrlPair[0] .equals("137") ? "1080p mpeg, " : // HD type=video/mp4;+codecs="avc1.640028" & size=1920x1080 fmtUrlPair[0] .equals("136") ? "720p mpeg, " : // HD type=video/mp4;+codecs="avc1.4d401f" & size=1280x720 fmtUrlPair[0] .equals("135") ? "480p mpeg, " : // SD type=video/mp4;+codecs="avc1.4d401f" & size=854x480 fmtUrlPair[0] .equals("134") ? "360p mpeg, " : // SD type=video/mp4;+codecs="avc1.4d401e" & size=640x360 fmtUrlPair[0] .equals("133") ? "240p mpeg, " : // LD type=video/mp4;+codecs="avc1.4d4015" & size=426x240 fmtUrlPair[0] .equals("160") ? "144p mpeg, " : // LD type=video/mp4;+codecs="avc1.42c00c" & size=256x144 fmtUrlPair[0] .equals("243") ? "360p webm, " : // LD type=video/webm;+codecs="vp9" fmtUrlPair[0] .equals("242") ? "240p webm, " : // LD type=video/webm;+codecs="vp9" fmtUrlPair[0] .equals("140") ? "mpeg audio only, " : // ?? type=audio/mp4;+codecs="mp4a.40.2 & bitrate=127949 fmtUrlPair[0] .equals("171") ? "ogg vorbis audio only, " : // ?? audio/webm;+codecs="vorbis" & bitrate=127949 "unknown resolution! (" .concat(fmtUrlPair[0]) .concat(") ")); } catch (java.lang.ArrayIndexOutOfBoundsException aioobe) { //TODO catch must not be empty } } if (true) { output(resolutions); } outputDebugMessage(resolutions); // TODO move scope into the if blocks int index = 0; this.nextVideoUrl.removeAllElements(); outputDebugMessage( "ssourcecodevideourls.length: ".concat(Integer.toString(sourceCodeVideoUrls.size()))); // figure out what resolution-button is pressed right now (!!) and fill list with possible URLs of video currently being processed // try AUDIO ONLY setting before any video resolution if (false) { if (false) { this.nextVideoUrl.add(index++, new YoutubeUrl(sourceCodeVideoUrls.get("140"), this.url, "AUDIO")); // MP4 AUDIO ONLY mime=audio/mp4 type=audio/mp4;+codecs=%22mp4a.40.2%22 this.nextVideoUrl.add(index++, new YoutubeUrl(sourceCodeVideoUrls.get("171"), this.url, "AUDIO")); // WEBM AUDIO ONLY mime=audio/webm type=audio/webm;+codecs=%22vorbis%22 } else { this.nextVideoUrl.add(index++, new YoutubeUrl(sourceCodeVideoUrls.get("171"), this.url, "AUDIO")); // WEBM AUDIO ONLY mime=audio/webm type=audio/webm;+codecs=%22vorbis%22 this.nextVideoUrl.add(index++, new YoutubeUrl(sourceCodeVideoUrls.get("140"), this.url, "AUDIO")); // MP4 AUDIO ONLY mime=audio/mp4 type=audio/mp4;+codecs=%22mp4a.40.2%22 } } else { switch (2) { case 8: // ULTRA HD // try 1440p, 2160p and 2304p first, if selected in GUI if (true) { index = addMPEG_ULTRAHD_Urls(index, sourceCodeVideoUrls); } if (false) { index = addWEBM_ULTRAHD_Urls(index, sourceCodeVideoUrls); } //$FALL-THROUGH$ case 4: // HD // try 1080p/720p in selected format first. if it's not available than the other format will be used if (true) { index = addMPEG_HD_Urls(index, sourceCodeVideoUrls); } if (false) { index = addWBEM_HD_Urls(index, sourceCodeVideoUrls); } // there are no FLV HD URLs for now, so at least try mpg, webm HD then index = addMPEG_HD_Urls(index, sourceCodeVideoUrls); index = addWBEM_HD_Urls(index, sourceCodeVideoUrls); //$FALL-THROUGH$ case 2: // SD // try to download desired format first, if it's not available we take the other of same res if (true) { index = addMPEG_SD_Urls(index, sourceCodeVideoUrls); } if (false) { index = addWBEM_SD_Urls(index, sourceCodeVideoUrls); } if (false) { index = addFLV_SD_Urls(index, sourceCodeVideoUrls); } index = addMPEG_SD_Urls(index, sourceCodeVideoUrls); index = addWBEM_SD_Urls(index, sourceCodeVideoUrls); index = addFLV_SD_Urls(index, sourceCodeVideoUrls); //$FALL-THROUGH$ case 1: // LD if (true) { index = addMPEG_LD_Urls(index, sourceCodeVideoUrls); } if (false) { index = addWEBM_LD_Urls(index, sourceCodeVideoUrls); } if (false) { index = addFLV_LD_Urls(index, sourceCodeVideoUrls); } // we must ensure all (16) possible URLs get added so the list of URLs is never empty index = addMPEG_LD_Urls(index, sourceCodeVideoUrls); index = addWEBM_LD_Urls(index, sourceCodeVideoUrls); index = addFLV_LD_Urls(index, sourceCodeVideoUrls); break; default: //this.vNextVideoURL = null; this.videoUrl = null; break; } } // if the first 2 entries are null than there are no URLs for the selected resolution // strictly speaking this is only true for HD as there are only two URLs in contrast to three of SD - in this case the output will not be shown but downloading should work anyway // 2014-01-25 since there is another block of video urls (including one for audio only) and a new list of itags, we check only the first entry try { if (this.nextVideoUrl.get(0).getUrl() == null) { String smsg = "could not find video url for selected resolution! trying lower res..."; output(smsg); outputDebugMessage(smsg); } } catch (java.lang.ArrayIndexOutOfBoundsException aioob) { String smsg = "could not find video url for selected resolution! trying lower res..."; output(smsg); outputDebugMessage(smsg); } // remove null entries in list - we later try to download the first (index 0) and if it fails the next one (at index 1) and so on for (int x = this.nextVideoUrl.size() - 1; x >= 0; x--) { if (this.nextVideoUrl.get(x).getUrl() == null) { this.nextVideoUrl.remove(x); } } try { this.videoUrl = this.nextVideoUrl.get(0).getUrl(); outputDebugMessage(String.format("trying this one: %s %s %s %s", this.nextVideoUrl.get(0).getHtmlTagId(), this.nextVideoUrl.get(0).getQuality(), this.nextVideoUrl.get(0).getSize(), this.nextVideoUrl.get(0).getHtmlType())); } catch (ArrayIndexOutOfBoundsException aioobe) { //TODO catch must not be empty } this.setTitle(this.getTitle().concat("Video") .concat(!this.nextVideoUrl.get(0).getRespart().equals("") ? "." + this.nextVideoUrl.get(0).getRespart() : "")); // java.lang.ArrayIndexOutOfBoundsException would be thrown here if no known resolution was found until here } if (this.recursionCount == 0 && line.matches("(.*)<meta name=\"title\" content=(.*)")) { String stmp = line.replaceFirst("(.*)<meta name=\"title\" content=", "").trim(); // change html characters to their UTF8 counterpart stmp = UTF8.changeHTMLtoUTF8(stmp); stmp = stmp.replaceFirst("^\"", "").replaceFirst("\">$", ""); // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx // stmp = stmp.replaceAll("<", ""); stmp = stmp.replaceAll(">", ""); stmp = stmp.replaceAll(":", ""); stmp = stmp.replaceAll("/", " "); stmp = stmp.replaceAll("\\\\", " "); stmp = stmp.replaceAll("|", ""); stmp = stmp.replaceAll("\\?", ""); stmp = stmp.replaceAll("\\*", ""); stmp = stmp.replaceAll("/", " "); stmp = stmp.replaceAll("\"", " "); stmp = stmp.replaceAll("%", ""); this.setTitle(stmp); // complete file name without path } } catch (NullPointerException npe) { } } return rc; } private void saveBinaryData() throws IOException { FileOutputStream fos = null; try { File file; Integer idUpCount = 0; String directoryChoosed; directoryChoosed = (String) "/home/simpleman/Videos"; //JFCMainClient.CONFIG.getProperty("savefolder"); outputDebugMessage("title: ".concat(this.getTitle()).concat("sfilename: ").concat(this.getTitle())); do { Vector<String> fileNames = getFileNames(this.getTitle(), this.response.getFirstHeader("Content-Type").getValue()); file = new File(directoryChoosed, fileNames.get(0).concat((idUpCount > 0 ? "(".concat(idUpCount.toString()).concat(")") : "")) .concat(".").concat(fileNames.get(1))); idUpCount += 1; } while (file.exists()); this.setFileName(file.getAbsolutePath()); Long bytesReadSum = (long) 0; Long percentage = (long) -1; Long bytesMax = Long.parseLong(this.response.getFirstHeader("Content-Length").getValue()); fos = new FileOutputStream(file); outputDebugMessage(String.format("writing %d bytes to: %s", bytesMax, this.getFileName())); output(("file size of \"").concat(this.getTitle()).concat("\" = ").concat(bytesMax.toString()) .concat(" Bytes").concat(" ~ ").concat(Long.toString((bytesMax / 1024)).concat(" KiB")) .concat(" ~ ").concat(Long.toString((bytesMax / 1024 / 1024)).concat(" MiB"))); byte[] bytes = new byte[4096]; Integer bytesRead = 1; // adjust blocks of percentage to output - larger files are shown with smaller pieces Integer blocks = 10; if (bytesMax > 20 * 1024 * 1024) blocks = 4; if (bytesMax > 32 * 1024 * 1024) blocks = 2; if (bytesMax > 56 * 1024 * 1024) blocks = 1; while (!this.isInterrupted && bytesRead > 0) { bytesRead = this.binaryReader.read(bytes); bytesReadSum += bytesRead; // update percentage of download if ((((bytesReadSum * 100 / bytesMax) / blocks) * blocks) > percentage) { percentage = (((bytesReadSum * 100 / bytesMax) / blocks) * blocks); //this.youtubeUrl.setPercentage(percentage.intValue()); //JFCMainClient.updateYoutubeUrlInList(this.youtubeUrl); } // TODO calculate and show ETA for bigger downloads (remaining time > 60s) - every 20%? try { fos.write(bytes, 0, bytesRead); } catch (IndexOutOfBoundsException ioob) { } this.isInterrupted = false; // try to get information about application shutdown } // this.youtubeUrl.setDownloadingFinished(); // JFCMainClient.updateYoutubeUrlInList(this.youtubeUrl); // rename files if download was interrupted before completion of download if (this.isInterrupted && bytesReadSum < bytesMax) { try { // this part is especially for our M$-Windows users because of the different behavior of File.renameTo() in contrast to non-windows // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6213298 and others // even with Java 1.6.0_22 the renameTo() does not work directly on M$-Windows! fos.close(); } catch (Exception e) { } // System.gc(); // we don't have to do this but to be sure the file handle gets released we do a thread sleep try { Thread.sleep(50); } catch (InterruptedException e) { } // this part runs on *ix platforms without closing the FileOutputStream explicitly this.httpClient.close(); // otherwise binaryreader.close() would cause the entire datastream to be transmitted outputDebugMessage(String.format("download canceled. (%d)", (bytesRead))); changeFileNamewith("CANCELED."); String message = ("renaming unfinished file to: ").concat(this.getFileName()); output(message); outputDebugMessage(message); // CANCELED filenames overwrite others as we do not test for CANCELED one, two... if (!file.renameTo(new File(this.getFileName()))) { message = ("error renaming unfinished file to: ").concat(this.getFileName()); output(message); outputDebugMessage(message); } } outputDebugMessage("done writing."); } catch (FileNotFoundException fnfe) { throw (fnfe); } catch (IOException ioe) { outputDebugMessage("IOException"); throw (ioe); } finally { this.videoUrl = null; try { fos.close(); } catch (Exception e) { } try { this.textReader.close(); } catch (Exception e) { } try { this.binaryReader.close(); } catch (Exception e) { } } } private void changeFileNamewith(String string) { File file = null; Integer idUpCount = 0; String fileSeparator = System.getProperty("file.separator"); if (fileSeparator.equals("\\")) { fileSeparator += fileSeparator; // on m$-windows we need to escape the \ } String directoryChoosed = ""; String[] renFileName = this.getFileName().split(fileSeparator); try { for (int i = 0; i < renFileName.length - 1; i++) { directoryChoosed += renFileName[i].concat((i < renFileName.length - 1) ? fileSeparator : ""); // constructing folder where file is saved now (could be changed in GUI already) } } catch (ArrayIndexOutOfBoundsException aioobe) { } String fileName = renFileName[renFileName.length - 1]; outputDebugMessage("changeFileNamewith() sfilename: ".concat(fileName)); do { // filename will be prepended with a parameter string and possibly with a duplicate counter file = new File(directoryChoosed, string .concat((idUpCount > 0 ? "(".concat(idUpCount.toString()).concat(")") : "")).concat(fileName)); idUpCount += 1; } while (file.exists()); outputDebugMessage("changeFileNamewith() new filename: ".concat(file.getAbsolutePath())); this.setFileName(file.getAbsolutePath()); } public String getProxy() { String sproxy = null; if (sproxy == null) return (""); else return (sproxy); } public String getTitle() { if (this.title != null) return this.title; else return (""); } public void setTitle(String sTitle) { this.title = sTitle; } public String getFileName() { if (this.fileName != null) { return this.fileName; } else { return (""); } } public void setFileName(String sFileName) { this.fileName = sFileName; } private synchronized void outputDebugMessage(String s) { System.out.println(s); if (true) { return; } // sometimes this happens: Exception in thread "Thread-2" java.lang.Error: Interrupted attempt to aquire write lock (on quit only) try { // JFCMainClient.addTextToConsole("#DEBUG ".concat(this.getMyName()).concat(" ").concat(s)); } catch (Exception e) { try { Thread.sleep(50); } catch (InterruptedException e1) { //TODO catch must not be empty } try { // JFCMainClient.addTextToConsole("#DEBUG ".concat(this.getMyName()).concat(" ").concat(s)); } catch (Exception e2) { //TODO catch must not be empty } } } private void output(String s) { System.out.println(s); if (false) { return; } // JFCMainClient.addTextToConsole("#info - ".concat(s)); } public String getMyName() { return this.getClass().getName().concat(Integer.toString(this.threadNumber)); } /* public void run() { boolean downloadOK = false; while (!this.isInterrupted) { try { synchronized (JFCMainClient.DLM) { // debugoutput("going to sleep."); JFCMainClient.DLM.wait(1000); // check for new URLs (if they got pasted faster than threads removing them) // debugoutput("woke up ".concat(this.getClass().getName())); this.isInterrupted = JFCMainClient.getQuitRequested(); // if quit was pressed while this threads works it would not get the InterruptedException and therefore prevent application shutdown // debugoutput("URLs remain in list: ".concat(Integer.toString(JFCMainClient.dlm.size()))); // running in CLI mode? if (JFCMainClient.FRAME == null) { if (JFCMainClient.DLM.size() == 0) { outputDebugMessage(this.getMyName().concat(" ran out of work.")); if (YTDownloadThread.THREAD_COUNT == 0) { // this is the last DownloadThread so shutdown Application as well outputDebugMessage("all DownloadThreads ended. shuting down ytd2."); JFCMainClient.shutdownApp(); } else { // this is not the last DownloadThread so shutdown this thread only this.isInterrupted = true; outputDebugMessage("end this thread."); throw new NullPointerException("end this thread."); } } } this.youtubeUrl = JFCMainClient.getFirstYoutubeUrlFromList(); this.url = this.youtubeUrl.toString(); output((JFCMainClient.isGerman()?"versuche herunterzuladen: ":"try to download: ").concat(this.url)); } this.youtubeUrl.setDownloading(); JFCMainClient.updateYoutubeUrlInList(this.youtubeUrl); this.noDownload = JFCMainClient.getNoDownload(); // copy ndl-state because this thread should end with a complete file (and report so) even if someone switches to no-download before this thread is finished // download one webresource and show result downloadOK = downloadOne(this.youtubeUrl.getUrl()); this.recursionCount=-1; if (downloadOK && !this.noDownload) output((JFCMainClient.isGerman()?"fertig heruntergeladen: ":"download complete: ").concat("\"").concat(this.getTitle()).concat("\"").concat(" to ").concat(this.getFileName())); else output((JFCMainClient.isGerman()?"Nicht heruntergeladen: ":"not downloaded: ").concat("\"").concat(this.getTitle()).concat("\"")); // not downloaded does not mean it was erroneous // running in CLI mode? if (JFCMainClient.FRAME == null) JFCMainClient.removeYoutubeUrlFromList(this.youtubeUrl); // JFCMainClient.removeURLFromList(this.sURL); else JFCMainClient.removeYoutubeUrlFromList(this.youtubeUrl); // JFCMainClient.removeURLFromList(JFCMainClient.szDLSTATE.concat(this.sURL)); } catch (InterruptedException e) { this.isInterrupted = true; } catch (NullPointerException npe) { //TODO catch must not be empty - debugoutput("npe - nothing to download?"); } catch (Exception e) { e.printStackTrace(); } } outputDebugMessage("thread ended: ".concat(this.getMyName())); YTDownloadThread.THREAD_COUNT--; } */ }