OSDN Git Service

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