编辑3
您可以使用Lib:https :
//github.com/HaarigerHarald/android-
youtubeExtractor
例如:
String youtubelink = "http://youtube.com/watch?v=xxxx";new YouTubeExtractor(this) {@Overridepublic void onExtractionComplete(SparseArray<YtFile> ytFiles, Videometa vmeta) { if (ytFiles != null) { int itag = 22; String downloadUrl = ytFiles.get(itag).getUrl(); }}}.extract(youtubelink, true, true);他们使用以下方法解密Signature:
private boolean decipherSignature(final SparseArray<String> encSignatures) throws IOException { // Assume the functions don't change that much if (decipherFunctionName == null || decipherFunctions == null) { String decipherFunctUrl = "https://s.ytimg.com/yts/jsbin/" + decipherJsFileName; BufferedReader reader = null; String javascriptFile; URL url = new URL(decipherFunctUrl); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestProperty("User-Agent", USER_AGENT); try { reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); StringBuilder sb = new StringBuilder(""); String line; while ((line = reader.readLine()) != null) { sb.append(line); sb.append(" "); } javascriptFile = sb.toString(); } finally { if (reader != null) reader.close(); urlConnection.disconnect(); } if (LOGGING) Log.d(LOG_TAG, "Decipher FunctURL: " + decipherFunctUrl); Matcher mat = patSignatureDecFunction.matcher(javascriptFile); if (mat.find()) { decipherFunctionName = mat.group(1); if (LOGGING) Log.d(LOG_TAG, "Decipher Functname: " + decipherFunctionName); Pattern patMainVariable = Pattern.compile("(var |\s|,|;)" + decipherFunctionName.replace("$", "\$") + "(=function\((.{1,3})\)\{)"); String mainDecipherFunct; mat = patMainVariable.matcher(javascriptFile); if (mat.find()) { mainDecipherFunct = "var " + decipherFunctionName + mat.group(2); } else { Pattern patMainFunction = Pattern.compile("function " + decipherFunctionName.replace("$", "\$") + "(\((.{1,3})\)\{)"); mat = patMainFunction.matcher(javascriptFile); if (!mat.find()) return false; mainDecipherFunct = "function " + decipherFunctionName + mat.group(2); } int startIndex = mat.end(); for (int braces = 1, i = startIndex; i < javascriptFile.length(); i++) { if (braces == 0 && startIndex + 5 < i) { mainDecipherFunct += javascriptFile.substring(startIndex, i) + ";"; break; } if (javascriptFile.charAt(i) == '{') braces++; else if (javascriptFile.charAt(i) == '}') braces--; } decipherFunctions = mainDecipherFunct; // Search the main function for extra functions and variables // needed for deciphering // Search for variables mat = patVariableFunction.matcher(mainDecipherFunct); while (mat.find()) { String variableDef = "var " + mat.group(2) + "={"; if (decipherFunctions.contains(variableDef)) { continue; } startIndex = javascriptFile.indexOf(variableDef) + variableDef.length(); for (int braces = 1, i = startIndex; i < javascriptFile.length(); i++) { if (braces == 0) { decipherFunctions += variableDef + javascriptFile.substring(startIndex, i) + ";"; break; } if (javascriptFile.charAt(i) == '{') braces++; else if (javascriptFile.charAt(i) == '}') braces--; } } // Search for functions mat = patFunction.matcher(mainDecipherFunct); while (mat.find()) { String functionDef = "function " + mat.group(2) + "("; if (decipherFunctions.contains(functionDef)) { continue; } startIndex = javascriptFile.indexOf(functionDef) + functionDef.length(); for (int braces = 0, i = startIndex; i < javascriptFile.length(); i++) { if (braces == 0 && startIndex + 5 < i) { decipherFunctions += functionDef + javascriptFile.substring(startIndex, i) + ";"; break; } if (javascriptFile.charAt(i) == '{') braces++; else if (javascriptFile.charAt(i) == '}') braces--; } } if (LOGGING) Log.d(LOG_TAG, "Decipher Function: " + decipherFunctions); decipherViaWebView(encSignatures); if (CACHING) { writeDeciperFunctToChache(); } } else { return false; } } else { decipherViaWebView(encSignatures); } return true;}现在,使用此库, 高质量视频会
丢失音频,因此我将MediaMuxer用于
MurgingAudio视频,并将视频用于最终输出
编辑1
https://stackoverflow.com/a/15240012/9909365
为什么先前的答案不起作用
Pattern p2 = Pattern.compile("sig=(.*?)[&]"); Matcher m2 = p2.matcher(url); String sig = null; if (m2.find()) { sig = m2.group(1); }截至2016年11月,这有点粗糙,但显示了基本原理。今天的url_enpred_fmt_stream_map在冒号后(最好将其设为可选),并且“
sig”已更改为“signature”在调试代码时,我
signature&s在许多视频的URL中找到了new关键字
在这里编辑答案
private static final HashMap<String, meta> typeMap = new HashMap<String, meta>();
initTypeMap(); 先打电话
class meta { public String num; public String type; public String ext; meta(String num, String ext, String type) { this.num = num; this.ext = ext; this.type = type; }}class Video { public String ext = ""; public String type = ""; public String url = ""; Video(String ext, String type, String url) { this.ext = ext; this.type = type; this.url = url; }}public ArrayList<Video> getStreamingUrisFromYouTubePage(String ytUrl) throws IOException { if (ytUrl == null) { return null; } // Remove any query params in query string after the watch?v=<vid> in // e.g. // http://www.youtube.com/watch?v=0RUPACpf8Vs&feature=youtube_gdata_player int andIdx = ytUrl.indexOf('&'); if (andIdx >= 0) { ytUrl = ytUrl.substring(0, andIdx); } // Get the HTML response String html = ""; HttpsURLConnection c = (HttpsURLConnection) new URL(ytUrl).openConnection(); c.setRequestMethod("GET"); c.setDoOutput(true); c.connect(); InputStream in = c.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuilder str = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { str.append(line.replace("\u0026", "&")); } in.close(); html = str.toString(); // Parse the HTML response and extract the streaming URIs if (html.contains("verify-age-thumb")) { Log.e("Downloader", "YouTube is asking for age verification. We can't handle that sorry."); return null; } if (html.contains("das_captcha")) { Log.e("Downloader", "Captcha found, please try with different IP address."); return null; } Pattern p = Pattern.compile("stream_map":"(.*?)?""); // Pattern p = Pattern.compile("/stream_map=(.[^&]*?)"/"); Matcher m = p.matcher(html); List<String> matches = new ArrayList<String>(); while (m.find()) { matches.add(m.group()); } if (matches.size() != 1) { Log.e("Downloader", "Found zero or too many stream maps."); return null; } String urls[] = matches.get(0).split(","); HashMap<String, String> foundArray = new HashMap<String, String>(); for (String ppUrl : urls) { String url = URLDeprer.depre(ppUrl, "UTF-8"); Log.e("URL","URL : "+url); Pattern p1 = Pattern.compile("itag=([0-9]+?)[&]"); Matcher m1 = p1.matcher(url); String itag = null; if (m1.find()) { itag = m1.group(1); } Pattern p2 = Pattern.compile("signature=(.*?)[&]"); Matcher m2 = p2.matcher(url); String sig = null; if (m2.find()) { sig = m2.group(1); } else { Pattern p23 = Pattern.compile("signature&s=(.*?)[&]"); Matcher m23 = p23.matcher(url); if (m23.find()) { sig = m23.group(1); } } Pattern p3 = Pattern.compile("url=(.*?)[&]"); Matcher m3 = p3.matcher(ppUrl); String um = null; if (m3.find()) { um = m3.group(1); } if (itag != null && sig != null && um != null) { Log.e("foundArray","Adding Value"); foundArray.put(itag, URLDeprer.depre(um, "UTF-8") + "&" + "signature=" + sig); } } Log.e("foundArray","Size : "+foundArray.size()); if (foundArray.size() == 0) { Log.e("Downloader", "Couldn't find any URLs and corresponding signatures"); return null; } ArrayList<Video> videos = new ArrayList<Video>(); for (String format : typeMap.keySet()) { meta meta = typeMap.get(format); if (foundArray.containsKey(format)) { Video newVideo = new Video(meta.ext, meta.type, foundArray.get(format)); videos.add(newVideo); Log.d("Downloader", "YouTube Video streaming details: ext:" + newVideo.ext + ", type:" + newVideo.type + ", url:" + newVideo.url); } } return videos;}private class YouTubePageStreamUriGetter extends AsyncTask<String, String, ArrayList<Video>> { ProgressDialog progressDialog; @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = ProgressDialog.show(webViewActivity.this, "", "Connecting to YouTube...", true); } @Override protected ArrayList<Video> doInBackground(String... params) { ArrayList<Video> fVideos = new ArrayList<>(); String url = params[0]; try { ArrayList<Video> videos = getStreamingUrisFromYouTubePage(url); if (videos != null && !videos.isEmpty()) { for (Video video : videos) { Log.e("Downloader", "ext : " + video.ext); if (video.ext.toLowerCase().contains("mp4") || video.ext.toLowerCase().contains("3gp") || video.ext.toLowerCase().contains("flv") || video.ext.toLowerCase().contains("webm")) { ext = video.ext.toLowerCase(); fVideos.add(new Video(video.ext,video.type,video.url)); } } return fVideos; } } catch (Exception e) { e.printStackTrace(); Log.e("Downloader", "Couldn't get YouTube streaming URL", e); } Log.e("Downloader", "Couldn't get stream URI for " + url); return null; } @Override protected void onPostExecute(ArrayList<Video> streamingUrl) { super.onPostExecute(streamingUrl); progressDialog.dismiss(); if (streamingUrl != null) { if (!streamingUrl.isEmpty()) { //Log.e("Steaming Url", "Value : " + streamingUrl); for (int i = 0; i < streamingUrl.size(); i++) { Video fX = streamingUrl.get(i); Log.e("Founded Video", "URL : " + fX.url); Log.e("Founded Video", "TYPE : " + fX.type); Log.e("Founded Video", "EXT : " + fX.ext); } //new ProgressBack().execute(new String[]{streamingUrl, filename + "." + ext}); } } }}public void initTypeMap(){ typeMap.put("13", new meta("13", "3GP", "Low Quality - 176x144")); typeMap.put("17", new meta("17", "3GP", "Medium Quality - 176x144")); typeMap.put("36", new meta("36", "3GP", "High Quality - 320x240")); typeMap.put("5", new meta("5", "FLV", "Low Quality - 400x226")); typeMap.put("6", new meta("6", "FLV", "Medium Quality - 640x360")); typeMap.put("34", new meta("34", "FLV", "Medium Quality - 640x360")); typeMap.put("35", new meta("35", "FLV", "High Quality - 854x480")); typeMap.put("43", new meta("43", "WEBM", "Low Quality - 640x360")); typeMap.put("44", new meta("44", "WEBM", "Medium Quality - 854x480")); typeMap.put("45", new meta("45", "WEBM", "High Quality - 1280x720")); typeMap.put("18", new meta("18", "MP4", "Medium Quality - 480x360")); typeMap.put("22", new meta("22", "MP4", "High Quality - 1280x720")); typeMap.put("37", new meta("37", "MP4", "High Quality - 1920x1080")); typeMap.put("33", new meta("38", "MP4", "High Quality - 4096x230"));}编辑2:
一段时间后,此代码无法正常工作
同源政策
https://zh.wikipedia.org/wiki/Same-
origin_policy
https://en.wikipedia.org/wiki/Cross-
origin_resource_sharing
problem of Same-origin policy. Essentially, you cannot download this file from www.youtube.com because they are different domains. A workaround of this problem is [CORS][1].
参考:https :
//superuser.com/questions/773719/how-do-all-of-these-save-video-from-youtube-
services-work/773998#773998
url_enpred_fmt_stream_map // traditional: contains video and audio streamadaptive_fmts // DASH: contains video or audio stream
Each of these is a comma separated array of what I would call “stream
objects”. Each “stream object” will contain values like this
url // direct HTTP link to a videoitag // pre specifying the qualitys // signature, security measure to counter downloading
Each URL will be enpred so you will need to depre them. Now the tricky part.
YouTube has at least 3 security levels for their videos
unsecured // as expected, you can download these with just the unenpred URLs // see belowRTMPE // uses "rtmpe://" protocol, no known method for these
The RTMPE videos are typically used on official full length movies, and are
protected with SWF Verification Type 2. This has been around since 2011 and
has yet to be reverse engineered.
The type “s” videos are the most difficult that can actually be downloaded.
You will typcially see these on VEVO videos and the like. They start with a
signature such as
AA5D05FA7771AD4868BA4C977C3DEAAC620DE020E.0F421820F42978A1F8EAFCDAC4EF507DB5
Then the signature is scrambled with a function like this
function mo(a) { a = a.split(""); a = lo.rw(a, 1); a = lo.rw(a, 32); a = lo.IC(a, 1); a = lo.wS(a, 77); a = lo.IC(a, 3); a = lo.wS(a, 77); a = lo.IC(a, 3); a = lo.wS(a, 44); return a.join("")}This function is dynamic, it typically changes every day. To make it more
difficult the function is hosted at a URL such as
http://s.ytimg.com/yts/jsbin/html5player-en_US-vflycBCEX.js
this introduces the problem of Same-origin policy. Essentially, you cannot
download this file from www.youtube.com because they are different domains. A
workaround of this problem is CORS. With CORS, s.ytimg.com could add this
header
Access-Control-Allow-Origin: http://www.youtube.com
and it would allow the Javascript to download from www.youtube.com. Of course
they do not do this. A workaround for this workaround is to use a CORS proxy.
This is a proxy that responds with the following header to all requests
Access-Control-Allow-Origin: *
So, now that you have proxied your JS file, and used the function to scramble
the signature, you can use that in the querystring to download a video.



