PageRenderTime 25ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/cli/src/cli.cpp

https://github.com/asarium/apngasm
C++ | 296 lines | 241 code | 35 blank | 20 comment | 48 complexity | 5f7679e65baef8f449474968163ee9d4 MD5 | raw file
  1. #include "cli.h"
  2. #include "apngasm-cli-version.h"
  3. #include <iostream>
  4. #include <boost/algorithm/string.hpp>
  5. #include <boost/regex.hpp>
  6. #include <boost/filesystem.hpp>
  7. #include "listener/apngasmlistener.h"
  8. namespace {
  9. const char separator = boost::filesystem::path::preferred_separator;
  10. bool isNumber(const std::string s)
  11. {
  12. std::string::const_iterator it = s.begin();
  13. while (it != s.end() && std::isdigit(*it)) ++it;
  14. return !s.empty() && it == s.end();
  15. }
  16. class FrameDelay {
  17. public:
  18. int num, den;
  19. FrameDelay(void) : num(100), den(1000) {}
  20. FrameDelay(int num) : num(num), den(1000) {}
  21. FrameDelay(int num, int den) : num(num), den(den) {}
  22. FrameDelay(const std::string &src_str)
  23. : den(1000)
  24. {
  25. using namespace std;
  26. if(isNumber(src_str)) {
  27. num = atoi(src_str.c_str());
  28. } else { // Delay is in fractions of a second or invalid
  29. using boost::algorithm::split;
  30. vector<string> portions;
  31. split(portions, src_str, boost::is_any_of(":"));
  32. if(portions.size() != 2
  33. || !isNumber(portions[0]) || !isNumber(portions[1])) {
  34. throw std::runtime_error("parse delay error");
  35. }
  36. num = atoi(portions[0].c_str());
  37. den = atoi(portions[1].c_str());
  38. }
  39. }
  40. };
  41. class CustomAPNGAsmListener : public apngasm::listener::APNGAsmListener
  42. {
  43. public:
  44. // Initialize CustomAPNGAsmListener object.
  45. CustomAPNGAsmListener(const apngasm::APNGAsm* apngasm, int overwriteMode)
  46. : _apngasm(apngasm)
  47. , _overwriteMode(overwriteMode)
  48. {
  49. // nop
  50. }
  51. // Called after add frame.
  52. void onPostAddFrame(const std::string& filePath, unsigned int delayNum, unsigned int delayDen) const
  53. {
  54. std::cout << filePath << " => Delay=(" << delayNum << "/" << delayDen << ") sec" << std::endl;
  55. }
  56. // Called after add frame.
  57. void onPostAddFrame(const apngasm::APNGFrame& frame) const
  58. {
  59. apngasm::APNGFrame& tmp = const_cast<apngasm::APNGFrame&>(frame);
  60. std::cout << "New Frame => Delay=(" << tmp.delayNum() << "/" << tmp.delayDen() << ") sec" << std::endl;
  61. }
  62. // Called before save.
  63. // Return true if can save.
  64. bool onPreSave(const std::string& filePath) const
  65. {
  66. using namespace std;
  67. using namespace boost;
  68. if(_overwriteMode == apngasm_cli::Options::OVERWRITE_FORCE) {
  69. createParentDirs(filePath);
  70. return true;
  71. }
  72. if(!filesystem::exists(filesystem::path(filePath))) {
  73. createParentDirs(filePath);
  74. return true;
  75. }
  76. cout << "The file '" << filePath << "' already exists.";
  77. if(_overwriteMode == apngasm_cli::Options::OVERWRITE_INTERACTIVE) {
  78. cout << " Overwrite? [y/N]: ";
  79. string reply;
  80. getline(cin, reply);
  81. static const regex RE("\\Ay(es)?\\z", regex::icase);
  82. if(regex_match(reply, RE)) {
  83. return true;
  84. }
  85. } else {
  86. cout << "\nuse `--interactive' or `--force' to overwrite." << endl;
  87. }
  88. return false;
  89. }
  90. // Called after save.
  91. void onPostSave(const std::string& filePath) const
  92. {
  93. std::cout << filePath << std::endl;
  94. }
  95. // Called when create output path of png file.
  96. // Return output path.
  97. const std::string onCreatePngPath(const std::string& outputDir, int index) const
  98. {
  99. // saving visible frames as #1, #2, #3, ...
  100. // saving skipped frame as #0 (if it's exists)
  101. return apngasm::listener::APNGAsmListener::onCreatePngPath(outputDir, _apngasm->isSkipFirst() ? index : index+1);
  102. }
  103. private:
  104. const apngasm::APNGAsm* const _apngasm;
  105. const int _overwriteMode;
  106. // Return true if create succeeded.
  107. bool createParentDirs(const std::string& filePath) const
  108. {
  109. boost::filesystem::path path = filePath;
  110. boost::filesystem::path parent = path.parent_path();
  111. if(parent == "") {
  112. return true;
  113. }
  114. return boost::filesystem::create_directories(parent);
  115. }
  116. }; // class CustomAPNGAsmListener
  117. } // unnamed namespace
  118. namespace apngasm_cli {
  119. const std::string CLI::VERSION = APNGASM_CLI_VERSION;
  120. CLI::CLI(int argc, char **argv)
  121. : options(argc, argv), assembler()
  122. {
  123. pListener = new CustomAPNGAsmListener(&assembler, options.getOverwriteMode());
  124. assembler.setAPNGAsmListener(pListener);
  125. }
  126. CLI::~CLI()
  127. {
  128. delete pListener;
  129. }
  130. int CLI::start(void)
  131. {
  132. using namespace std;
  133. if(options.has("help")) {
  134. options.putHelpTo(cout);
  135. return ERRCODE_HELP;
  136. }
  137. if(options.has("version")) {
  138. options.putVersionTo(cout);
  139. return ERRCODE_VERSION;
  140. }
  141. string srcFile;
  142. if(options.disassembleFile(srcFile)) {
  143. return disassemble(srcFile);
  144. }
  145. if(options.specFile(srcFile))
  146. {
  147. assembler.loadAnimationSpec(srcFile);
  148. return assemble();
  149. }
  150. assembler.setLoops(options.getLoops());
  151. assembler.setSkipFirst( options.has("skip") );
  152. return assemble();
  153. }
  154. int CLI::assemble(void)
  155. {
  156. using std::cout;
  157. std::string outfile;
  158. options.outputFile(outfile);
  159. // if(!checkOverwrite(outfile)) {
  160. // return ERRCODE_OUTPUTFILE_ALREADYEXISTS;
  161. // }
  162. static const boost::regex PNG_RE(".+\\.png\\z");
  163. static const boost::regex DELAY_RE("[0-9]+[:[0-9]+]?");
  164. const FrameDelay DEFAULT_DELAY = options.getDefaultDelay();
  165. std::string lastFile;
  166. std::string errorFile;
  167. Options::INPUTS::const_iterator arg = options.inputFilesBegin();
  168. for(; arg != options.inputFilesEnd(); ++arg) {
  169. if(regex_match(*arg, DELAY_RE)) {
  170. if(!lastFile.empty())
  171. {
  172. const FrameDelay delay = *arg;
  173. assembler.addFrame(lastFile, delay.num, delay.den);
  174. lastFile.clear();
  175. continue;
  176. }
  177. }
  178. else if(regex_match(*arg, PNG_RE) || arg->find("*", 0)) {
  179. if(!lastFile.empty())
  180. {
  181. const FrameDelay delay = DEFAULT_DELAY;
  182. const int frameCount = assembler.frameCount();
  183. if( frameCount < assembler.addFrame(lastFile, delay.num, delay.den) )
  184. {
  185. lastFile = *arg;
  186. continue;
  187. }
  188. errorFile = lastFile;
  189. }
  190. else
  191. {
  192. lastFile = *arg;
  193. continue;
  194. }
  195. }
  196. else
  197. {
  198. errorFile = *arg;
  199. }
  200. }
  201. if(!lastFile.empty())
  202. {
  203. const FrameDelay delay = DEFAULT_DELAY;
  204. const int frameCount = assembler.frameCount();
  205. if( frameCount >= assembler.addFrame(lastFile, delay.num, delay.den) )
  206. errorFile = lastFile;
  207. }
  208. if( !errorFile.empty() )
  209. {
  210. // Error.
  211. cout << "argument `" << errorFile
  212. << "' is invalid." << std::endl;
  213. return ERRCODE_INVALIDARGUMENT;
  214. }
  215. if (assembler.frameCount() == 0)
  216. cout << "apngasm " << assembler.version() << "\nNo source frames were specified. Use --help for usage information." << std::endl;
  217. else
  218. cout << assembler.frameCount() << " Frames" << std::endl;
  219. if (outfile == "") {
  220. outfile = "out.png";
  221. }
  222. if (assembler.assemble(outfile)) {
  223. cout << "APNG assembled successfully => " << outfile << std::endl;
  224. return 0;
  225. } else {
  226. return ERRCODE_ASSEMBLEERR;
  227. }
  228. }
  229. int CLI::disassemble(const std::string &src)
  230. {
  231. // Dissassemble apng file.
  232. std::vector<apngasm::APNGFrame> frames = assembler.disassemble(src);
  233. std::cout << frames.size() << " Frames" << std::endl;
  234. // Output png image files.
  235. std::string outdir;
  236. if(!options.outputFile(outdir)) {
  237. boost::filesystem::path path = src;
  238. outdir = path.replace_extension("").string();
  239. }
  240. if( !assembler.savePNGs(outdir) )
  241. return 1;
  242. // Output json spec files.
  243. std::string outSpecFile;
  244. if( options.outputJSONFile(outSpecFile) )
  245. {
  246. boost::filesystem::path path = outSpecFile;
  247. if(path.is_relative())
  248. outSpecFile = outdir + separator + outSpecFile;
  249. assembler.saveJSON(outSpecFile, outdir);
  250. }
  251. // Output XML spec files.
  252. if( options.outputXMLFile(outSpecFile) )
  253. {
  254. boost::filesystem::path path = outSpecFile;
  255. if(path.is_relative())
  256. outSpecFile = outdir + separator + outSpecFile;
  257. assembler.saveXML(outSpecFile, outdir);
  258. }
  259. return 0;
  260. }
  261. }