Get metadata from shoutcast stream - java

I'm developing a radio app with multiple radios, the stream is playing fine. But I'm struggling to show artist and music playing at the moment.
This is the class I'm using to get metadata from shoutcast stream:
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IcyStreamMeta<Message> {
protected URL streamUrl;
private Map<String, String> metadata;
private boolean isError;
public IcyStreamMeta(URL streamUrl) {
setStreamUrl(streamUrl);
isError = false;
}
/**
* Get artist using stream's title
*
* #return String
* #throws IOException
*/
public String getArtist() throws IOException {
Map<String, String> data = getMetadata();
if (!data.containsKey("StreamTitle"))
return "";
String streamTitle = data.get("StreamTitle");
String title = streamTitle.substring(0, streamTitle.indexOf("-"));
return title.trim();
}
/**
* Get title using stream's title
*
* #return String
* #throws IOException
*/
public String getTitle() throws IOException {
Map<String, String> data = getMetadata();
if (!data.containsKey("StreamTitle"))
return "";
String streamTitle = data.get("StreamTitle");
String artist = streamTitle.substring(streamTitle.indexOf("-")+1);
return artist.trim();
}
public Map<String, String> getMetadata() throws IOException {
if (metadata == null) {
refreshMeta();
}
return metadata;
}
public void refreshMeta() throws IOException {
retreiveMetadata();
}
private void retreiveMetadata() throws IOException {
URLConnection con = streamUrl.openConnection();
con.setRequestProperty("Icy-MetaData", "1");
con.setRequestProperty("Connection", "close");
con.setRequestProperty("Accept", null);
con.connect();
int metaDataOffset = 0;
Map<String, List<String>> headers = con.getHeaderFields();
InputStream stream = con.getInputStream();
if (headers.containsKey("icy-metaint")) {
// Headers are sent via HTTP
metaDataOffset = Integer.parseInt(headers.get("icy-metaint").get(0));
} else {
// Headers are sent within a stream
StringBuilder strHeaders = new StringBuilder();
char c;
while ((c = (char)stream.read()) != -1) {
strHeaders.append(c);
if (strHeaders.length() > 5 && (strHeaders.substring((strHeaders.length() - 4), strHeaders.length()).equals("\r\n\r\n"))) {
// end of headers
break;
}
}
// Match headers to get metadata offset within a stream
Pattern p = Pattern.compile("\\r\\n(icy-metaint):\\s*(.*)\\r\\n");
Matcher m = p.matcher(strHeaders.toString());
if (m.find()) {
metaDataOffset = Integer.parseInt(m.group(2));
}
}
// In case no data was sent
if (metaDataOffset == 0) {
isError = true;
return;
}
// Read metadata
int b;
int count = 0;
int metaDataLength = 4080; // 4080 is the max length
boolean inData = false;
StringBuilder metaData = new StringBuilder();
// Stream position should be either at the beginning or right after headers
while ((b = stream.read()) != -1) {
count++;
// Length of the metadata
if (count == metaDataOffset + 1) {
metaDataLength = b * 16;
}
if (count > metaDataOffset + 1 && count < (metaDataOffset + metaDataLength)) {
inData = true;
} else {
inData = false;
}
if (inData) {
if (b != 0) {
metaData.append((char)b);
}
}
if (count > (metaDataOffset + metaDataLength)) {
break;
}
}
// Set the data
metadata = IcyStreamMeta.parseMetadata(metaData.toString());
// Close
stream.close();
}
public boolean isError() {
return isError;
}
public URL getStreamUrl() {
return streamUrl;
}
public void setStreamUrl(URL streamUrl) {
this.metadata = null;
this.streamUrl = streamUrl;
this.isError = false;
}
public static Map<String, String> parseMetadata(String metaString) {
Map<String, String> metadata = new HashMap();
String[] metaParts = metaString.split(";");
Pattern p = Pattern.compile("^([a-zA-Z]+)=\\'([^\\']*)\\'$");
Matcher m;
for (int i = 0; i < metaParts.length; i++) {
m = p.matcher(metaParts[i]);
if (m.find()) {
metadata.put(m.group(1), m.group(2));
}
}
return metadata;
}
}
And the method on MainActivity to get the metadata every 10 seconds
private void getMeta()
{
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
try {
IcyStreamMeta icy = new IcyStreamMeta(new URL(RadiophonyService.getRadioURL()));
final String data = icy.getArtist() + " - " + icy.getTitle();
final TextView meta = (TextView) findViewById(R.id.now_playing);
runOnUiThread(new Runnable() {
public void run() {
meta.setText(data);
}
});
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}, 0, 10000);
}
Initially, when I select one station, it plays but does not show metadata and when I select another station the app crashes with this runtime error:
E/AndroidRuntime: FATAL EXCEPTION: Timer-0
Process: com.example.app, PID: 23597
java.lang.StringIndexOutOfBoundsException: length=0; regionStart=0; regionLength=-1
at java.lang.String.startEndAndLength(String.java:504)
at java.lang.String.substring(String.java:1333)
at com.example.app.utilities.IcyStreamMeta.getArtist(IcyStreamMeta.java:41)
at com.example.app.activities.MainActivity$8.run(MainActivity.java:306)
at java.util.Timer$TimerImpl.run(Timer.java:284)
I'm stuck at this for days, and I tried other solutions but nothing works!

Looks like this line
String title = streamTitle.substring(0, streamTitle.indexOf("-"));
is the culprit.
If streamTitle does not have a dash in its title, indexOf() will return a -1, and substring() is choking because you can't have an end index less than the start index.
Maybe you need something like this:
int pos = streamTitle.indexOf("-");
String title = (pos == -1) ? streamTitle : streamTitle.substring(0, pos);

Related

How to use network I/O with Android [duplicate]

This question already has answers here:
How can I fix 'android.os.NetworkOnMainThreadException'?
(66 answers)
Closed 6 years ago.
The purpose of the class below is to get text from different articles of different news websites. The version below is designed for Android, but it throws a NetworkOnMainThread Exception when run. When I used an earlier version of this class, made specifically to run on a computer, it worked fine, but I'm not really sure how network I/O works on Android. I've seen some other answers to questions about this topic, but I don't understand why in Android the program throws an exception but on a desktop it works fine. Can anyone explain?
package com.example.user.helloworld;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
public class ArticleReceiver {
private ArrayList<Article> newsArticles = new ArrayList<>();
private ArrayList<String> newsLinks = new ArrayList<>();
public ArticleReceiver(int numArticles, String link) {
if (numArticles != 0) {
receiveNewsArticles(numArticles, link);
}else{
System.out.println("ERROR: numArticles request for " + link + " cannot equal 0.");
}
}
private void receiveNewsArticles(int numArticles, String urlAddress) {
URL rssUrl = null;
// if connected to Internet
if (true){//isInternetAvailable()) {
try {
// gather links
rssUrl = new URL(urlAddress);
BufferedReader in = new BufferedReader(new InputStreamReader(rssUrl.openStream()));
String line;
// fix bbc trash urls
if (urlAddress.equals(Main.BBC_URL)) {
numArticles++;
}
while ((line = in.readLine()) != null && newsLinks.size() <= numArticles) {
if (line.contains("<link>")) {
// find links through tags
int firstPos = line.indexOf("<link>");
String temp = line.substring(firstPos);
temp = temp.replace("<link>", "");
int lastPos = temp.indexOf("</link>");
temp = temp.substring(0, lastPos);
newsLinks.add(temp);
}
}
in.close();
// test if there are links and if there is remove first
// unnecessary
// link
if (!newsLinks.isEmpty()) {
if (urlAddress.equals(Main.BBC_URL)) {
newsLinks.remove(0);
newsLinks.remove(0);
}else if(urlAddress.equals(Main.CNN_URL) || urlAddress.equals(Main.FOX_URL) || urlAddress.equals(Main.ESPN_URL)){
newsLinks.remove(0);
}
} else {
System.out.println("ERROR: No Found Articles. Check If You Have Wifi.");
}
// gather articles from HTML "section" or "p" tag of article using Jsoup
for (String newsLink : newsLinks) {
// get webpage
Document doc = Jsoup.connect(newsLink).get();
// get article from different websites
String article = null;
if (urlAddress.equals(Main.FOX_URL)) {
Elements element = doc.select("p");
article = element.text();
} else if (urlAddress.equals(Main.CNN_URL)) {
Elements element = doc.select("section");
article = element.text();
} else if (urlAddress.equals(Main.BBC_URL)) {
Elements element = doc.select("p");
article = element.text();
}else if(urlAddress.equals(Main.ESPN_URL)){
Elements element = doc.select("p");
article = element.text();
}
newsArticles.add(new Article(article, Main.SUMMARY_SENTENCES));
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("ERROR: No internet connection established.");
return;
}
}
public ArrayList<Article> getArticles() {
return newsArticles;
}
public Article getArticle(int i) {
if (newsArticles.size() <= i) {
return null;
} else {
return newsArticles.get(i);
}
}
//The method below does not recognize the "getSystemService" method, and when the method is no longer present there is a NetworkOnMainThreadException
private boolean isInternetAvailable() {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
}
You need to execute web service connections asynchronous.
What I use in my projects is have a class ApiConnection and with interface get response. Example:
Apiconnection class
public class APIConnection extends AsyncTask<Object, String, Void> {
private final String TAG = "API-CONNECTION";
private StringBuilder sbuilder;
private JSONObject json;
private APIConnectionInterface mInterface;
protected int httpResponseCode = 0;
private String entity = null, url;
private APIConnectionType mmode;
private boolean DEBUG = BuildConfig.DEBUG;
private String[][] headers;
/**
Constructor For APIConnection
*/
public APIConnection(APIConnectionInterface thisdelegate, APIConnectionType mode, String murl, String entity) {
this.mInterface = thisdelegate;
this.mmode = mode;
this.url = murl;
this.entity = entity;
initHeaders();
}
private void initHeaders(){
headers = new String[][]{
{"token", "MY_TOKEN"},
{"Content-Type", "application/json;charset=utf-8"},
{"user-agent", "android"},
{"Accept-Language", "es"}
};
}
#Override
protected Void doInBackground(Object... params) {
BufferedReader buffer = null;
InputStreamReader in = null;
OutputStream os = null;
int timeoutConnection = 30000, timeoutSocket = 20000;
try{
sbuilder = new StringBuilder();
url = convertURL(url);
if (entity==null)entity="{}";
URL u = new URL(url);
HttpURLConnection conn;
if (url.startsWith("https://"))
conn = (HttpsURLConnection) u.openConnection();
else
conn = (HttpURLConnection) u.openConnection();
conn.setReadTimeout(timeoutConnection);
conn.setConnectTimeout(timeoutSocket);
for (String[] arr : headers){ conn.addRequestProperty(arr[0], arr[1]); }
/*GET*/if (mmode == APIConnectionType.GET) {
conn.setDoInput(true);
conn.setRequestMethod(mmode.toString());
httpResponseCode = conn.getResponseCode();
in = new InputStreamReader(
httpResponseCode == HttpURLConnection.HTTP_OK ? conn.getInputStream() : conn.getErrorStream(),"UTF-8");
/*OTHER*/} else if (mmode == APIConnectionType.POST || mmode == APIConnectionType.PUT ||
mmode == APIConnectionType.PATCH || mmode == APIConnectionType.DELETE) {
conn.setRequestMethod(mmode.toString());
conn.setDoOutput(true);
byte[] outputInBytes = entity.getBytes("UTF-8");
os = conn.getOutputStream();
os.write( outputInBytes );
httpResponseCode = conn.getResponseCode();
in = new InputStreamReader(
httpResponseCode == HttpURLConnection.HTTP_OK ? conn.getInputStream() : conn.getErrorStream(), "UTF-8");
}
if (in!=null){
buffer=new BufferedReader(in);
String line;
while ((line = buffer.readLine()) != null) {
sbuilder.append(line);
}
}else {
sbuilder.append("");
}
}
catch(IOException e) {
if (DEBUG)Log.d(TAG, "onBackground Exception " + e.getMessage());
sbuilder= new StringBuilder();
httpResponseCode = 0;
cancel(true);
return null;
} finally {
if (buffer != null) {
try {
buffer.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
if (os!=null){
try {
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
#Override
protected void onPostExecute(Void result){
try{
if (DEBUG) timelapse_e = System.currentTimeMillis();
if (sbuilder != null) {
json = new JSONObject(sbuilder.toString());
}
if (sbuilder != null){
sbuilder.setLength(0);
sbuilder.trimToSize();
}
sbuilder = null;
GoRunning();
hideDialog();
}
catch(RuntimeException e) {
if (DEBUG)Log.d(TAG, "PostExecute RuntimeException " + e.getMessage());
cancel(true);
}
catch(Exception e) {
if (DEBUG)Log.d(TAG, "PostExecute Exception " + e.getMessage());
cancel(true);
}
}
#Override protected void onCancelled() {
if (mInterface != null) mInterface.onCancelled(APIConnection.this);
super.onCancelled();
}
#Override protected void onPreExecute() {
super.onPreExecute();
if (DEBUG) timelapse_s = System.currentTimeMillis();
if (mInterface != null) mInterface.onStartLoading(APIConnection.this);
}
public void GoRunning(){
if (mInterface != null) try {
mInterface.onDataArrival(APIConnection.this, json, httpResponseCode);
} catch (JSONException e) {
onCancelled();
e.printStackTrace();
}
}
/**
* Hide Dialog (Progress dialog) if is showing and activity NOT Finishing
*/
private void hideDialog() {
if (mInterface != null) mInterface.onFinishedLoading(APIConnection.this);
}
/** <b>convertURL(String str);</b><br/>
* replaces any special characters to <b>%??</b><br/>
* Replacements actived:<br/>
* "{Space}" ==> "%20"
* #param str URL to encode
* #return url encoded
*/
public static String convertURL(String str) {
return str.trim().replace(" ", "%20");
// .replace("&", "%26")
// .replace(",", "%2c").replace("(", "%28").replace(")", "%29")
// .replace("!", "%21").replace("=", "%3D").replace("<", "%3C")
// .replace(">", "%3E").replace("#", "%23").replace("$", "%24")
// .replace("'", "%27").replace("*", "%2A").replace("-", "%2D")
// .replace(".", "%2E").replace("/", "%2F").replace(":", "%3A")
// .replace(";", "%3B").replace("?", "%3F").replace("#", "%40")
// .replace("[", "%5B").replace("\\", "%5C").replace("]", "%5D")
// .replace("_", "%5F").replace("`", "%60").replace("{", "%7B")
// .replace("|", "%7C").replace("}", "%7D"));
}
public interface APIConnectionInterface {
void onDataArrival(APIConnection apiConnection, JSONObject json, int httpResponseCode) throws JSONException;
void onStartLoading(APIConnection apiConnection);
void onFinishedLoading(APIConnection apiConnection);
void onCancelled(APIConnection apiConnection);
}
public enum APIConnectionType {
GET("GET"),
POST("POST"),
PUT("PUT"),
PATCH("PATCH"),
DELETE("DELETE");
private String methodName;
APIConnectionType(String methodName){this.methodName = methodName;}
#Override public String toString() {return methodName;}
}
}
And then from any Activity or Fragment I can call the web service async
like this:
new APIConnection(new APIConnection.APIConnectionInterface() {
#Override public void onDataArrival(APIConnection apiConnection, JSONObject json, int httpResponseCode) {
try {
if (isHttpResponseOk(httpResponseCode, json)){//200 or 201
JSONObject obj = json.getJSONObject("results");
// do things with json
}
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override public void onStartLoading(APIConnection apiConnection) {showProgressDialog();}
#Override public void onFinishedLoading(APIConnection apiConnection) {hideProgressDialog();}
#Override public void onCancelled(APIConnection apiConnection) {hideProgressDialog();}
}, APIConnection.APIConnectionType.GET, MyApp.API_URL + "/master_data/", null).execute();
The only thing you need is to adapt the response to other object you need.
I hope that helps

paging in Java for a lookup

I am trying to implement paging in an already running application, it works like mongodb where you get a query with bunch of parameters like
searchConditions ,limit( how many docs you want), skip ( skip these many docs)
and so on
and the response is back in json my task is to generate the previous and next link to the query that means previous page and the next page as per query
I came up with a method below
public Paging generateLink(RequestFilter filter) {
int prev_skip;
int prev_limit;
String pagePrev = "";
String pageNext = "";
int next_skip;
int next_limit;
String searchCondition = JsonUtils.toSimpleJsonString(filter.getSearch());
String url = "http://isgswmdi1n1.nam.nsroot.net:7014/account-search/rest/api/v1/pmm";
//properties.getProperty("paging.link");
System.out.println(url);
Paging pagingOption = new Paging();
Result result = new Result();
int count = luceneSearchService.searchCount(filter);
try {
if (count > 1) {
if (filter.getSkip() > 0) {
prev_skip = Math.max((filter.getSkip() - 1), 0);
prev_limit = filter.getLimit();
pagePrev = url + "?search=" + searchCondition + "&skip=" + prev_skip + "$limit=" + prev_limit;
} else{
result.setPaging(null);
return null;
}
if (count > (filter.getLimit() + filter.getSkip())) {
next_skip = (filter.getSkip() + filter.getLimit());
next_limit = filter.getLimit();
pageNext = url + "?search=" + searchCondition + "&skip=" + next_skip + "$limit=" + next_limit;
} else{
result.setPaging(null);
return null;
}
pagingOption.setPagePrev(pagePrev);
pagingOption.setPageNext(pageNext);
result.setPaging(pagingOption);
}
return pagingOption;
} catch (NullPointerException n)
{
n.printStackTrace();
return pagingOption;
}
}
call to generateLink method
return Result.ok(resultRow, generateLink(filter));
the ok response method in the Result class
public static Result ok(List<ResultRow> resultRows, Paging paging) {
if (paging != null) {
return new Result(resultRows, 200, "Completed", paging);
} else
return new Result(resultRows, 200, "Completed");
}
Underlying paging class
public class Paging implements Serializable {
public String getPagePrev() {
return pagePrev;
}
public void setPagePrev(String pagePrev) {
this.pagePrev = pagePrev;
}
#JsonProperty("pagePrev")
private String pagePrev;
public String getPageNext() {
return pageNext;
}
public void setPageNext(String pageNext) {
this.pageNext = pageNext;
}
#JsonProperty("pageNext")
private String pageNext;
}
but my test method is failing
#Test
public void search_allFilterParamsAreDefined_hp() throws Exception {
// given
Map<String, Serializable> searchConditions = singletonMap("shortName", "GO");
List<String> returnFields = asList("shortname", "gfcid");
String returnFieldsStr = returnFields.stream().collect(joining(","));
RequestFilter filter = RequestFilter.create(searchConditions, returnFieldsStr, 5, 10, true);
int resultSize = 20;
List<ResultRow> searchResult = getSearchResult(resultSize);
when(luceneSearchService.search(filter)).thenReturn(searchResult);
when(luceneSearchService.searchCount(filter)).thenReturn(resultSize);
// do
Result result = searchCoordinator.search(filter);
// verify
assertEquals(searchResult, result.getRows());
assertReturnFields(returnFields, result.getRows());
assertNotNull(result.getPaging());
Map<String, String> nextParams = getQueryParams(result.getPaging().getPageNext());
assertParam(nextParams, "search", toSimpleJsonString(searchConditions));
assertParam(nextParams, "skip", "15");
assertParam(nextParams, "limit", "10");
}

Getting metadata from SHOUTcast using IcyStreamMeta

I am writing an app for Android that grabs meta data from SHOUTcast mp3 streams. I am using a pretty nifty class I found online that I slightly modified, but I am still having 2 problems.
1) I have to continuously ping the server to update the metadata using a TimerTask. I am not fond of this approach but it was all I could think of.
2) There is a metric tonne of garbage collection while my app is running. Removing the TimerTask got rid of the garbage collection issue so I am not sure if I am just doing it wrong or if this is normal.
Here is the class I am using:
public class IcyStreamMeta {
protected URL streamUrl;
private Map<String, String> metadata;
private boolean isError;
public IcyStreamMeta(URL streamUrl) {
setStreamUrl(streamUrl);
isError = false;
}
/**
* Get artist using stream's title
*
* #return String
* #throws IOException
*/
public String getArtist() throws IOException {
Map<String, String> data = getMetadata();
if (!data.containsKey("StreamTitle"))
return "";
try {
String streamTitle = data.get("StreamTitle");
String title = streamTitle.substring(0, streamTitle.indexOf("-"));
return title.trim();
}catch (StringIndexOutOfBoundsException e) {
return "";
}
}
/**
* Get title using stream's title
*
* #return String
* #throws IOException
*/
public String getTitle() throws IOException {
Map<String, String> data = getMetadata();
if (!data.containsKey("StreamTitle"))
return "";
try {
String streamTitle = data.get("StreamTitle");
String artist = streamTitle.substring(streamTitle.indexOf("-")+1);
return artist.trim();
} catch (StringIndexOutOfBoundsException e) {
return "";
}
}
public Map<String, String> getMetadata() throws IOException {
if (metadata == null) {
refreshMeta();
}
return metadata;
}
public void refreshMeta() throws IOException {
retreiveMetadata();
}
private void retreiveMetadata() throws IOException {
URLConnection con = streamUrl.openConnection();
con.setRequestProperty("Icy-MetaData", "1");
con.setRequestProperty("Connection", "close");
//con.setRequestProperty("Accept", null);
con.connect();
int metaDataOffset = 0;
Map<String, List<String>> headers = con.getHeaderFields();
InputStream stream = con.getInputStream();
if (headers.containsKey("icy-metaint")) {
// Headers are sent via HTTP
metaDataOffset = Integer.parseInt(headers.get("icy-metaint").get(0));
} else {
// Headers are sent within a stream
StringBuilder strHeaders = new StringBuilder();
char c;
while ((c = (char)stream.read()) != -1) {
strHeaders.append(c);
if (strHeaders.length() > 5 && (strHeaders.substring((strHeaders.length() - 4), strHeaders.length()).equals("\r\n\r\n"))) {
// end of headers
break;
}
}
// Match headers to get metadata offset within a stream
Pattern p = Pattern.compile("\\r\\n(icy-metaint):\\s*(.*)\\r\\n");
Matcher m = p.matcher(strHeaders.toString());
if (m.find()) {
metaDataOffset = Integer.parseInt(m.group(2));
}
}
// In case no data was sent
if (metaDataOffset == 0) {
isError = true;
return;
}
// Read metadata
int b;
int count = 0;
int metaDataLength = 4080; // 4080 is the max length
boolean inData = false;
StringBuilder metaData = new StringBuilder();
// Stream position should be either at the beginning or right after headers
while ((b = stream.read()) != -1) {
count++;
// Length of the metadata
if (count == metaDataOffset + 1) {
metaDataLength = b * 16;
}
if (count > metaDataOffset + 1 && count < (metaDataOffset + metaDataLength)) {
inData = true;
} else {
inData = false;
}
if (inData) {
if (b != 0) {
metaData.append((char)b);
}
}
if (count > (metaDataOffset + metaDataLength)) {
break;
}
}
// Set the data
metadata = IcyStreamMeta.parseMetadata(metaData.toString());
// Close
stream.close();
}
public boolean isError() {
return isError;
}
public URL getStreamUrl() {
return streamUrl;
}
public void setStreamUrl(URL streamUrl) {
this.metadata = null;
this.streamUrl = streamUrl;
this.isError = false;
}
public static Map<String, String> parseMetadata(String metaString) {
Map<String, String> metadata = new HashMap<String, String>();
String[] metaParts = metaString.split(";");
Pattern p = Pattern.compile("^([a-zA-Z]+)=\\'([^\\']*)\\'$");
Matcher m;
for (int i = 0; i < metaParts.length; i++) {
m = p.matcher(metaParts[i]);
if (m.find()) {
metadata.put((String)m.group(1), (String)m.group(2));
}
}
return metadata;
}
}
And here is my timer:
private void getMeta() {
timer.schedule(new TimerTask() {
public void run() {
try {
icy = new IcyStreamMeta(new URL(stationUrl));
runOnUiThread(new Runnable() {
public void run() {
try {
artist.setText(icy.getArtist());
title.setText(icy.getTitle());
} catch (IOException e) {
e.printStackTrace();
} catch (StringIndexOutOfBoundsException e) {
e.printStackTrace();
}
}
});
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
},0,5000);
}
Much appreciation for any assistance!
I've replaced the IcyStreamMeta class in my program and am getting the meta data from the 7.html file that is a part of the SHOUTcast spec. Far less data usage and all that so I feel it is a better option.
I am still using the TimerTask, which is acceptable. There is practically no GC any more and I am happy with using 7.html and a little regex. :)

Function based expression message renderer

I'm doing a simple MessageRenderer.
It's specification:
Render message based on an Context (it's a map that's contains all key/value pair parameters)
Supports simple render such as: Your username is << username >>. Assume username in the context is barcelona and the result will be Your username is Barcelona.
Supported function-like object. Example: Current time is << now() >>, now(): is an object that will returns a string of current date time. And result will be: Current time is 2011-05-30
Each parameter of function can also be templated: Current time is << now( << date_format >> ) >> . This template returns a string of current date time with format is the value of key 'date_format' retrieved from the Context. Assume date_format in Context is dd/MM/yyyy and the result will be: Current time is 30/05/2011
Each parameter of function can also be templated with a different method call: Time is << now_locale ( << getLocale() >> ). Assume that getLocale() is an function object that will be return a locale is en_US and the result will be: Time is 2011/05/30 11:20:34 PM
Template can be nested. Example: Your user name is << << username >> >>. It means, Key username has value param1, Key param1 has value is barcelona so the final result will be: Your user name is Barcelona.
My classes and interfaces:
RenderContext.java
public interface RenderContext {
public String getParameter(String key);
}
MessageRenderer.java
public interface MessageRenderer {
public String render(String s, RenderContext... context);
}
MethodExpressionEvaluator.java
// Using this class to implements the method evaluation, such as now(), now_locale()
public interface MethodExpressionEvaluator {
public String evaluate(String[] methodParams, RenderContext... context);
}
AbstractMessageRenderer.java
public abstract class AbstractMessageRenderer implements MessageRenderer {
public static final String DEFAULT_NULL = "###";
public static final String PLACEHOLDER_START_TOKEN = "<<";
public static final String PLACEHOLDER_END_TOKEN = ">>";
protected int lenPlaceholderStartToken = 0;
protected int lenPlaceholderEndToken = 0;
protected String nullToken;
protected String placeholderStartToken;
protected String placeholderEndToken;
protected boolean escape = true;
public AbstractMessageRenderer() {
placeholderStartToken = PLACEHOLDER_START_TOKEN;
placeholderEndToken = PLACEHOLDER_END_TOKEN;
lenPlaceholderStartToken = placeholderStartToken.length();
lenPlaceholderEndToken = placeholderEndToken.length();
nullToken = DEFAULT_NULL;
}
public String getNullToken() {
return nullToken;
}
public void setNullToken(String defaultNull) {
this.nullToken = defaultNull;
}
public String getPlaceholderStartToken() {
return placeholderStartToken;
}
public void setPlaceholderStartToken(String placeholderStartToken) {
this.placeholderStartToken = placeholderStartToken;
lenPlaceholderStartToken = placeholderStartToken.length();
}
public String getPlaceholderEndToken() {
return placeholderEndToken;
}
public void setPlaceholderEndToken(String placeholderEndToken) {
this.placeholderEndToken = placeholderEndToken;
lenPlaceholderEndToken = placeholderEndToken.length();
}
public boolean isEscape() {
return escape;
}
public boolean getEscape() {
return escape;
}
public void setEscape(boolean escape) {
this.escape = escape;
}
public String getParam(String key, RenderContext... context) {
if(context != null)
{
for(RenderContext param:context)
{
if(param != null)
{
String value = param.getParameter(key);
if(!StringUtil.isEmpty(value))
{
return value;
}
}
}
}
return nullToken;
}
public String render(String s, RenderContext... context) {
// handle trivial cases of empty template or no placeholders
if (s == null)
{
Log4j.app.debug("Message is null in template. Cannot render null message.");
return nullToken;
}
if (context == null)
{
Log4j.app.debug("RenderContext is null. Cannot render message with null RenderContext.");
return nullToken;
}
if (s.indexOf(placeholderStartToken) < 0)
{
return s;
}
String msg = nullToken;
try
{
// private int renderTemplate(Renderable r, String src, StringBuffer dst, String nil, int i, String[] marks, StringBuffer end,boolean escapes)
msg = doRender(s, context);
}
catch (Exception e)
{
Log4j.app.error("Exception in rendering template: " + e.getMessage(), e);
return nullToken;
}
return msg;
}
protected abstract String doRender(String s, RenderContext... context);
}
MethodExpressionRenderer.java
public class MethodExpressionRenderer extends AbstractMessageRenderer {
private boolean inSingleQuote = false;
private boolean inDoubleQuote=false;
private int placeholders;
private Stack<String> methodStack;
private String[] endTokens;
private String marker;
private List<String> methodParams;
private String prefix = "&";
public MethodExpressionRenderer() {
super();
methodStack = new Stack<String>();
marker = ",";
endTokens = new String[] { placeholderEndToken, marker, "(", ")" };
methodParams = new ArrayList<String>();
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getMarker() {
return marker;
}
public void setMarker(String marker) {
this.marker = marker;
endTokens = new String[] { placeholderEndToken, marker };
}
#Override
public void setPlaceholderEndToken(String placeholderEndToken) {
super.setPlaceholderEndToken(placeholderEndToken);
endTokens = new String[] { placeholderEndToken, marker };
}
protected String doRender(String s, RenderContext... context) {
StringBuffer sb = new StringBuffer();
try
{
renderTemplate(s, sb, nullToken, 0, endTokens, null, context);
}
catch (Exception e)
{
Log4j.app.error("Exception in rendering method expression message emplate: " + e.getMessage(), e);
return nullToken;
}
return sb.toString();
}
private int renderTemplate(String src, StringBuffer dst, String nil, int i, String[] marks, StringBuffer end, RenderContext... context) {
int len = src.length();
while (i < len)
{
char c = src.charAt(i);
if (escape)
{
if (c=='\\')
{
i++;
char ch = src.charAt(i);
if(inSingleQuote)
{
if(ch=='\'')
{
inSingleQuote=false;
}
}
else if(inDoubleQuote)
{
if(ch=='"')
{
inDoubleQuote=false;
}
}
else
{
if(ch=='\'')
{
inSingleQuote=true;
}
else if(ch=='"')
{
inDoubleQuote=true;
}
}
dst.append(ch);
i++;
continue;
}
}
if(inSingleQuote)
{
if(c=='\'')
{
inSingleQuote=false;
}
}
else if(inDoubleQuote)
{
if(c=='"')
{
inDoubleQuote=false;
}
}
else
{
if(c=='\'')
{
inSingleQuote=true;
}
else if(c=='"')
{
inDoubleQuote=true;
}
}
// check for end marker
if (marks != null && !inSingleQuote && !inDoubleQuote)
{
for (int m = 0; m < marks.length; m++)
{
// If one of markers found
if (src.regionMatches(i, marks[m], 0, marks[m].length()))
{
// return marker if required
if (end != null)
{
end.append(marks[m]);
}
return i+marks[m].length();
}
}
}
// check for start of placeholder
if (src.regionMatches(i, placeholderStartToken, i, lenPlaceholderStartToken))
{
synchronized(this)
{
++placeholders;
}
i = renderPlaceholder(src, dst, nil, i, new ArrayList<String>(), context);
continue;
}
// just add plain character
if(c != '\'' && c!= '"')
{
dst.append(c);
}
i++;
}
return i;
}
private int renderPlaceholder(String src, StringBuffer dst, String nil, int i, List<String> params, RenderContext... context){
StringBuffer token = new StringBuffer(); // placeholder token
StringBuffer end = new StringBuffer(); // placeholder end marker
String value;
i = renderTemplate(src, token, nil, i+lenPlaceholderStartToken, endTokens, end);
String sToken = token.toString().trim();
String sEnd = end.toString().trim();
boolean isFunction = sEnd.equals("(");
// This is method name
if(isFunction && placeholders > methodStack.size())
{ // Method
synchronized(this)
{
methodStack.push(sToken); // put method into stack
}
}
else if(!isFunction && (methodStack.size()==0) && sEnd.equals(placeholderEndToken)) // Single template param such as <<param>>
{
value = getParam(sToken, context);
if(value != null)
{
if(value.trim().startsWith(placeholderStartToken))
{
value = render(src, context);
}
dst.append(value);
return i;
}
}
// TODO: Process method parameters to invoke
//.... ?????????
// Found end method token ')'
// Pop method out of stack to invoke
if ( (methodStack.size() >0) && (sEnd.length() == 0 || sEnd.equals(")")))
{
String method = null;
synchronized(this)
{
// Pop method out of stack to invoke
method = methodStack.pop();
--placeholders;
dst.append(invokeMethodEvaluator(method, methodParams.toArray(new String[0]), context));
methodParams.clear();
}
}
return i;
}
// Currently this method just implement to test so it just printout the method name
// and its parameter
// We can register MethodExpressionEvaluator to process
protected String invokeMethodEvaluator(String method, String[] params, RenderContext... context){
StringBuffer result = new StringBuffer();
result.append("[ ")
.append(method)
.append(" ( ");
if(params != null)
{
for(int i=0; i<params.length; i++)
{
result.append(params[i]);
if(i != params.length-1)
{
result.append(" , ");
}
}
}
result.append(" ) ")
.append(" ] ");
return result.toString();
}
}
We can easily register more method to the renderer to invoke. Each method will be an object and can be reused. But I'm in trouble how to resolve the nested method parameter. Can anyone give me an advice how we can process nested template of method parameter to invoke??? The line has TODO. Will my code in on the right way???
When you evaluate something like << count( << getTransId() >> ) >> you can either:
perform direct-evaluation as you parse, and push each function onto a stack, so that once you've evaluated getTransId() you pop the stack and use the return value (from the stack) as an argument for count(), or
you can build a parse tree to represent all the function calls that will be made, and then evaluate your parse tree after building it. (Building a tree probably doesn't buy you anything; since you're writing a template engine there is probably no high-level tree operation 'optimizations' that you could perform.)
An excellent little book I really enjoyed was Language Implementation Patterns by Parr. He walks through building simple to complex languages, and covers decisions like this in some depth. (Yes, he uses the ANTLR parser generator throughout, but your code looks like you're familiar enough with hand-generated parsers that different tools won't be a distraction for you.)
I found the bug and fixed it.
This is my new source:
// AbstractMethodExpressionRenderer.java
public class AbstractMethodExpressionRenderer extends AbstractMessageRenderer {
private boolean inSingleQuote = false;
private boolean inDoubleQuote=false;
private Stack<MethodExpressionDescriptor> functionStack;
private String[] endTokens;
private String marker;
private String prefix = "~";
public AbstractMethodExpressionRenderer() {
super();
functionStack = new Stack<MethodExpressionDescriptor>();
marker = ",";
endTokens = new String[] { placeholderEndToken, "(", ")", };
}
private class MethodExpressionDescriptor {
public List<String> params;
public String function;
public MethodExpressionDescriptor() {
params = new ArrayList<String>();
}
public MethodExpressionDescriptor(String name) {
this();
this.function = name;
}
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getMarker() {
return marker;
}
public void setMarker(String marker) {
this.marker = marker;
endTokens = new String[] { placeholderEndToken, marker };
}
#Override
public void setPlaceholderEndToken(String placeholderEndToken) {
super.setPlaceholderEndToken(placeholderEndToken);
endTokens = new String[] { placeholderEndToken, marker };
}
protected String doRender(String s, RenderContext... context) {
StringBuffer sb = new StringBuffer();
try
{
renderTemplate(s, sb, nullToken, 0, endTokens, null, context);
}
catch (Exception e)
{
Log4j.app.error("Exception in rendering method expression message emplate: " + e.getMessage(), e);
return nullToken;
}
return sb.toString();
}
private int renderTemplate(String src, StringBuffer dst, String nil, int i, String[] marks, StringBuffer end, RenderContext... context) {
int len = src.length();
while (i < len)
{
char c = src.charAt(i);
if (escape)
{
if (c=='\\')
{
i++;
char ch = src.charAt(i);
if(inSingleQuote)
{
if(ch=='\'')
{
inSingleQuote=false;
}
}
else if(inDoubleQuote)
{
if(ch=='"')
{
inDoubleQuote=false;
}
}
else
{
if(ch=='\'')
{
inSingleQuote=true;
}
else if(ch=='"')
{
inDoubleQuote=true;
}
}
dst.append(ch);
i++;
continue;
}
}
if(inSingleQuote)
{
if(c=='\'')
{
inSingleQuote=false;
}
}
else if(inDoubleQuote)
{
if(c=='"')
{
inDoubleQuote=false;
}
}
else
{
if(c=='\'')
{
inSingleQuote=true;
}
else if(c=='"')
{
inDoubleQuote=true;
}
}
// check for end marker
if (marks != null && !inSingleQuote && !inDoubleQuote)
{
for (int m = 0; m < marks.length; m++)
{
// If one of markers found
if (src.regionMatches(i, marks[m], 0, marks[m].length()))
{
// return marker if required
if (end != null)
{
end.append(marks[m]);
}
return i+marks[m].length();
}
}
}
// check for start of placeholder
if (src.regionMatches(i, placeholderStartToken, 0, lenPlaceholderStartToken))
{
i = renderPlaceholder(src, dst, nil, i, new ArrayList<String>(), context);
continue;
}
// just add plain character
if(c != '\'' && c!= '"')
{
dst.append(c);
}
i++;
}
return i;
}
/**
* Render a placeholder as follows:
*
* <<key>>: Simple render, key value map
* <<function(<<param1>>, <<param2>>)>> : Function object render
*
* #param src
* #param dst
* #param nil
* #param i
* #param params
* #param context
* #return
*/
private int renderPlaceholder(String src, StringBuffer dst, String nil, int i, List<String> params, RenderContext... context){
StringBuffer token = new StringBuffer(); // placeholder token
StringBuffer end = new StringBuffer(); // placeholder end marker
String value = null;
// Simple key
i = renderTemplate(src, token, nil, i+lenPlaceholderStartToken, endTokens, end, context);
String sToken = token.toString().trim();
String sEnd = end.toString().trim();
// This is method name
if(sEnd.equals("("))
{ // Method
functionStack.add(new MethodExpressionDescriptor(sToken));
}
else // Try to resolve value
{
if(sToken.startsWith(placeholderStartToken))
{
value = render(sToken, context);
}
else if(sToken.startsWith(prefix))
{
if(functionStack.size() > 0)
{
functionStack.peek().params.add(sToken.substring(1));
}
return i;
}
else
{
value = getParam(sToken, context);
}
}
if (sEnd.length() == 0 || sEnd.equals(placeholderEndToken))
{
// No method found but found the end of placeholder token
if(functionStack.size() == 0)
{
if(value != null)
{
dst.append(value);
}
else
{
dst.append(nil);
}
}
else
{
functionStack.peek().params.add(value);
}
}
else
{
if(value != null)
{
value = value.trim();
}
if(end.substring(0, 1).equals("(") ||
end.substring(0, 1).equals(marker))
{
// right hand side is remainder of placeholder
StringBuffer tmp = new StringBuffer();
end = new StringBuffer();
i = renderTemplate(src, tmp, nil, i, endTokens, end, context);
}
if(end.substring(0, 1).equals(")"))
{
if ( functionStack.size() > 0 )
{
// Pop method out of stack to invoke
MethodExpressionDescriptor descriptor = functionStack.pop();
if(functionStack.size() > 0 )
{
functionStack.peek().params.add(invokeMethodEvaluator(descriptor.function, descriptor.params.toArray(new String[0]), context));
}
else
{
dst.append(invokeMethodEvaluator(descriptor.function, descriptor.params.toArray(new String[0]), context));
}
end = new StringBuffer();
StringBuffer tmp = new StringBuffer();
i = renderTemplate(src, tmp, nil, i, endTokens, end, context);
}
}
}
return i;
}
protected String invokeMethodEvaluator(String method, String[] params, RenderContext... context){
StringBuffer result = new StringBuffer();
result.append("[ ")
.append(method)
.append(" ( ");
if(params != null)
{
for(int i=0; i<params.length; i++)
{
result.append(params[i]);
if(i != params.length-1)
{
result.append(" , ");
}
}
}
result.append(" ) ")
.append(" ] ");
return result.toString();
}
}

How to download videos from youtube on java? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
how to download videos from youtube on Java?
need class(or piece of code) which describes how to do that.
Thank you.
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
public class JavaYoutubeDownloader {
public static String newline = System.getProperty("line.separator");
private static final Logger log = Logger.getLogger(JavaYoutubeDownloader.class.getCanonicalName());
private static final Level defaultLogLevelSelf = Level.FINER;
private static final Level defaultLogLevel = Level.WARNING;
private static final Logger rootlog = Logger.getLogger("");
private static final String scheme = "http";
private static final String host = "www.youtube.com";
private static final Pattern commaPattern = Pattern.compile(",");
private static final Pattern pipePattern = Pattern.compile("\\|");
private static final char[] ILLEGAL_FILENAME_CHARACTERS = { '/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':' };
private static void usage(String error) {
if (error != null) {
System.err.println("Error: " + error);
}
System.err.println("usage: JavaYoutubeDownload VIDEO_ID DESTINATION_DIRECTORY");
System.exit(-1);
}
public static void main(String[] args) {
if (args == null || args.length == 0) {
usage("Missing video id. Extract from http://www.youtube.com/watch?v=VIDEO_ID");
}
try {
setupLogging();
log.fine("Starting");
String videoId = null;
String outdir = ".";
// TODO Ghetto command line parsing
if (args.length == 1) {
videoId = args[0];
} else if (args.length == 2) {
videoId = args[0];
outdir = args[1];
}
int format = 18; // http://en.wikipedia.org/wiki/YouTube#Quality_and_codecs
String encoding = "UTF-8";
String userAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13";
File outputDir = new File(outdir);
String extension = getExtension(format);
play(videoId, format, encoding, userAgent, outputDir, extension);
} catch (Throwable t) {
t.printStackTrace();
}
log.fine("Finished");
}
private static String getExtension(int format) {
// TODO
return "mp4";
}
private static void play(String videoId, int format, String encoding, String userAgent, File outputdir, String extension) throws Throwable {
log.fine("Retrieving " + videoId);
List<NameValuePair> qparams = new ArrayList<NameValuePair>();
qparams.add(new BasicNameValuePair("video_id", videoId));
qparams.add(new BasicNameValuePair("fmt", "" + format));
URI uri = getUri("get_video_info", qparams);
CookieStore cookieStore = new BasicCookieStore();
HttpContext localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet(uri);
httpget.setHeader("User-Agent", userAgent);
log.finer("Executing " + uri);
HttpResponse response = httpclient.execute(httpget, localContext);
HttpEntity entity = response.getEntity();
if (entity != null && response.getStatusLine().getStatusCode() == 200) {
InputStream instream = entity.getContent();
String videoInfo = getStringFromInputStream(encoding, instream);
if (videoInfo != null && videoInfo.length() > 0) {
List<NameValuePair> infoMap = new ArrayList<NameValuePair>();
URLEncodedUtils.parse(infoMap, new Scanner(videoInfo), encoding);
String token = null;
String downloadUrl = null;
String filename = videoId;
for (NameValuePair pair : infoMap) {
String key = pair.getName();
String val = pair.getValue();
log.finest(key + "=" + val);
if (key.equals("token")) {
token = val;
} else if (key.equals("title")) {
filename = val;
} else if (key.equals("fmt_url_map")) {
String[] formats = commaPattern.split(val);
for (String fmt : formats) {
String[] fmtPieces = pipePattern.split(fmt);
if (fmtPieces.length == 2) {
// in the end, download somethin!
downloadUrl = fmtPieces[1];
int pieceFormat = Integer.parseInt(fmtPieces[0]);
if (pieceFormat == format) {
// found what we want
downloadUrl = fmtPieces[1];
break;
}
}
}
}
}
filename = cleanFilename(filename);
if (filename.length() == 0) {
filename = videoId;
} else {
filename += "_" + videoId;
}
filename += "." + extension;
File outputfile = new File(outputdir, filename);
if (downloadUrl != null) {
downloadWithHttpClient(userAgent, downloadUrl, outputfile);
}
}
}
}
private static void downloadWithHttpClient(String userAgent, String downloadUrl, File outputfile) throws Throwable {
HttpGet httpget2 = new HttpGet(downloadUrl);
httpget2.setHeader("User-Agent", userAgent);
log.finer("Executing " + httpget2.getURI());
HttpClient httpclient2 = new DefaultHttpClient();
HttpResponse response2 = httpclient2.execute(httpget2);
HttpEntity entity2 = response2.getEntity();
if (entity2 != null && response2.getStatusLine().getStatusCode() == 200) {
long length = entity2.getContentLength();
InputStream instream2 = entity2.getContent();
log.finer("Writing " + length + " bytes to " + outputfile);
if (outputfile.exists()) {
outputfile.delete();
}
FileOutputStream outstream = new FileOutputStream(outputfile);
try {
byte[] buffer = new byte[2048];
int count = -1;
while ((count = instream2.read(buffer)) != -1) {
outstream.write(buffer, 0, count);
}
outstream.flush();
} finally {
outstream.close();
}
}
}
private static String cleanFilename(String filename) {
for (char c : ILLEGAL_FILENAME_CHARACTERS) {
filename = filename.replace(c, '_');
}
return filename;
}
private static URI getUri(String path, List<NameValuePair> qparams) throws URISyntaxException {
URI uri = URIUtils.createURI(scheme, host, -1, "/" + path, URLEncodedUtils.format(qparams, "UTF-8"), null);
return uri;
}
private static void setupLogging() {
changeFormatter(new Formatter() {
#Override
public String format(LogRecord arg0) {
return arg0.getMessage() + newline;
}
});
explicitlySetAllLogging(Level.FINER);
}
private static void changeFormatter(Formatter formatter) {
Handler[] handlers = rootlog.getHandlers();
for (Handler handler : handlers) {
handler.setFormatter(formatter);
}
}
private static void explicitlySetAllLogging(Level level) {
rootlog.setLevel(Level.ALL);
for (Handler handler : rootlog.getHandlers()) {
handler.setLevel(defaultLogLevelSelf);
}
log.setLevel(level);
rootlog.setLevel(defaultLogLevel);
}
private static String getStringFromInputStream(String encoding, InputStream instream) throws UnsupportedEncodingException, IOException {
Writer writer = new StringWriter();
char[] buffer = new char[1024];
try {
Reader reader = new BufferedReader(new InputStreamReader(instream, encoding));
int n;
while ((n = reader.read(buffer)) != -1) {
writer.write(buffer, 0, n);
}
} finally {
instream.close();
}
String result = writer.toString();
return result;
}
}
/**
* <pre>
* Exploded results from get_video_info:
*
* fexp=90...
* allow_embed=1
* fmt_stream_map=35|http://v9.lscache8...
* fmt_url_map=35|http://v9.lscache8...
* allow_ratings=1
* keywords=Stefan Molyneux,Luke Bessey,anarchy,stateless society,giant stone cow,the story of our unenslavement,market anarchy,voluntaryism,anarcho capitalism
* track_embed=0
* fmt_list=35/854x480/9/0/115,34/640x360/9/0/115,18/640x360/9/0/115,5/320x240/7/0/0
* author=lukebessey
* muted=0
* length_seconds=390
* plid=AA...
* ftoken=null
* status=ok
* watermark=http://s.ytimg.com/yt/swf/logo-vfl_bP6ud.swf,http://s.ytimg.com/yt/swf/hdlogo-vfloR6wva.swf
* timestamp=12...
* has_cc=False
* fmt_map=35/854x480/9/0/115,34/640x360/9/0/115,18/640x360/9/0/115,5/320x240/7/0/0
* leanback_module=http://s.ytimg.com/yt/swfbin/leanback_module-vflJYyeZN.swf
* hl=en_US
* endscreen_module=http://s.ytimg.com/yt/swfbin/endscreen-vflk19iTq.swf
* vq=auto
* avg_rating=5.0
* video_id=S6IZP3yRJ9I
* token=vPpcFNh...
* thumbnail_url=http://i4.ytimg.com/vi/S6IZP3yRJ9I/default.jpg
* title=The Story of Our Unenslavement - Animated
* </pre>
*/
I know i am answering late. But this code may useful for some one. So i am attaching it here.
Use the following java code to download the videos from YouTube.
package com.mycompany.ytd;
import java.io.File;
import java.net.URL;
import com.github.axet.vget.VGet;
/**
*
* #author Manindar
*/
public class YTD {
public static void main(String[] args) {
try {
String url = "https://www.youtube.com/watch?v=s10ARdfQUOY";
String path = "D:\\Manindar\\YTD\\";
VGet v = new VGet(new URL(url), new File(path));
v.download();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Add the below Dependency in your POM.XML file
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>vget</artifactId>
<version>1.1.33</version>
</dependency>
Hope this will be useful.
Regarding the format (mp4 or flv) decide which URL you want to use. Then use this tutorial to download the video and save it into a local directory.
Ref :Youtube Video Download (Android/Java)
Edit 3
You can use the Lib : https://github.com/HaarigerHarald/android-youtubeExtractor
Ex :
String youtubeLink = "http://youtube.com/watch?v=xxxx";
new YouTubeExtractor(this) {
#Override
public void onExtractionComplete(SparseArray<YtFile> ytFiles, VideoMeta vMeta) {
if (ytFiles != null) {
int itag = 22;
String downloadUrl = ytFiles.get(itag).getUrl();
}
}
}.extract(youtubeLink, true, true);
They decipherSignature using :
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;
}
Now with use of this library High Quality Videos Lossing Audio so i use the MediaMuxer for Murging Audio and Video for Final Output
Edit 1
https://stackoverflow.com/a/15240012/9909365
Why the previous answer not worked
Pattern p2 = Pattern.compile("sig=(.*?)[&]");
Matcher m2 = p2.matcher(url);
String sig = null;
if (m2.find()) {
sig = m2.group(1);
}
As of November 2016, this is a little rough around the edges, but
displays the basic principle. The url_encoded_fmt_stream_map today
does not have a space after the colon (better make this optional) and
"sig" has been changed to "signature"
and while i am debuging the code i found the new keyword its
signature&s in many video's URL
here edited answer
private static final HashMap<String, Meta> typeMap = new HashMap<String, Meta>();
initTypeMap(); call first
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 userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1)";*/
/* HttpClient client = new DefaultHttpClient();
client.getParams().setParameter(CoreProtocolPNames.USER_AGENT,
userAgent);
HttpGet request = new HttpGet(ytUrl);
HttpResponse response = client.execute(request);*/
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 = URLDecoder.decode(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, URLDecoder.decode(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);
/* Log.e("Downloader","Size of Video : "+videos.size());*/
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"));
}
Edit 2:
Some time This Code Not worked proper
Same-origin policy
https://en.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].
Ref : https://superuser.com/questions/773719/how-do-all-of-these-save-video-from-youtube-services-work/773998#773998
url_encoded_fmt_stream_map // traditional: contains video and audio stream
adaptive_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 video
itag // code specifying the quality
s // signature, security measure to counter downloading
Each URL will be encoded so you will need to decode 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 unencoded URL
s // see below
RTMPE // 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.
ytd2 is a fully functional YouTube video downloader. Check out its source code if you want to see how it's done.
Alternatively, you can also call an external process like youtube-dl to do the job. This is probably the easiest solution but it isn't in "pure" Java.

Categories

Resources