OSDN Git Service

200d20633010144eac57c0141773edd1b7b35d85
[coroid/inqubus.git] / frontend / src / saccubus / converter / FfmpegCommand.java
1 /* $Id$ */
2 package saccubus.converter;
3
4 import java.io.BufferedReader;
5 import java.io.File;
6 import java.io.IOException;
7 import java.io.InputStreamReader;
8 import java.io.UnsupportedEncodingException;
9 import java.net.URLEncoder;
10 import java.util.ArrayList;
11 import java.util.List;
12 import java.util.logging.Level;
13 import java.util.logging.Logger;
14 import javax.swing.SwingWorker;
15 import org.apache.commons.lang.StringUtils;
16 import saccubus.ConvertStopFlag;
17 import saccubus.conv.ConvertToVideoHook;
18 import saccubus.converter.profile.ConvertProfile;
19 import saccubus.converter.profile.FfmpegProfile;
20 import saccubus.converter.profile.GeneralProfile;
21 import saccubus.net.TextProgressListener;
22 import yukihane.mediainfowrapper.Info;
23 import yukihane.mediainfowrapper.MediaInfo;
24 import yukihane.mediainfowrapper.Size;
25 import saccubus.converter.profile.ConvertProfile.HideCondition;
26 import yukihane.swf.Cws2Fws;
27
28 /**
29  *
30  * @author yuki
31  */
32 public class FfmpegCommand extends SwingWorker<FfmpegCommandResult, FfmpegCommandProgress> {
33
34     private static final Logger logger = Logger.getLogger(FfmpegCommand.class.getName());
35     private final File commentMiddleFile;
36     private final File tcommMiddleFile;
37     private final File TMP_CWS;
38     private final File commentFile;
39     private final File videoFile;
40     private final File convertedVideoFile;
41     private final ConvertProfile ffmpeg;
42
43     FfmpegCommand(TextProgressListener listener, ConvertStopFlag flag, File commentFile,
44             File videoFile, File convertedVideoFile, ConvertProfile ffmpeg, GeneralProfile general) throws IOException {
45         this.commentFile = commentFile;
46         this.videoFile = videoFile;
47         this.convertedVideoFile = convertedVideoFile;
48         this.ffmpeg = ffmpeg;
49
50         File tmpDir = general.getTempDir();
51         commentMiddleFile = File.createTempFile("vhk", ".tmp", tmpDir);
52         tcommMiddleFile = File.createTempFile("tcom", ".tmp", tmpDir);
53         TMP_CWS = File.createTempFile("cws", ".swf", tmpDir);
54     }
55
56     @Override
57     protected FfmpegCommandResult doInBackground() throws Exception {
58         try {
59             return exec();
60         } finally {
61             if (commentMiddleFile.exists()) {
62                 commentMiddleFile.delete();
63             }
64             if (tcommMiddleFile.exists()) {
65                 tcommMiddleFile.delete();
66             }
67             if (TMP_CWS.exists()) {
68                 TMP_CWS.delete();
69             }
70         }
71     }
72
73     private FfmpegCommandResult exec() throws InterruptedException, IOException {
74         final HideCondition ngSetting = getFfmpeg().getNgSetting();
75         if (commentFile != null) {
76             publish(new FfmpegCommandProgress("コメントの中間ファイルへの変換中"));
77             boolean conv = ConvertToVideoHook.convert(commentFile, commentMiddleFile, ngSetting.getId(), ngSetting.
78                     getWord());
79             if (!conv) {
80                 publish(new FfmpegCommandProgress("コメント変換に失敗。ファイル名に使用できない文字が含まれているか正規表現の間違い?"));
81                 return new FfmpegCommandResult(false);
82             }
83         }
84         checkStop();
85 //        if (tcommFile != null) {
86 //            sendText("投稿者コメントの中間ファイルへの変換中");
87 //            boolean conv = ConvertToVideoHook.convert(tcommFile, tcommMiddleFile, ngSetting.getId(), ngSetting.getWord());
88 //            if (!conv) {
89 //                sendText("コメント変換に失敗。ファイル名に使用できない文字が含まれているか正規表現の間違い?");
90 //                return false;
91 //            }
92 //        }
93 //        stopFlagReturn();
94         publish(new FfmpegCommandProgress("動画の変換を開始"));
95         int code;
96         if ((code = converting_video(videoFile, convertedVideoFile, (commentFile != null), commentMiddleFile.getPath(),
97                 false, tcommMiddleFile.getPath(), getFfmpeg().getFfmpegOption())) == 0) {
98             publish(new FfmpegCommandProgress("変換が正常に終了しました。"));
99             return new FfmpegCommandResult(true);
100         } else {
101             publish(new FfmpegCommandProgress("変換エラー:" + convertedVideoFile.getPath()));
102         }
103         return new FfmpegCommandResult(false);
104     }
105
106     private int converting_video(File videoFile, File convertedVideoFile, boolean addComment, String commPath,
107             boolean addTcomment, String tcommPath, FfmpegProfile ov) throws InterruptedException, IOException {
108         File fwsFile = Cws2Fws.createFws(videoFile, TMP_CWS);
109
110         List<String> cmdList = new ArrayList<String>();
111         cmdList.add(getFfmpeg().getFfmpeg().getPath());
112         cmdList.add("-y");
113         String[] mainOptions = ov.getMainOption().split(" +");
114         for (String opt : mainOptions) {
115             if (StringUtils.isNotBlank(opt)) {
116                 cmdList.add(opt);
117             }
118         }
119         String[] inOptions = ov.getInOption().split(" +");
120         for (String opt : inOptions) {
121             if (StringUtils.isNotBlank(opt)) {
122                 cmdList.add(opt);
123             }
124         }
125         cmdList.add("-i");
126
127         if (fwsFile == null) {
128             cmdList.add(videoFile.getPath());
129         } else {
130             cmdList.add(fwsFile.getPath());
131         }
132         String[] outOptions = ov.getOutOption().split(" +");
133         for (String opt : outOptions) {
134             if (StringUtils.isNotBlank(opt)) {
135                 cmdList.add(opt);
136             }
137         }
138
139         final Info info = MediaInfo.getInfo(new File("bin", "MediaInfo"), videoFile);
140         // 4:3 なら1.33, 16:9 なら1.76
141         boolean isHD = ((double) info.getWidth() / (double) info.getHeight() > 1.5);
142         if (ov.isResize()) {
143             final Size scaled = (ov.isAdjustRatio()) ? MediaInfo.adjustSize(info, ov.getResizeWidth(), ov.
144                     getResizeHeight()) : new Size(info.getWidth(), info.getHeight());
145             cmdList.add("-s");
146             cmdList.add(scaled.getWidth() + "x" + scaled.getHeight());
147         }
148
149         List<String> avfilterArgs = getAvfilterOptions(ov.getAvfilterOption());
150         if (!getFfmpeg().isVhookDisabled()) {
151             String vhookArg = getVhookArg(addComment, commPath, addTcomment, tcommPath, isHD);
152             if (StringUtils.isNotBlank(vhookArg)) {
153                 avfilterArgs.add(vhookArg);
154             }
155         }
156
157         if (!avfilterArgs.isEmpty()) {
158             cmdList.add("-vfilters");
159             final String args = "\"" + StringUtils.join(avfilterArgs, ", ") + "\"";
160             cmdList.add(args);
161         }
162
163         cmdList.add(convertedVideoFile.getPath());
164
165         final StringBuilder argMsg = new StringBuilder();
166         argMsg.append("arg:");
167         for (String s : cmdList) {
168             argMsg.append(" ").append(s);
169         }
170         logger.log(Level.INFO, argMsg.toString());
171
172         Process process = null;
173         try {
174             logger.log(Level.INFO, "\n\n----\nProcessing FFmpeg...\n----\n\n");
175             process = Runtime.getRuntime().exec(cmdList.toArray(new String[0]));
176             BufferedReader ebr = new BufferedReader(new InputStreamReader(
177                     process.getErrorStream()));
178             String e;
179             while ((e = ebr.readLine()) != null) {
180                 String state = e;
181                 if (state.startsWith("frame=")) {
182                     publish(new FfmpegCommandProgress(state));
183                 } else if (!state.endsWith("No accelerated colorspace conversion found")) {
184                     logger.log(Level.INFO, e);
185                 }
186
187                 try {
188                     checkStop();
189                 } catch (InterruptedException ex) {
190                     throw ex;
191                 }
192
193             }
194             process.waitFor();
195             return process.exitValue();
196         } finally {
197             // TODO 正常終了した場合もdestroyしていいのか?
198             if (process != null) {
199                 process.destroy();
200             }
201             if (fwsFile != null) {
202                 fwsFile.delete();
203             }
204         }
205     }
206
207     private List<String> getAvfilterOptions(String avfilterOption) {
208         final List<String> avfilterArgs = new ArrayList<String>();
209         if (StringUtils.isNotBlank(avfilterOption)) {
210             avfilterArgs.add(avfilterOption);
211         }
212         return avfilterArgs;
213     }
214
215     private String getVhookArg(boolean addComment, String commPath, boolean addTcomment,
216             String tcommPath, boolean isHD) throws UnsupportedEncodingException {
217         StringBuilder sb = new StringBuilder();
218         sb.append("vhext=");
219         sb.append(getFfmpeg().getVhook().getPath().replace("\\", "/"));
220         if (addComment) {
221             sb.append("|");
222             sb.append("--data-user:");
223             sb.append(URLEncoder.encode(commPath.replace("\\", "/"), "Shift_JIS"));
224         }
225         if (addTcomment) {
226             sb.append("|");
227             sb.append("--data-owner:");
228             sb.append(URLEncoder.encode(tcommPath.replace("\\", "/"), "Shift_JIS"));
229         }
230         sb.append("|");
231         sb.append("--font:");
232         sb.append(URLEncoder.encode(
233                 getFfmpeg().getFont().getPath().replace("\\", "/"), "Shift_JIS"));
234         sb.append("|");
235         sb.append("--font-index:");
236         sb.append(getFfmpeg().getFontIndex());
237         sb.append("|");
238         sb.append("--show-user:");
239         sb.append(getFfmpeg().getMaxNumOfComment());
240         sb.append("|");
241         sb.append("--shadow:");
242         sb.append(getFfmpeg().getShadowIndex());
243         sb.append("|");
244         if (getFfmpeg().isShowConverting()) {
245             sb.append("--enable-show-video");
246             sb.append("|");
247         }
248         if (!getFfmpeg().isDisableFontSizeArrange()) {
249             sb.append("--enable-fix-font-size");
250             sb.append("|");
251         }
252         if (getFfmpeg().isCommentOpaque()) {
253             sb.append("--enable-opaque-comment");
254             sb.append("|");
255         }
256         if (isHD) {
257             sb.append("--aspect-mode:1");
258             sb.append("|");
259         }
260         return sb.toString();
261     }
262
263     private ConvertProfile getFfmpeg() {
264         return ffmpeg;
265     }
266
267     protected void checkStop() throws InterruptedException {
268         if (Thread.interrupted()) {
269             throw new InterruptedException("中止要求を受け付けました");
270         }
271     }
272 }