2 package saccubus.worker;
4 import static org.apache.commons.io.FilenameUtils.getBaseName;
5 import static org.apache.commons.lang.StringUtils.*;
7 import java.io.BufferedReader;
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;
31 * 動画を(コメント付きに)変換するワーカクラス.
34 public class Convert extends SwingWorker<ConvertResult, ConvertProgress> {
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;
45 * @param profile 変換用プロファイル.
47 * @param comment 変換元コメント. コメントを付与しない場合はnull.
48 * @param output 変換後出力動画.
49 * @throws IOException 変換失敗.
51 public Convert(ConvertProfile profile, File video, File comment) {
52 this.profile = profile;
53 this.videoFile = video;
54 this.commentFile = comment;
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());
71 protected ConvertResult doInBackground() throws Exception {
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.
81 throw new IOException("コメント変換に失敗。ファイル名に使用できない文字が含まれているか正規表現の間違い?");
86 publish(new ConvertProgress("動画の変換を開始"));
88 final int code = convert();
90 publish(new ConvertProgress("変換エラー:" + outputFile.getPath()));
91 return new ConvertResult(false, null);
93 publish(new ConvertProgress("変換が正常に終了しました。"));
94 return new ConvertResult(true, outputFile.getName());
96 if (transformedComment != null && transformedComment.exists()) {
97 transformedComment.delete();
102 private int convert() throws InterruptedException, IOException {
106 tmpCws = File.createTempFile("cws", ".swf", profile.getTempDir());
107 fwsFile = Cws2Fws.createFws(videoFile, tmpCws);
108 final File target = (fwsFile != null) ? fwsFile : videoFile;
110 final List<String> arguments = createArguments(profile.getFfmpegOption(), target);
111 return executeFfmpeg(arguments);
113 if (tmpCws != null && tmpCws.exists()) {
116 if (fwsFile != null && fwsFile.exists()) {
122 // TODO static にできた方が良い
123 private List<String> createArguments(final FfmpegProfile ffop, final File targetVideoFile)
125 UnsupportedEncodingException {
126 final List<String> cmdList = new ArrayList<String>();
127 cmdList.add(profile.getFfmpeg().getPath());
129 final String[] mainOptions = ffop.getMainOption().split(" +");
130 for (String opt : mainOptions) {
131 if (isNotBlank(opt)) {
135 final String[] inOptions = ffop.getInOption().split(" +");
136 for (String opt : inOptions) {
137 if (isNotBlank(opt)) {
142 cmdList.add(targetVideoFile.getPath());
143 final String[] outOptions = ffop.getOutOption().split(" +");
144 for (String opt : outOptions) {
145 if (isNotBlank(opt)) {
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());
156 cmdList.add(scaled.getWidth() + "x" + scaled.getHeight());
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);
166 if (!avfilterArgs.isEmpty()) {
167 cmdList.add("-vfilters");
168 final String args = "\"" + join(avfilterArgs, ", ") + "\"";
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);
177 logger.log(Level.INFO, argMsg.toString());
181 private int executeFfmpeg(final List<String> cmdList) throws InterruptedException, IOException {
182 Process process = null;
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()));
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);
200 return process.exitValue();
202 // TODO 正常終了した場合もdestroyしていいのか?
203 if (process != null) {
209 private static List<String> createAvfilterOptions(String avfilterOption) {
210 final List<String> avfilterArgs = new ArrayList<String>();
211 if (isNotBlank(avfilterOption)) {
212 avfilterArgs.add(avfilterOption);
217 private static String getVhookArg(ConvertProfile prof, boolean addComment, String commPath, boolean isHD) throws
218 UnsupportedEncodingException {
219 StringBuilder sb = new StringBuilder();
221 sb.append(prof.getVhook().getPath().replace("\\", "/"));
224 sb.append("--data-user:");
225 sb.append(URLEncoder.encode(commPath.replace("\\", "/"), "Shift_JIS"));
228 sb.append("--font:");
229 sb.append(URLEncoder.encode(
230 prof.getFont().getPath().replace("\\", "/"), "Shift_JIS"));
232 sb.append("--font-index:");
233 sb.append(prof.getFontIndex());
235 sb.append("--show-user:");
236 sb.append(prof.getMaxNumOfComment());
238 sb.append("--shadow:");
239 sb.append(prof.getShadowIndex());
241 if (prof.isShowConverting()) {
242 sb.append("--enable-show-video");
245 if (!prof.isDisableFontSizeArrange()) {
246 sb.append("--enable-fix-font-size");
249 if (prof.isCommentOpaque()) {
250 sb.append("--enable-opaque-comment");
254 sb.append("--aspect-mode:1");
257 return sb.toString();
260 protected void checkStop() throws InterruptedException {
261 if (Thread.interrupted()) {
262 throw new InterruptedException("中止要求を受け付けました");