/*$Id$*/
package nicobrowser;
+import java.net.URI;
import java.net.URISyntaxException;
+import java.util.Set;
+import java.util.TreeMap;
import java.util.regex.Matcher;
import nicobrowser.entity.NicoContent;
+import nicobrowser.search.SortKind;
+import nicobrowser.search.SortOrder;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
+import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import nicobrowser.entity.NicoContent.Status;
+import nicobrowser.search.SearchKind;
+import nicobrowser.search.SearchResult;
import nicobrowser.util.Result;
import nicobrowser.util.Util;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
+import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.RedirectLocations;
import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
*/
public class NicoHttpClient {
- private static Log log = LogFactory.getLog(NicoHttpClient.class);
+ private static Log logger = LogFactory.getLog(NicoHttpClient.class);
private final DefaultHttpClient http;
private static final String LOGIN_PAGE =
"https://secure.nicovideo.jp/secure/login?site=niconico";
private static final String MOVIE_THUMBNAIL_PAGE_HEADER =
"http://www.nicovideo.jp/api/getthumbinfo/";
private static final String GET_FLV_INFO = "http://www.nicovideo.jp/api/getflv/";
- private static final String SEARCH_HEAD = "http://www.nicovideo.jp/search/";
- private static final String SEARCH_TAIL = "?sort=v";
+ private static final String SEARCH_HEAD = "http://www.nicovideo.jp/";
private static final String ADD_MYLIST_PAGE = "http://www.nicovideo.jp/mylist_add/video/";
+ private static final String GET_THREAD_KEY_PAGE = "http://www.nicovideo.jp/api/getthreadkey?thread=";
public NicoHttpClient() {
http = new DefaultHttpClient();
}
/**
+ * プロキシサーバを経由してアクセスする場合のコンストラクタ.
+ * @param host プロキシサーバのホスト名.
+ * @param port プロキシサーバで利用するポート番号.
+ */
+ public NicoHttpClient(String host, int port) {
+ this();
+ HttpHost proxy = new HttpHost(host, port);
+ http.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
+ }
+
+ /**
* ニコニコ動画へログインする.
* @param mail ログイン識別子(登録メールアドレス).
* @param password パスワード.
//post.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
HttpResponse response = http.execute(post);
- log.debug("ログインステータスコード: " + response.getStatusLine().getStatusCode());
+ logger.debug("ログインステータスコード: " + response.getStatusLine().getStatusCode());
// ログイン可否の判定.
HttpEntity entity = response.getEntity();
- entity.consumeContent();
+ EntityUtils.consume(entity);
List<Cookie> cookies = http.getCookieStore().getCookies();
if (!cookies.isEmpty()) {
auth = true;
}
} catch (IOException ex) {
- log.error("ログイン時に問題が発生", ex);
+ logger.error("ログイン時に問題が発生", ex);
}
return auth;
}
HttpGet method = new HttpGet(LOGOUT_PAGE);
try {
HttpResponse response = http.execute(method);
- log.debug("ログアウトステータスコード: " + response.getStatusLine().getStatusCode());
+ logger.debug("ログアウトステータスコード: " + response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
result = true;
}
- response.getEntity().consumeContent();
+ EntityUtils.consume(response.getEntity());
} catch (IOException ex) {
- log.error("ログアウト時に問題が発生", ex);
+ logger.error("ログアウト時に問題が発生", ex);
}
return result;
}
/**
* キーワード検索を行う.
* @param word 検索キーワード
+ * @param sort ソート種別
+ * @param order ソート順
+ * @page 検索結果ページのうち, 結果を返すページ.
* @return 検索結果.
*/
- public List<NicoContent> search(String word) {
- log.debug("検索:" + word);
+ public SearchResult search(SearchKind kind, String word, SortKind sort, SortOrder order, int page) throws
+ IOException {
+ logger.debug("検索:" + word);
InputStream is = null;
- List<NicoContent> conts = new ArrayList<NicoContent>();
- String url = new String(SEARCH_HEAD + word + SEARCH_TAIL);
+ ArrayList<NicoContent> conts = new ArrayList<NicoContent>();
+ String url = SEARCH_HEAD + kind.getKey() + "/" + URLEncoder.encode(word, "UTF-8") + "?page=" + Integer.toString(
+ page) + "&sort=" + sort.getKey() + "&order=" + order.getKey();
try {
- while (url != null) {
- HttpGet get = new HttpGet(url);
- HttpResponse response;
- response = http.execute(get);
- is = new BufferedInputStream(response.getEntity().getContent());
- assert is.markSupported();
- is.mark(1024 * 1024);
- List<Result> results = Util.parseSerchResult(is);
- for (Result r : results) {
- NicoContent c = loadMyMovie(r.getId());
- if (c != null) {
- conts.add(c);
- }
+ HttpGet get = new HttpGet(url);
+ HttpResponse response;
+ response = http.execute(get);
+ is = new BufferedInputStream(response.getEntity().getContent());
+ assert is.markSupported();
+ is.mark(1024 * 1024);
+ List<Result> results = Util.parseSearchResult(is);
+ for (Result r : results) {
+ NicoContent c = loadMyMovie(r.getId());
+ if (c != null) {
+ conts.add(c);
}
- is.reset();
- url = Util.getNextPage(is);
- is.close();
}
+ is.reset();
+ TreeMap<Integer, String> otherPages = Util.getOtherPages(is);
+ return new SearchResult(conts, otherPages);
} catch (IOException ex) {
- log.error("検索結果処理時に例外発生", ex);
+ logger.error("検索結果処理時に例外発生", ex);
+ throw ex;
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ex) {
+ }
+ }
}
- return conts;
}
/**
*/
public List<NicoContent> loadMyListDaily() throws URISyntaxException, HttpException, InterruptedException {
List<NicoContent> list = new ArrayList<NicoContent>();
- String url = new String("http://www.nicovideo.jp/ranking/mylist/daily/all?rss=atom");
- log.debug("全動画サイトのマイリスト登録数ランキング(本日)[全体] : " + url);
+ String url = "http://www.nicovideo.jp/ranking/mylist/daily/all?rss=atom";
+ logger.debug("全動画サイトのマイリスト登録数ランキング(本日)[全体] : " + url);
HttpGet get = new HttpGet(url);
// reader.skip(1);
list = getNicoContents(reader);
deleteRankString(list);
- response.getEntity().consumeContent();
+ EntityUtils.consume(response.getEntity());
} catch (FeedException ex) {
- log.error("", ex);
+ logger.error("", ex);
} catch (IOException ex) {
- log.error("", ex);
+ logger.error("", ex);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
- log.error("", ex);
+ logger.error("", ex);
}
}
}
* @return コンテンツリスト.
*/
public List<NicoContent> getContentsFromRss(String url) {
- log.debug("アクセスURL: " + url);
+ logger.debug("アクセスURL: " + url);
List<NicoContent> list = accessRssUrl(url);
if (url.contains("ranking")) {
deleteRankString(list);
}
/**
+ * 過去ログ取得用のキーを取得します.
+ * @param vi {@link #getVideoInfo(java.lang.String) }で取得したオブジェクト.
+ * @return 過去ログ取得用キー
+ * @throws IOException 取得に失敗した場合.
+ */
+ public String getWayBackKey(VideoInfo vi) throws IOException {
+ final String url = "http://flapi.nicovideo.jp/api/getwaybackkey?thread=" + vi.getThreadId();
+ final HttpGet get = new HttpGet(url);
+ HttpResponse response = http.execute(get);
+ String res;
+ try {
+ final int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != HttpStatus.SC_OK) {
+ throw new IOException("waybackkey get error " + statusCode);
+ }
+
+ final BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
+ res = br.readLine();
+ logger.debug("wayback get result text: " + res);
+ } finally {
+ EntityUtils.consume(response.getEntity());
+ }
+
+ final String keyWayBackKey = "waybackkey";
+ final String[] keyValues = res.split("&");
+ for (String s : keyValues) {
+ final String[] kv = s.split("=");
+ if (keyWayBackKey.equals(kv[0])) {
+ return kv[1];
+ }
+ }
+
+ throw new IOException("wayback key get fail: " + res);
+ }
+
+ /**
* rankingの場合、本当のタイトルの前に"第XX位:"の文字列が
* 挿入されているため, それを削る.
* @param list 対象のリスト.
* @return 動画一覧.
*/
public List<NicoContent> loadMyList(String listNo) {
- String url = new String(MY_LIST_PAGE_HEADER + listNo + "?rss=atom");
- log.debug("マイリストURL: " + url);
+ String url = MY_LIST_PAGE_HEADER + listNo + "?rss=atom";
+ logger.debug("マイリストURL: " + url);
return accessRssUrl(url);
}
/**
+ * コンテンツ概略のストリームを取得する.
+ * @param movieNo
+ * @return コンテンツ概略. 取得元でcloseすること.
+ * @throws IOException
+ */
+ public InputStream getThumbInfo(String movieNo) throws IOException {
+ String url = MOVIE_THUMBNAIL_PAGE_HEADER + movieNo;
+ logger.debug("動画サムネイルURL: " + url);
+
+ HttpGet get = new HttpGet(url);
+ HttpResponse response = http.execute(get);
+ return response.getEntity().getContent();
+
+ }
+
+ /**
* 動画番号を指定したコンテンツ情報の取得.
* @param movieNo 動画番号.
* @return コンテンツ情報.
public NicoContent loadMyMovie(String movieNo) {
NicoContent cont = null;
InputStream re = null;
- List<SyndEntryImpl> list = null;
- String url = new String(MOVIE_THUMBNAIL_PAGE_HEADER + movieNo);
- log.debug("動画サムネイルURL: " + url);
-
- HttpGet get;
try {
- get = new HttpGet(url);
- HttpResponse response = http.execute(get);
- re = response.getEntity().getContent();
+ re = getThumbInfo(movieNo);
// ドキュメントビルダーファクトリを生成
DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
// ドキュメントビルダーを生成
Element root = doc.getDocumentElement();
if ("fail".equals(root.getAttribute("status"))) {
- log.warn("情報取得できません: " + movieNo);
+ logger.warn("情報取得できません: " + movieNo);
return null;
}
// } catch (ParseException ex) {
// Logger.getLogger(NicoHttpClient.class.getName()).log(Level.SEVERE, null, ex);
} catch (SAXException ex) {
- log.error("", ex);
+ logger.error("", ex);
} catch (IOException ex) {
- log.error("", ex);
+ logger.error("", ex);
} catch (ParserConfigurationException ex) {
- log.error("", ex);
+ logger.error("", ex);
} finally {
try {
if (re != null) {
re.close();
}
} catch (IOException ex) {
- log.error("", ex);
+ logger.error("", ex);
}
}
return cont;
try {
HttpResponse response = http.execute(get);
reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
- if (log.isTraceEnabled()) {
+ if (logger.isTraceEnabled()) {
reader.mark(1024 * 1024);
while (true) {
String str = reader.readLine();
if (str == null) {
break;
}
- log.trace(str);
+ logger.trace(str);
}
reader.reset();
}
contList = getNicoContents(reader);
} catch (FeedException ex) {
- log.warn("アクセスできません: " + url);
- log.debug("", ex);
+ logger.warn("アクセスできません: " + url);
+ logger.debug("", ex);
} catch (IOException ex) {
- log.error("", ex);
+ logger.error("", ex);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
- log.error("", ex);
+ logger.error("", ex);
}
}
}
}
private List<NicoContent> getNicoContents(Reader reader) throws FeedException {
- List<SyndEntryImpl> list = null;
SyndFeedInput input = new SyndFeedInput();
SyndFeed feed = input.build(reader);
- list = (List<SyndEntryImpl>) feed.getEntries();
+ @SuppressWarnings("unchecked")
+ final List<SyndEntryImpl> list = (List<SyndEntryImpl>) feed.getEntries();
List<NicoContent> contList;
if (list == null) {
return contList;
}
+ @SuppressWarnings("unchecked")
private List<NicoContent> createContentsList(List<SyndEntryImpl> list) {
class CallBack extends HTMLEditorKit.ParserCallback {
@Override
public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
- log.debug("--------<" + t.toString() + ">--------");
- log.debug(a);
+ logger.debug("--------<" + t.toString() + ">--------");
+ logger.debug(a);
if (HTML.Tag.IMG.equals(t)) {
imageLink = a.getAttribute(HTML.Attribute.SRC).toString();
}
descFlag = true;
}
}
- log.debug("--------<" + t.toString() + ">--------");
- log.debug(a);
+ logger.debug("--------<" + t.toString() + ">--------");
+ logger.debug(a);
}
@Override
if (HTML.Tag.P.equals(t)) {
descFlag = false;
}
- log.debug("--------</" + t.toString() + ">--------");
+ logger.debug("--------</" + t.toString() + ">--------");
}
@Override
if (descFlag) {
description.append(data);
}
- log.debug("--------TEXT--------");
- log.debug(data);
+ logger.debug("--------TEXT--------");
+ logger.debug(data);
}
private void printAttributes(MutableAttributeSet a) {
- Enumeration e = a.getAttributeNames();
+ final Enumeration<?> e = a.getAttributeNames();
while (e.hasMoreElements()) {
Object key = e.nextElement();
- log.debug("---- " + key.toString() + " : " + a.getAttribute(key));
+ logger.debug("---- " + key.toString() + " : " + a.getAttribute(key));
}
}
Reader reader = new StringReader(sc.getValue());
new ParserDelegator().parse(reader, callBack, true);
} catch (IOException ex) {
- log.error("RSSの読み込み失敗: " + content.getTitle());
+ logger.error("RSSの読み込み失敗: " + content.getTitle());
}
}
/**
* FLVファイルのURLを取得する. ログインが必要.
* また, 実際にFLVファイルの実態をダウンロードするには
- * 一度http://www.nicovideo.jp/watch/ビデオIDに一度アクセスする必要があることに
+ * 一度http://www.nicovideo.jp/watch/ビデオID に一度アクセスする必要があることに
* 注意.
* (参考: http://yusukebe.com/tech/archives/20070803/124356.html)
* @param videoId ニコニコ動画のビデオID.
* @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
*/
public VideoInfo getVideoInfo(String videoId) throws IOException {
- final String realVideoId = getRealVideoId(videoId);
+ final GetRealVideoIdResult res = accessWatchPage(videoId);
+ final String realVideoId = res.videoId;
String accessUrl = GET_FLV_INFO + realVideoId;
if (realVideoId.startsWith("nm")) {
accessUrl += "?as3=1";
}
- log.debug("アクセス: " + accessUrl);
+ Map<String, String> map = getParameterMap(accessUrl);
+
+ LinkedHashMap<String, String> keyMap = new LinkedHashMap<String, String>();
+ if ("1".equals(map.get("needs_key"))) {
+ // 公式動画投稿者コメント取得用パラメータ.
+ keyMap = getParameterMap(GET_THREAD_KEY_PAGE + map.get(VideoInfo.KEY_THREAD_ID));
+ }
+ return new VideoInfo(realVideoId, res.title, map, keyMap);
+ }
+
+ private LinkedHashMap<String, String> getParameterMap(String accessUrl) throws IOException, IllegalStateException {
+ logger.debug("アクセス: " + accessUrl);
HttpGet get = new HttpGet(accessUrl);
String resultString;
BufferedReader reader = null;
try {
HttpResponse response = http.execute(get);
reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
-
String str;
StringBuilder strBuilder = new StringBuilder();
while ((str = reader.readLine()) != null) {
strBuilder.append(str);
}
resultString = strBuilder.toString();
- response.getEntity().consumeContent();
- log.debug(resultString);
+ EntityUtils.consume(response.getEntity());
+ logger.debug(resultString);
} finally {
if (reader != null) {
reader.close();
}
}
-
String[] params = resultString.split("&");
-// final String marker = "url=";
- Map<String, String> map = new HashMap<String, String>();
+ LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
for (String param : params) {
String[] elm = param.split("=");
map.put(elm[0], elm[1]);
- // if (url.contains(marker)) {
- // String result = url.substring(marker.length());
- // result = URLDecoder.decode(result, "UTF-8");
- //
- // return new URL(result);
- // }
}
-// throw new IOException("ダウンロードに失敗しました(削除済みの可能性もあります)。 ID: " + videoID + ", パラメータ:" + resultString);
- return new VideoInfo(realVideoId, map);
+ return map;
+ }
+
+ /**
+ * watchページコンテンツからタイトルを抽出する.
+ * @param content watchページコンテンツのストリーム.
+ */
+ private String getTitleInWatchPage(InputStream content) throws IOException {
+ final String TITLE_PARSE_STR_START = "<title>";
+ BufferedReader br = new BufferedReader(new InputStreamReader(content, "UTF-8"));
+ String ret;
+ while ((ret = br.readLine()) != null) {
+ final int index = ret.indexOf(TITLE_PARSE_STR_START);
+ if (index >= 0) {
+ String videoTitle = ret.substring(index + TITLE_PARSE_STR_START.length(), ret.indexOf(" ‐", index));
+ return videoTitle;
+ }
+ }
+ return "";
+
+ }
+
+ private static class GetRealVideoIdResult {
+
+ private final String videoId;
+ private final String title;
+
+ private GetRealVideoIdResult(String videoId, String title) {
+ this.videoId = videoId;
+ this.title = title;
+ }
}
- private String getRealVideoId(String videoId) throws IOException {
+ /**
+ * WATCHページへアクセスする. getflvを行うためには, 必ず事前にWATCHページへアクセスしておく必要があるため.
+ * WATCHページ参照時にリダイレクトが発生する(so動画ではスレッドIDのWATCHページにリダイレクトされる)場合には
+ * そちらのページにアクセスし、そのスレッドIDをrealIdとして返します.
+ * @param videoId 取得したいビデオのビデオID.
+ * @return 実際のアクセスに必要なIDと、タイトル. タイトルはいんきゅばす互換用です.
+ * @throws IOException アクセスに失敗した場合. 有料動画などがこれに含まれます.
+ */
+ private GetRealVideoIdResult accessWatchPage(String videoId) throws IOException {
+ String realId = videoId;
+ String title;
String watchUrl = WATCH_PAGE + videoId;
- log.debug("アクセス: " + watchUrl);
- http.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
+ logger.debug("アクセス: " + watchUrl);
+ final HttpGet get = new HttpGet(watchUrl);
+ final HttpContext context = new BasicHttpContext();
+ final HttpResponse response = http.execute(get, context);
try {
- HttpGet get = new HttpGet(watchUrl);
- HttpResponse response = http.execute(get);
- String realID = videoId;
-
- // ステータスコード302など、リダイレクトが必要な場合
- if (response.containsHeader("Location")) {
- realID = response.getFirstHeader("Location").getValue().replace("/watch/", "");
- response.getEntity().consumeContent();
- watchUrl = WATCH_PAGE + realID;
- log.debug("アクセス: " + watchUrl);
- HttpGet watchGet = new HttpGet(watchUrl);
- HttpResponse watchResponse = http.execute(get);
- watchResponse.getEntity().consumeContent();
- } else {
- response.getEntity().consumeContent();
+ final RedirectLocations rl = (RedirectLocations) context.getAttribute(
+ "http.protocol.redirect-locations");
+ // 通常の動画(sm動画など)はリダイレクトが発生しないためnullになる
+ if (rl != null) {
+ final List<URI> locations = rl.getAll();
+ logger.debug("リダイレクト数: " + locations.size());
+
+ // so動画はスレッドIDのページへリダイレクトされる
+ if (locations.size() == 1) {
+ realId = locations.get(0).toString().replace(WATCH_PAGE, "");
+ } else if (locations.size() > 1) {
+ throw new IOException("有料動画と思われるため処理を中断しました: " + ArrayUtils.toString(locations));
+ }
}
- return realID;
+
+ title = getTitleInWatchPage(response.getEntity().getContent());
} finally {
- http.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, true);
+ EntityUtils.consume(response.getEntity());
}
+ return new GetRealVideoIdResult(realId, title);
}
/**
* ニコニコ動画から動画ファイルをダウンロードする.
- * @param videoId smxxxx形式のビデオID.
- * @param fileName ダウンロード後のファイル名. 拡張子は別途付与されるため不要.
+ * @param vi getVideoInfoメソッドで取得したオブジェクト.
+ * @param saveDir ダウンロードしたファイルを保存するディレクトリ.
+ * @param np 保存するファイル名の命名規則. 拡張子は別途付与されるため不要.
* @param nowStatus ダウンロードしようとしている動画ファイルの, 現在のステータス.
* @param needLowFile エコノミー動画をダウンロードするのであればtrue.
* @return この処理を行った後の, 対象ファイルのステータス.
* @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
*/
- public GetFlvResult getFlvFile(String videoID, String fileName, Status nowStatus, boolean needLowFile) throws
- IOException,
- URISyntaxException, HttpException, InterruptedException {
- VideoInfo vi = getVideoInfo(videoID);
+ public GetFlvResult getFlvFile(VideoInfo vi, File saveDir, NamePattern np, Status nowStatus, boolean needLowFile,
+ ProgressListener listener) throws IOException, URISyntaxException, HttpException, InterruptedException {
final URL notifierUrl = vi.getSmileUrl();
HttpGet get = new HttpGet(notifierUrl.toString());
HttpResponse response = http.execute(get);
userName = Util.getUserName(response.getEntity().getContent());
- response.getEntity().consumeContent();
+ EntityUtils.consume(response.getEntity());
}
final URL url = vi.getVideoUrl();
if (nowStatus == Status.GET_LOW || !needLowFile) {
if (url.toString().contains("low")) {
- log.info("エコノミー動画のためスキップ: " + videoID);
- return new GetFlvResult(nowStatus, userName);
+ logger.info("エコノミー動画のためスキップ: " + vi.getRealVideoId());
+ return new GetFlvResult(null, nowStatus, userName);
}
}
+ final boolean isNotLow = !url.toString().contains("low");
+
+ final File downloadFile = new File(saveDir, np.createFileName(vi.getRealVideoId(), isNotLow));
HttpGet get = new HttpGet(url.toURI());
HttpResponse response = http.execute(get);
String contentType = response.getEntity().getContentType().getValue();
- log.debug(contentType);
- log.debug(fileName);
+ logger.debug(contentType);
+ logger.debug(downloadFile.toString());
if ("text/plain".equals(contentType) || "text/html".equals(contentType)) {
- log.error("取得できませんでした. サーバが混みあっている可能性があります: " + videoID);
- response.getEntity().consumeContent();
- return new GetFlvResult(Status.GET_INFO, userName);
+ logger.error("取得できませんでした. サーバが混みあっている可能性があります: " + vi.getRealVideoId());
+ EntityUtils.consume(response.getEntity());
+ return new GetFlvResult(null, Status.GET_INFO, userName);
}
String ext = Util.getExtention(contentType);
final long fileSize = response.getEntity().getContentLength();
final int BUF_SIZE = 1024 * 32;
BufferedInputStream in = new BufferedInputStream(response.getEntity().getContent());
- File file = new File(fileName + "." + ext);
- log.info("保存します(" + fileSize / 1024 + "KB): " + file.getPath());
+ File file = new File(downloadFile.toString() + "." + ext);
+ logger.info("保存します(" + fileSize / 1024 + "KB): " + file.getPath());
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream out = new BufferedOutputStream(fos);
+ long downloadSize = 0;
int i;
byte[] buffer = new byte[BUF_SIZE];
while ((i = in.read(buffer)) != -1) {
out.write(buffer, 0, i);
+ downloadSize += i;
+ listener.progress(fileSize, downloadSize);
+ if (listener.getCancel()) {
+ return new GetFlvResult(null, Status.GET_INFO, userName);
+ }
}
- response.getEntity().consumeContent();
+ EntityUtils.consume(response.getEntity());
out.close();
in.close();
if (url.toString().contains("low")) {
- return new GetFlvResult(Status.GET_LOW, userName);
+ return new GetFlvResult(file, Status.GET_LOW, userName);
}
- return new GetFlvResult(Status.GET_FILE, userName);
+ return new GetFlvResult(file, Status.GET_FILE, userName);
}
/**
* ニコニコ動画から動画ファイルをダウンロードする.
- * @param videoId smxxxx形式のビデオID.
+ * @param vi getVideoInfoメソッドで取得したオブジェクト.
* @param fileName ダウンロード後のファイル名. 拡張子は別途付与されるため不要.
+ * @param nowStatus ダウンロードしようとしている動画ファイルの, 現在のステータス.
+ * @param needLowFile エコノミー動画をダウンロードするのであればtrue.
* @return この処理を行った後の, 対象ファイルのステータス.
* @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
*/
- public GetFlvResult getFlvFile(String videoID, String fileName) throws IOException, URISyntaxException,
+ public GetFlvResult getFlvFile(VideoInfo vi, String fileName, Status nowStatus, boolean needLowFile,
+ ProgressListener listener) throws IOException, URISyntaxException, HttpException, InterruptedException {
+ String file = FilenameUtils.getName(fileName);
+ String dir = fileName.substring(0, fileName.length() - file.length());
+ NamePattern np = new NamePattern(file, "", "", "");
+ return getFlvFile(vi, new File(dir), np, nowStatus, needLowFile, listener);
+ }
+
+ /**
+ * ニコニコ動画から動画ファイルをダウンロードする.
+ * @param vi getVideoInfoメソッドで取得したオブジェクト.
+ * @param fileName ダウンロード後のファイル名. 拡張子は別途付与されるため不要.
+ * @return この処理を行った後の, 対象ファイルのステータス.
+ * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
+ */
+ public GetFlvResult getFlvFile(VideoInfo vi, String fileName, ProgressListener listener) throws IOException,
+ URISyntaxException,
HttpException, InterruptedException {
- return getFlvFile(videoID, fileName, Status.GET_INFO, true);
+ return getFlvFile(vi, fileName, Status.GET_INFO, true, listener);
}
/**
* ニコニコ動画から動画ファイルをダウンロードする.
* ファイル名はビデオID名となる.
- * @param videoId smxxxx形式のビデオID.
+ * @param vi getVideoInfoメソッドで取得したオブジェクト.
* @return この処理を行った後の, 対象ファイルのステータス.
* @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
*/
- public GetFlvResult getFlvFile(String videoID) throws IOException, URISyntaxException, HttpException,
+ public GetFlvResult getFlvFile(VideoInfo vi) throws IOException, URISyntaxException, HttpException,
InterruptedException {
- return getFlvFile(videoID, videoID, Status.GET_INFO, true);
+ return getFlvFile(vi, vi.getRealVideoId(), Status.GET_INFO, true, ProgressListener.EMPTY_LISTENER);
+ }
+
+ public File getCommentFile(VideoInfo vi, String fileName, WayBackInfo wayback, int commentNum, boolean oldVersion)
+ throws Exception {
+ return downloadComment(vi, fileName, false, wayback, Integer.valueOf(commentNum), oldVersion);
+ }
+
+ public File getCommentFile(VideoInfo vi, String fileName) throws Exception {
+ return downloadComment(vi, fileName, false, null, null, false);
+ }
+
+ public File getTCommentFile(VideoInfo vi, String fileName) throws Exception {
+ return downloadComment(vi, fileName, true, null, Integer.valueOf(1000), true);
+ }
+
+ private File downloadComment(VideoInfo vi, String fileName, boolean isTcomm, WayBackInfo wayback, Integer commentNum,
+ boolean oldVersion)
+ throws Exception {
+ HttpResponse response = null;
+ BufferedOutputStream bos = null;
+
+ try {
+ final HttpPost post = new HttpPost(vi.getMessageUrl().toString());
+ final String param;
+ if (oldVersion || isTcomm) {
+ param = createCommentDownloadParameter20101222(vi, isTcomm, wayback, commentNum);
+ } else {
+ param = createCommentDownloadParameter(vi, wayback, commentNum);
+ }
+ final StringEntity se = new StringEntity(param);
+ post.setEntity(se);
+ response = http.execute(post);
+ final InputStream is = response.getEntity().getContent();
+ final BufferedInputStream bis = new BufferedInputStream(is);
+
+ final String outputFileName = (fileName.endsWith(".xml")) ? fileName : fileName + ".xml";
+ bos = new BufferedOutputStream(new FileOutputStream(outputFileName));
+
+ final byte[] buf = new byte[1024 * 1024];
+ int read;
+ while ((read = bis.read(buf, 0, buf.length)) > 0) {
+ bos.write(buf, 0, read);
+ }
+
+ return new File(outputFileName);
+ } catch (Exception e) {
+ throw new Exception("コメントダウンロードに失敗しました。", e);
+ } finally {
+ if (response != null) {
+ EntityUtils.consume(response.getEntity());
+ }
+ if (bos != null) {
+ bos.close();
+ }
+ }
+ }
+
+ /**
+ * 2011/2/3 以降のコメント表示仕様に基づいた取得パラメータ生成.
+ * @param vi ビデオ情報.
+ * @param wayback 過去ログ情報. 過去ログ取得でないバイはnull.
+ * @param commentNum コメント取得数. ビデオ再生時間に応じたデフォルト値を用いる場合にはnull.
+ * @return 生成されたパラメータ.
+ */
+ private String createCommentDownloadParameter(VideoInfo vi, WayBackInfo wayback, Integer commentNum) {
+ final Map<String, String> threadKey = vi.getKeyMap();
+ final Map<String, String> th = new HashMap<String, String>();
+ th.put("thread", vi.getThreadId());
+ th.put("version", "20090904");
+ th.put("user_id", vi.getUserId());
+ if (wayback != null) {
+ th.put("waybackkey", wayback.getKey());
+ th.put("when", Long.toString(wayback.getTime()));
+ }
+
+ final Map<String, String> leaf = new HashMap<String, String>();
+ leaf.put("thread", vi.getThreadId());
+ leaf.put("user_id", vi.getUserId());
+ if (wayback != null) {
+ leaf.put("waybackkey", wayback.getKey());
+ leaf.put("when", Long.toString(wayback.getTime()));
+ }
+
+ final int minutes = (int) Math.ceil(vi.getVideoLength() / 60.0);
+ // 1分当たり100件のコメントを表示するのは720分未満の動画だけで, それ以上は調整が入るらしい
+ // (どんなに長くても1動画当たり720*100件が最大。それを超える場合には1分当たりの件数を削減する)
+ final int max100perMin = 720;
+ final int perMin = (minutes < max100perMin) ? 100 : (max100perMin * 100) / minutes;
+
+ final int resFrom = (commentNum != null) ? commentNum.intValue() : vi.getResFrom();
+ final String element = "0-" + minutes + ":" + perMin + "," + resFrom;
+
+ final StringBuilder str = new StringBuilder();
+ str.append("<packet>");
+
+ str.append("<thread");
+ addMapToAttr(str, th);
+ addMapToAttr(str, threadKey);
+ str.append(" />");
+
+ str.append("<thread_leaves");
+ addMapToAttr(str, leaf);
+ addMapToAttr(str, threadKey);
+ str.append(">");
+ str.append(element);
+ str.append("</thread_leaves>");
+
+ str.append("</packet>");
+
+ return str.toString();
+ }
+
+ private static void addMapToAttr(final StringBuilder str, final Map<String, String> map) {
+ final String quote = "\"";
+ for (String k : map.keySet()) {
+ final String v = map.get(k);
+ str.append(" ");
+ str.append(k);
+ str.append("=");
+ str.append(quote);
+ str.append(v);
+ str.append(quote);
+ }
+ }
+
+ /**
+ * 2010/12/22 までのコメント表示仕様に基づいた取得パラメータ生成.
+ * 「コメントの量を減らす」にチェックを入れた場合は現在でもこれが用いられているはず.
+ */
+ private String createCommentDownloadParameter20101222(VideoInfo vi, boolean isTcomm, WayBackInfo wayback,
+ Integer commentNum) {
+ final Map<String, String> params = new HashMap<String, String>();
+
+ params.put(VideoInfo.KEY_USER_ID, vi.getUserId());
+ params.put("thread", vi.getThreadId());
+ params.put("version", "20061206");
+
+ final int resFrom = (commentNum == null || commentNum <= 0) ? vi.getResFrom() : commentNum.intValue();
+ params.put("res_from", "-" + Integer.toString(resFrom));
+
+ if (isTcomm) {
+ params.put("fork", "1");
+ }
+
+ if (wayback != null) {
+ params.put("waybackkey", wayback.getKey());
+ params.put("when", Long.toString(wayback.getTime()));
+ }
+
+ final StringBuilder str = new StringBuilder();
+ str.append("<thread");
+
+ addMapToAttr(str, vi.getKeyMap());
+ addMapToAttr(str, params);
+
+ str.append("/>");
+
+ return str.toString();
}
/**
}
}
} finally {
- entity.consumeContent();
+ EntityUtils.consume(entity);
}
if (itemType == null || itemId == null || token == null) {
post.setEntity(se);
response = http.execute(post);
int statusCode = response.getStatusLine().getStatusCode();
- response.getEntity().consumeContent();
- if (statusCode != 200) {
+ EntityUtils.consume(response.getEntity());
+ if (statusCode != HttpStatus.SC_OK) {
throw new IOException("マイリスト登録に失敗" + "マイリスト:" + myListId + ", 動画ID:" + videoId);
}
}