OSDN Git Service

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