1 package saccubus.worker.impl.download;
3 import static saccubus.worker.impl.download.DownloadStatus.*;
6 import java.io.IOException;
7 import java.net.URISyntaxException;
9 import java.util.logging.Logger;
10 import nicobrowser.GetFlvResult;
11 import nicobrowser.NamePattern;
12 import nicobrowser.NicoHttpClient;
13 import nicobrowser.ProgressListener;
14 import nicobrowser.WayBackInfo;
15 import nicobrowser.entity.NicoContent.Status;
16 import org.apache.http.HttpException;
17 import saccubus.worker.Worker;
18 import saccubus.worker.WorkerListener;
19 import saccubus.worker.profile.CommentProfile;
20 import saccubus.worker.profile.GeneralProfile;
21 import saccubus.worker.profile.DownloadProfile;
22 import saccubus.worker.profile.ProxyProfile;
27 * <p>説明: ニコニコ動画の動画をコメントつきで保存</p>
29 * <p>著作権: Copyright (c) 2007 PSI</p>
36 public class Download extends Worker<DownloadResult, DownloadProgress> {
38 private static final Logger logger = Logger.getLogger(Download.class.getName());
39 private static final Object timeLockObj = new Object();
40 private static volatile long lastStartTime = 0L;
41 private final DownloadProfile profile;
42 private final String videoId;
44 public Download(DownloadProfile profile, String videoId) {
45 this(profile, videoId, null);
50 * @param videoId 対象となる動画のID.
56 public Download(DownloadProfile profile, String videoId, WorkerListener<DownloadResult, DownloadProgress> listener) {
59 this.videoId = videoId;
60 this.profile = profile;
64 // public Boolean call() throws Exception {
66 // final DownloadResult result = doInBackground();
67 // return Boolean.valueOf(result.getResultValue());
70 //// getStopFlag().finished();
73 // // TODO Runnableを実装しなくなったので削除する
74 // public void run() {
77 // } catch (Exception ex) {
78 // String text = (ex.getMessage() != null) ? ex.getMessage() : "予期しないエラー発生のため中断しました。";
80 // logger.log(Level.SEVERE, null, ex);
84 public DownloadResult work() throws Exception {
87 publish(new DownloadProgress(PROCESS, 0.0, "ログイン中"));
89 NicoHttpClient client = null;
90 nicobrowser.VideoInfo vi = null;
91 NamePattern videoNamePattern = null;
92 WayBackInfo wbi = null;
94 client = createClientAndLogin();
95 vi = client.getVideoInfo(videoId);
97 final String name = profile.getVideoProfile().getFileName();
98 final String replaceFrom = profile.getGeneralProfile().getReplaceFrom();
99 final String replaceTo = profile.getGeneralProfile().getReplaceTo();
100 videoNamePattern = new NamePattern(name, replaceFrom, replaceTo, vi.getTitleInWatchPage());
102 if (needsBackLog()) {
103 final String key = client.getWayBackKey(vi);
104 wbi = new WayBackInfo(key, profile.getCommentProfile().getBackLogPoint());
111 if (profile.getCommentProfile().isDownload()) {
112 final CommentProfile prof = profile.getCommentProfile();
113 final GeneralProfile gene = profile.getGeneralProfile();
115 final NamePattern pattern = new NamePattern(prof.getFileName(), gene.getReplaceFrom(), gene.getReplaceTo(),
116 vi.getTitleInWatchPage());
117 // TODO コメントファイルに{low}は使えないことをどこかに書くべきか
118 final String name = pattern.createFileName(videoId, true);
119 final File file = new File(profile.getCommentProfile().getDir(), name);
121 commentFile = client.getCommentFile(vi, file.getPath(), wbi, profile.getCommentProfile().
122 getLengthRelatedCommentSize(),
123 profile.getCommentProfile().isDisablePerMinComment());
125 commentFile = profile.getCommentProfile().getLocalFile();
132 if (profile.getVideoProfile().isDownload()) {
133 vf = client.getFlvFile(vi, profile.getVideoProfile().getDir(), videoNamePattern,
134 Status.GET_INFO, true, new ProgressListener() {
137 public void progress(long fileSize, long downloadSize) {
138 final double vol = (double) downloadSize / (double) fileSize * 100.0;
139 publish(new DownloadProgress(PROCESS, vol, String.format("ダウンロード%.2f%%", vol)));
143 videoFile = vf.getFile();
145 videoFile = profile.getVideoProfile().getLocalFile();
147 return new DownloadResult(true, videoFile, commentFile);
150 // TODO FFMPEG 実行開始は別タスクとして実装する.
151 // if (!profile.getOutputFileSetting().isConvert()) {
152 // publish(new DownloadProgress("動画・コメントを保存し、変換は行いませんでした。"));
153 // return new DownloadResult(true);
156 // if (!videoFile.isFile()) {
157 // throw new IOException("入力動画ファイルが存在しません:" + videoFile.getPath());
160 // if (profile.getOutputFileSetting().isAddComment()) {
161 // if (!commentFile.isFile()) {
162 // throw new IOException("入力コメントファイルが存在しません:" + commentFile.getPath());
165 // commentFile = null;
169 // final boolean isNotLow = (vf == null) ? true : (vf.getStatus() != Status.GET_LOW);
170 // File convertedVideoFile = getOutputFileName(vi.getTitleInWatchPage(), isNotLow);
173 // boolean res = new FfmpegCommand(getListener(), getStopFlag(), commentFile, videoFile,
174 // convertedVideoFile, profile.getFfmpeg(), profile.getGeneralSetting()).execute();
178 /** @return 何かダウンロードするものがあればtrue. */
179 private static boolean needsDownload(DownloadProfile profile) {
180 return (profile.getVideoProfile().isDownload() || profile.getCommentProfile().isDownload());
183 // TODO どこかに処理を移す必要がある.
185 * (ネットワーク設定以外の)設定を検証する.
186 * @throws IllegalArgumentException 設定に不備がある場合.
188 // private void validSetting() {
189 // if (profile.getOutputFileSetting().isConvert()) {
190 // File a = profile.getFfmpeg().getFfmpeg();
191 // if (!a.canRead()) {
192 // throw new IllegalArgumentException("FFmpegが見つかりません。");
194 // if (profile.getFfmpeg().getVhook().getPath().indexOf(' ') >= 0) {
195 // throw new IllegalArgumentException("すいません。現在vhookライブラリには半角空白は使えません。");
197 // a = profile.getFfmpeg().getVhook();
198 // if (!a.canRead()) {
199 // throw new IllegalArgumentException("Vhookライブラリが見つかりません。");
201 // a = profile.getFfmpeg().getFont();
202 // if (!a.canRead()) {
203 // throw new IllegalArgumentException("フォントが見つかりません。");
208 * HttpClientを生成し, ニコニコ動画サーバへログインします.
209 * @return 生成したHttpClientインスタンス.
210 * @throws IOException ログイン失敗.
211 * @throws InterruptedException ログイン失敗.
213 // TODO HttpException を投げるのをやめたい. コンパイル時にHttpComponentが必要になるので.
214 private NicoHttpClient createClientAndLogin() throws IOException, InterruptedException, HttpException {
215 final NicoHttpClient client = createClient(profile.getProxyProfile());
216 final boolean hasLogin;
218 hasLogin = client.login(profile.getLoginInfo().getMail(), profile.getLoginInfo().getPassword());
220 throw new IOException("login fail");
222 } catch (URISyntaxException ex) {
223 throw new IOException("login fail", ex);
228 private NicoHttpClient createClient(ProxyProfile proxy) {
230 return new NicoHttpClient(proxy.getHost(), proxy.getPort());
232 return new NicoHttpClient();
236 /** @return ログインする必要があればtrue. */
237 private boolean needsLogin() {
238 return profile.getVideoProfile().isDownload() || profile.getCommentProfile().isDownload();
241 /** @return 過去ログ取得の必要があればtrue. */
242 private boolean needsBackLog() {
243 return profile.getCommentProfile().getBackLogPoint() >= 0L;
246 private void checkStop() throws InterruptedException {
247 if (Thread.interrupted()) {
248 throw new InterruptedException("中止要求を受け付けました");
253 * ニコニコ動画サービスに連続アクセスするとはじかれるため, 前回のアクセス開始時刻から一定期間は
254 * 次のアクセスに行かないよう待機する必要があります.
255 * @throws InterruptedException 中断されました.
257 private void waitAndGo() throws InterruptedException {
258 synchronized (timeLockObj) {
259 final long now = new Date().getTime();
260 // TODO 30秒間隔はコンフィグ設定できた方が良い
261 final long needSleep = (30 * 1000L) - (now - lastStartTime);
263 publish(new DownloadProgress(DownloadStatus.PROCESS, -1.0, "過剰アクセス抑制待機 " + needSleep / 1000));
264 Thread.sleep(needSleep);
266 lastStartTime = new Date().getTime();