2 package saccubus.worker.impl.convert;
4 import static org.apache.commons.io.FilenameUtils.getBaseName;
5 import static org.apache.commons.lang.StringUtils.*;
6 import static saccubus.worker.impl.convert.ConvertStatus.*;
8 import java.io.BufferedReader;
10 import java.io.IOException;
11 import java.io.InputStreamReader;
12 import java.io.UnsupportedEncodingException;
13 import java.net.URLEncoder;
14 import java.util.ArrayList;
15 import java.util.List;
16 import java.util.logging.Level;
17 import java.util.logging.Logger;
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;
33 * 動画を(コメント付きに)変換するワーカクラス.
36 public class Convert extends Worker<ConvertResult, ConvertProgress> {
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;
43 public Convert(ConvertProfile profile, File video, File comment) {
44 this(profile, video, comment, null);
49 * @param profile 変換用プロファイル.
51 * @param comment 変換元コメント. コメントを付与しない場合はnull.
52 * @param output 変換後出力動画.
53 * @throws IOException 変換失敗.
55 public Convert(ConvertProfile profile, File video, File comment,
56 WorkerListener<ConvertResult, ConvertProgress> listener) {
58 this.profile = profile;
59 this.videoFile = video;
60 this.commentFile = comment;
61 logger.log(Level.INFO, "convert video:{0}, comment:{1}", new Object[]{videoFile, commentFile});
65 protected ConvertResult work() throws Exception {
66 if (!profile.isConvert()) {
67 return new ConvertResult(true, "");
70 final GeneralProfile gene = profile.getGeneralProfile();
71 final OutputProfile outprof = profile.getOutputProfile();
72 final OutputNamePattern pattern = new OutputNamePattern(outprof.getFileName());
73 final String id = outprof.getVideoId();
74 pattern.setId(isNotEmpty(id) ? id : "");
75 final String title = outprof.getTitile();
76 pattern.setTitle(isNotEmpty(title) ? title : "");
77 final String fileName = getBaseName(videoFile.getPath());
78 pattern.setFileName(fileName);
79 pattern.setReplaceFrom(gene.getReplaceFrom());
80 pattern.setReplaceFrom(gene.getReplaceTo());
81 final File outputFile = new File(outprof.getDir(), pattern.createFileName());
83 File transformedComment = null;
86 if (profile.isCommentOverlay()) {
87 transformedComment = File.createTempFile("vhk", ".tmp", profile.getTempDir());
88 final HideCondition hide = profile.getNgSetting();
89 publish(new ConvertProgress(PROCESS, 0.0, "コメントの中間ファイルへの変換中"));
90 ConvertToVideoHook.convert(commentFile, transformedComment, hide.getId(), hide.getWord());
94 publish(new ConvertProgress(PROCESS, 0.0, "動画の変換を開始"));
96 final int code = convert(transformedComment, outputFile);
98 throw new IOException("ffmpeg実行失敗: " + outputFile.getPath());
100 publish(new ConvertProgress(PROCESS, 100.0, "変換が正常に終了しました。"));
101 return new ConvertResult(true, outputFile.getName());
103 if (transformedComment != null && transformedComment.exists()) {
104 transformedComment.delete();
109 private int convert(File transformedComment, File outputFile) throws InterruptedException, IOException {
112 final File tmpCws = File.createTempFile("cws", ".swf", profile.getTempDir());
113 fwsFile = Cws2Fws.createFws(videoFile, tmpCws);
115 final File target = (fwsFile != null) ? fwsFile : videoFile;
117 final List<String> arguments = createArguments(target, transformedComment, outputFile);
118 return executeFfmpeg(arguments);
120 if (fwsFile != null && fwsFile.exists()) {
126 private List<String> createArguments(final File targetVideoFile, File transformedComment, File output)
127 throws IOException, UnsupportedEncodingException {
128 final ConvertProfile prof = profile;
129 final FfmpegProfile ffop = prof.getFfmpegOption();
131 final List<String> cmdList = new ArrayList<String>();
132 cmdList.add(prof.getFfmpeg().getPath());
134 final String[] mainOptions = ffop.getMainOption().split(" +");
135 for (String opt : mainOptions) {
136 if (isNotBlank(opt)) {
140 final String[] inOptions = ffop.getInOption().split(" +");
141 for (String opt : inOptions) {
142 if (isNotBlank(opt)) {
147 cmdList.add(targetVideoFile.getPath());
148 final String[] outOptions = ffop.getOutOption().split(" +");
149 for (String opt : outOptions) {
150 if (isNotBlank(opt)) {
154 final Info info = MediaInfo.getInfo(new File("bin", "MediaInfo"), targetVideoFile);
155 // 4:3 なら1.33, 16:9 なら1.76
156 final boolean isHD = ((double) info.getWidth() / (double) info.getHeight() > 1.5);
157 if (ffop.isResize()) {
158 final Size scaled = (ffop.isAdjustRatio()) ? MediaInfo.adjustSize(info, ffop.getResizeWidth(), ffop.
159 getResizeHeight()) : new Size(info.getWidth(), info.getHeight());
161 cmdList.add(scaled.getWidth() + "x" + scaled.getHeight());
163 final List<String> avfilterArgs = createAvfilterOptions(ffop.getAvfilterOption());
164 if (!prof.isVhookDisabled()) {
166 final String vhookArg = getVhookArg(prof, prof.isCommentOverlay(), transformedComment.getPath(), isHD);
167 if (isNotBlank(vhookArg)) {
168 avfilterArgs.add(vhookArg);
171 if (!avfilterArgs.isEmpty()) {
172 cmdList.add("-vfilters");
173 final String args = "\"" + join(avfilterArgs, ", ") + "\"";
176 cmdList.add(output.getPath());
177 final StringBuilder argMsg = new StringBuilder();
178 argMsg.append("arg:");
179 for (String s : cmdList) {
180 argMsg.append(" ").append(s);
182 logger.log(Level.INFO, argMsg.toString());
186 private int executeFfmpeg(final List<String> cmdList) throws InterruptedException, IOException {
187 Process process = null;
189 logger.log(Level.INFO, "Processing FFmpeg...");
190 process = Runtime.getRuntime().exec(cmdList.toArray(new String[0]));
191 BufferedReader ebr = new BufferedReader(new InputStreamReader(
192 process.getErrorStream()));
194 while ((msg = ebr.readLine()) != null) {
195 if (msg.startsWith("frame=")) {
197 publish(new ConvertProgress(PROCESS, 0.0, msg));
198 } else if (!msg.endsWith("No accelerated colorspace conversion found")) {
199 logger.log(Level.INFO, msg);
206 return process.exitValue();
208 // TODO 正常終了した場合もdestroyしていいのか?
209 if (process != null) {
215 private static List<String> createAvfilterOptions(String avfilterOption) {
216 final List<String> avfilterArgs = new ArrayList<String>();
217 if (isNotBlank(avfilterOption)) {
218 avfilterArgs.add(avfilterOption);
223 private static String getVhookArg(ConvertProfile prof, boolean addComment, String commPath, boolean isHD) throws
224 UnsupportedEncodingException {
225 StringBuilder sb = new StringBuilder();
227 sb.append(prof.getVhook().getPath().replace("\\", "/"));
230 sb.append("--data-user:");
231 sb.append(URLEncoder.encode(commPath.replace("\\", "/"), "Shift_JIS"));
234 sb.append("--font:");
235 sb.append(URLEncoder.encode(
236 prof.getFont().getPath().replace("\\", "/"), "Shift_JIS"));
238 sb.append("--font-index:");
239 sb.append(prof.getFontIndex());
241 sb.append("--show-user:");
242 sb.append(prof.getMaxNumOfComment());
244 sb.append("--shadow:");
245 sb.append(prof.getShadowIndex());
247 if (prof.isShowConverting()) {
248 sb.append("--enable-show-video");
251 if (!prof.isDisableFontSizeArrange()) {
252 sb.append("--enable-fix-font-size");
255 if (prof.isCommentOpaque()) {
256 sb.append("--enable-opaque-comment");
260 sb.append("--aspect-mode:1");
263 return sb.toString();
266 protected void checkStop() throws InterruptedException {
267 if (Thread.interrupted()) {
268 throw new InterruptedException("中止要求を受け付けました");