OSDN Git Service

8a5a7087c056fc23d1cbfc2c032c7a9066cfe143
[coroid/inqubus.git] / frontend / src / saccubus / worker / Convert.java
1 /* $Id$ */
2 package saccubus.worker;
3
4 import static org.apache.commons.io.FilenameUtils.getBaseName;
5 import static org.apache.commons.lang.StringUtils.*;
6
7 import java.io.BufferedReader;
8 import java.io.File;
9 import java.io.IOException;
10 import java.io.InputStreamReader;
11 import java.io.UnsupportedEncodingException;
12 import java.net.URLEncoder;
13 import java.util.ArrayList;
14 import java.util.List;
15 import java.util.logging.Level;
16 import java.util.logging.Logger;
17 import javax.swing.SwingWorker;
18 import saccubus.conv.ConvertToVideoHook;
19 import saccubus.worker.profile.ConvertProfile;
20 import saccubus.worker.profile.ConvertProfile.HideCondition;
21 import saccubus.worker.profile.FfmpegProfile;
22 import saccubus.worker.profile.GeneralProfile;
23 import saccubus.worker.profile.OutputProfile;
24 import yukihane.inqubus.util.OutputNamePattern;
25 import yukihane.mediainfowrapper.Info;
26 import yukihane.mediainfowrapper.MediaInfo;
27 import yukihane.mediainfowrapper.Size;
28 import yukihane.swf.Cws2Fws;
29
30 /**
31  * 動画を(コメント付きに)変換するワーカクラス.
32  * @author yuki
33  */
34 public class Convert extends SaccubusCallable<ConvertResult, ConvertProgress> {
35
36     private static final Logger logger = Logger.getLogger(Convert.class.getName());
37     private final ConvertProfile profile;
38     private final File videoFile;
39     private final File commentFile;
40
41     /**
42      * 変換ワーカコンストラクタ.
43      * @param profile 変換用プロファイル.
44      * @param video 変換元動画.
45      * @param comment 変換元コメント. コメントを付与しない場合はnull.
46      * @param output 変換後出力動画.
47      * @throws IOException 変換失敗.
48      */
49     public Convert(ConvertProfile profile, File video, File comment) {
50         // TODO listener登録
51         super(null);
52         this.profile = profile;
53         this.videoFile = video;
54         this.commentFile = comment;
55     }
56
57     @Override
58     public ConvertResult call() throws Exception {
59         if (!profile.isConvert()) {
60             return new ConvertResult(true, "");
61         }
62
63         final GeneralProfile gene = profile.getGeneralProfile();
64         final OutputProfile outprof = profile.getOutputProfile();
65         final OutputNamePattern pattern = new OutputNamePattern(outprof.getFileName());
66         final String id = outprof.getVideoId();
67         pattern.setId(isNotEmpty(id) ? id : "");
68         final String title = outprof.getTitile();
69         pattern.setTitle(isNotEmpty(title) ? title : "");
70         final String fileName = getBaseName(videoFile.getPath());
71         pattern.setFileName(fileName);
72         pattern.setReplaceFrom(gene.getReplaceFrom());
73         pattern.setReplaceFrom(gene.getReplaceTo());
74         final File outputFile = new File(outprof.getDir(), pattern.createFileName());
75
76         File transformedComment = null;
77         try {
78
79             if (profile.isCommentOverlay()) {
80                 transformedComment = File.createTempFile("vhk", ".tmp", profile.getTempDir());
81                 final HideCondition hide = profile.getNgSetting();
82                 publish(new ConvertProgress("コメントの中間ファイルへの変換中"));
83                 ConvertToVideoHook.convert(commentFile, transformedComment, hide.getId(), hide.getWord());
84             }
85
86             checkStop();
87             publish(new ConvertProgress("動画の変換を開始"));
88
89             final int code = convert(transformedComment, outputFile);
90             if (code != 0) {
91                 throw new IOException("ffmpeg実行失敗: " + outputFile.getPath());
92             }
93             publish(new ConvertProgress("変換が正常に終了しました。"));
94             return new ConvertResult(true, outputFile.getName());
95         } finally {
96             if (transformedComment != null && transformedComment.exists()) {
97                 transformedComment.delete();
98             }
99         }
100     }
101
102     private int convert(File transformedComment, File outputFile) throws InterruptedException, IOException {
103         File fwsFile = null;
104         try {
105             final File tmpCws = File.createTempFile("cws", ".swf", profile.getTempDir());
106             fwsFile = Cws2Fws.createFws(videoFile, tmpCws);
107             tmpCws.delete();
108             final File target = (fwsFile != null) ? fwsFile : videoFile;
109
110             final List<String> arguments = createArguments(target, transformedComment, outputFile);
111             return executeFfmpeg(arguments);
112         } finally {
113             if (fwsFile != null && fwsFile.exists()) {
114                 fwsFile.delete();
115             }
116         }
117     }
118
119     private List<String> createArguments(final File targetVideoFile, File transformedComment, File output)
120             throws IOException, UnsupportedEncodingException {
121         final ConvertProfile prof = profile;
122         final FfmpegProfile ffop = prof.getFfmpegOption();
123
124         final List<String> cmdList = new ArrayList<String>();
125         cmdList.add(prof.getFfmpeg().getPath());
126         cmdList.add("-y");
127         final String[] mainOptions = ffop.getMainOption().split(" +");
128         for (String opt : mainOptions) {
129             if (isNotBlank(opt)) {
130                 cmdList.add(opt);
131             }
132         }
133         final String[] inOptions = ffop.getInOption().split(" +");
134         for (String opt : inOptions) {
135             if (isNotBlank(opt)) {
136                 cmdList.add(opt);
137             }
138         }
139         cmdList.add("-i");
140         cmdList.add(targetVideoFile.getPath());
141         final String[] outOptions = ffop.getOutOption().split(" +");
142         for (String opt : outOptions) {
143             if (isNotBlank(opt)) {
144                 cmdList.add(opt);
145             }
146         }
147         final Info info = MediaInfo.getInfo(new File("bin", "MediaInfo"), targetVideoFile);
148         // 4:3 なら1.33, 16:9 なら1.76
149         final boolean isHD = ((double) info.getWidth() / (double) info.getHeight() > 1.5);
150         if (ffop.isResize()) {
151             final Size scaled = (ffop.isAdjustRatio()) ? MediaInfo.adjustSize(info, ffop.getResizeWidth(), ffop.
152                     getResizeHeight()) : new Size(info.getWidth(), info.getHeight());
153             cmdList.add("-s");
154             cmdList.add(scaled.getWidth() + "x" + scaled.getHeight());
155         }
156         final List<String> avfilterArgs = createAvfilterOptions(ffop.getAvfilterOption());
157         if (!prof.isVhookDisabled()) {
158             // TODO 引数冗長
159             final String vhookArg = getVhookArg(prof, prof.isCommentOverlay(), transformedComment.getPath(), isHD);
160             if (isNotBlank(vhookArg)) {
161                 avfilterArgs.add(vhookArg);
162             }
163         }
164         if (!avfilterArgs.isEmpty()) {
165             cmdList.add("-vfilters");
166             final String args = "\"" + join(avfilterArgs, ", ") + "\"";
167             cmdList.add(args);
168         }
169         cmdList.add(output.getPath());
170         final StringBuilder argMsg = new StringBuilder();
171         argMsg.append("arg:");
172         for (String s : cmdList) {
173             argMsg.append(" ").append(s);
174         }
175         logger.log(Level.INFO, argMsg.toString());
176         return cmdList;
177     }
178
179     private int executeFfmpeg(final List<String> cmdList) throws InterruptedException, IOException {
180         Process process = null;
181         try {
182             logger.log(Level.INFO, "Processing FFmpeg...");
183             process = Runtime.getRuntime().exec(cmdList.toArray(new String[0]));
184             BufferedReader ebr = new BufferedReader(new InputStreamReader(
185                     process.getErrorStream()));
186             String msg;
187             while ((msg = ebr.readLine()) != null) {
188                 if (msg.startsWith("frame=")) {
189                     publish(new ConvertProgress(msg));
190                 } else if (!msg.endsWith("No accelerated colorspace conversion found")) {
191                     logger.log(Level.INFO, msg);
192                 }
193
194                 checkStop();
195             }
196
197             process.waitFor();
198             return process.exitValue();
199         } finally {
200             // TODO 正常終了した場合もdestroyしていいのか?
201             if (process != null) {
202                 process.destroy();
203             }
204         }
205     }
206
207     private static List<String> createAvfilterOptions(String avfilterOption) {
208         final List<String> avfilterArgs = new ArrayList<String>();
209         if (isNotBlank(avfilterOption)) {
210             avfilterArgs.add(avfilterOption);
211         }
212         return avfilterArgs;
213     }
214
215     private static String getVhookArg(ConvertProfile prof, boolean addComment, String commPath, boolean isHD) throws
216             UnsupportedEncodingException {
217         StringBuilder sb = new StringBuilder();
218         sb.append("vhext=");
219         sb.append(prof.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         sb.append("|");
226         sb.append("--font:");
227         sb.append(URLEncoder.encode(
228                 prof.getFont().getPath().replace("\\", "/"), "Shift_JIS"));
229         sb.append("|");
230         sb.append("--font-index:");
231         sb.append(prof.getFontIndex());
232         sb.append("|");
233         sb.append("--show-user:");
234         sb.append(prof.getMaxNumOfComment());
235         sb.append("|");
236         sb.append("--shadow:");
237         sb.append(prof.getShadowIndex());
238         sb.append("|");
239         if (prof.isShowConverting()) {
240             sb.append("--enable-show-video");
241             sb.append("|");
242         }
243         if (!prof.isDisableFontSizeArrange()) {
244             sb.append("--enable-fix-font-size");
245             sb.append("|");
246         }
247         if (prof.isCommentOpaque()) {
248             sb.append("--enable-opaque-comment");
249             sb.append("|");
250         }
251         if (isHD) {
252             sb.append("--aspect-mode:1");
253             sb.append("|");
254         }
255         return sb.toString();
256     }
257
258     protected void checkStop() throws InterruptedException {
259         if (Thread.interrupted()) {
260             throw new InterruptedException("中止要求を受け付けました");
261         }
262     }
263 }