/* * This file is part of the Advance project. * * Copyright (C) 2002, 2003, 2004, 2005 Andrea Mazzoleni * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "portable.h" #include "pngex.h" #include "mngex.h" #include "except.h" #include "file.h" #include "compress.h" #include "siglock.h" #include "scroll.h" #include "lib/endianrw.h" #include "lib/mng.h" #include #include #include using namespace std; int opt_dx; int opt_dy; int opt_limit; bool opt_reduce; bool opt_expand; bool opt_noalpha; shrink_t opt_level; bool opt_quiet; bool opt_verbose; bool opt_scroll; adv_mng_type opt_type; bool opt_force; bool opt_crc; void clear_line() { cout << " \r"; } adv_scroll_info* analyze_f_mng(adv_fz* f) { adv_mng* mng; unsigned counter; adv_scroll* scroll; int dx = 0; int dy = 0; mng = adv_mng_init(f); if (!mng) { throw error() << "Error in the mng stream"; } scroll = scroll_init(opt_dx, opt_dy, opt_limit); counter = 0; try { while (1) { unsigned pix_width; unsigned pix_height; unsigned char* pix_ptr; unsigned pix_pixel; unsigned pix_scanline; unsigned char* dat_ptr_ext; unsigned dat_size; unsigned char* pal_ptr_ext; unsigned pal_size; unsigned tick; int r; r = adv_mng_read(mng, &pix_width, &pix_height, &pix_pixel, &dat_ptr_ext, &dat_size, &pix_ptr, &pix_scanline, &pal_ptr_ext, &pal_size, &tick, f); if (r < 0) { throw_png_error(); } if (r > 0) break; data_ptr dat_ptr(dat_ptr_ext); data_ptr pal_ptr(pal_ptr_ext); scroll_analyze(scroll, pix_width, pix_height, pix_pixel, pix_ptr, pix_scanline); ++counter; if (opt_verbose) { int x, y; scroll_last_get(scroll, &x, &y); if (dx < abs(x)) dx = abs(x); if (dy < abs(y)) dy = abs(y); cout << "Scroll frame " << counter << ", range " << dx << "x" << dy << " \r"; cout.flush(); } } } catch (...) { adv_mng_done(mng); scroll_done(scroll); if (opt_verbose) { cout << endl; } throw; } adv_mng_done(mng); if (opt_verbose) { clear_line(); } adv_scroll_info* info = scroll_info_init(scroll); scroll_done(scroll); return info; } adv_scroll_info* analyze_mng(const string& path) { adv_fz* f; adv_scroll_info* info; f = fzopen(path.c_str(), "rb"); if (!f) { throw error() << "Failed open for reading " << path; } try { info = analyze_f_mng(f); } catch (...) { fzclose(f); throw; } fzclose(f); return info; } adv_scroll_info* analyze_png(int argc, char* argv[]) { unsigned counter; adv_scroll* scroll; int dx = 0; int dy = 0; scroll = scroll_init(opt_dx, opt_dy, opt_limit); counter = 0; try { for(int i=0;i 0) break; data_ptr dat_ptr(dat_ptr_ext); data_ptr pal_ptr(pal_ptr_ext); if (!is_reducible_image(pix_width, pix_height, pix_pixel, pix_ptr, pix_scanline)) reducible = false; } } catch (...) { adv_mng_done(mng); if (opt_verbose) { cout << endl; } throw; } adv_mng_done(mng); if (opt_verbose) { clear_line(); } } catch (...) { fzclose(f); throw; } fzclose(f); return reducible; } bool is_reducible_png(int argc, char* argv[]) { bool reducible; reducible = true; for(int i=1;ix, info->y, info->width, info->height, alpha); } else { mng_write_header(mng, f, fc, frame_width, frame_height, frame_frequency, 0, 0, 0, 0, alpha); } } void convert_image(adv_mng_write* mng, adv_fz* f_out, unsigned* fc, unsigned pix_width, unsigned pix_height, unsigned pix_pixel, unsigned char* pix_ptr, unsigned pix_scanline, unsigned char* pal_ptr, unsigned pal_size, adv_scroll_coord* scc) { if (opt_noalpha && pix_pixel == 4) { /* convert to 3 bytes per pixel */ unsigned dst_pixel = 3; unsigned dst_scanline = 3 * pix_width; data_ptr dst_ptr; dst_ptr = data_alloc(dst_scanline * pix_height); unsigned i, j; for(i=0;ix, scc->y); } else { mng_write_image(mng, f_out, fc, pix_width, pix_height, pix_pixel, pix_ptr, pix_scanline, pal_ptr, pal_size, 0, 0); } } } void convert_f_mng(adv_fz* f_in, adv_fz* f_out, unsigned* filec, unsigned* framec, adv_scroll_info* info, bool reduce, bool expand) { unsigned counter; adv_mng* mng; adv_mng_write* mng_write; bool first = true; mng = adv_mng_init(f_in); if (!mng) { throw error() << "Error in the mng stream"; } mng_write = mng_write_init(opt_type, opt_level, reduce, expand); if (!mng_write) { throw error() << "Error in the mng stream"; } *filec = 0; counter = 0; try { while (1) { unsigned pix_width; unsigned pix_height; unsigned char* pix_ptr; unsigned pix_pixel; unsigned pix_scanline; unsigned char* dat_ptr_ext; unsigned dat_size; unsigned char* pal_ptr_ext; unsigned pal_size; unsigned tick; int r; r = adv_mng_read(mng, &pix_width, &pix_height, &pix_pixel, &dat_ptr_ext, &dat_size, &pix_ptr, &pix_scanline, &pal_ptr_ext, &pal_size, &tick, f_in); if (r < 0) { throw_png_error(); } if (r > 0) break; data_ptr dat_ptr(dat_ptr_ext); data_ptr pal_ptr(pal_ptr_ext); if (first) { unsigned frequency = adv_mng_frequency_get(mng); if (opt_type == mng_vlc && tick!=1) { // adjust the frequency frequency = (frequency + tick / 2) / tick; if (frequency == 0) frequency = 1; } convert_header(mng_write, f_out, filec, adv_mng_width_get(mng), adv_mng_height_get(mng), frequency, info, pix_pixel == 4 && !opt_noalpha); first = false; } if (opt_type != mng_vlc) mng_write_frame(mng_write, f_out, filec, tick); if (info) { if (counter >= info->mac) { throw error() << "Internal error"; } convert_image(mng_write, f_out, filec, pix_width, pix_height, pix_pixel, pix_ptr, pix_scanline, pal_ptr, pal_size, &info->map[counter]); } else { convert_image(mng_write, f_out, filec, pix_width, pix_height, pix_pixel, pix_ptr, pix_scanline, pal_ptr, pal_size, 0); } ++counter; if (opt_verbose) { cout << "Compressing "; if (reduce) cout << "and reducing "; if (expand) cout << "and expanding "; cout << "frame " << counter << ", size " << *filec << " \r"; cout.flush(); } } } catch (...) { adv_mng_done(mng); mng_write_done(mng_write); if (opt_verbose) { cout << endl; } throw; } mng_write_footer(mng_write, f_out, filec); adv_mng_done(mng); mng_write_done(mng_write); if (opt_verbose) { clear_line(); } *framec = counter; } void convert_mng(const string& path_src, const string& path_dst) { adv_scroll_info* info; bool reduce; bool expand; if (opt_scroll && opt_type == mng_vlc) { throw error() << "The --scroll and --vlc options are incompatible"; } if (opt_scroll && opt_type == mng_lc) { throw error() << "The --scroll and --lc options are incompatible"; } if (opt_scroll) { info = analyze_mng(path_src); } else { info = 0; } if (opt_reduce) { reduce = is_reducible_mng(path_src); } else { reduce = false; } if (opt_expand) { expand = true; } else { expand = false; } adv_fz* f_in; adv_fz* f_out; unsigned filec; unsigned framec; f_in = fzopen(path_src.c_str(), "rb"); if (!f_in) { throw error() << "Failed open for reading " << path_src; } f_out = fzopen(path_dst.c_str(), "wb"); if (!f_out) { fzclose(f_in); throw error() << "Failed open for writing " << path_dst; } try { convert_f_mng(f_in, f_out, &filec, &framec, info, reduce, expand); } catch (...) { fzclose(f_in); fzclose(f_out); remove(path_dst.c_str()); throw; } fzclose(f_in); fzclose(f_out); if (info) scroll_info_done(info); } void convert_mng_inplace(const string& path) { // temp name of the saved file string path_dst = file_basepath(path) + ".tmp"; try { convert_mng(path, path_dst); } catch (...) { remove(path_dst.c_str()); throw; } unsigned dst_size = file_size(path_dst); if (!opt_force && file_size(path) < dst_size) { // delete the new file remove(path_dst.c_str()); throw error_unsupported() << "Bigger " << dst_size; } else { // prevent external signal sig_auto_lock sal; // delete the old file if (remove(path.c_str()) != 0) { remove(path_dst.c_str()); throw error() << "Failed delete of " << path; } // rename the new version with the correct name if (::rename(path_dst.c_str(), path.c_str()) != 0) { throw error() << "Failed rename of " << path_dst << " to " << path; } } } void mng_print(const string& path) { unsigned type; unsigned size; adv_fz* f_in; f_in = fzopen(path.c_str(), "rb"); if (!f_in) { throw error() << "Failed open for reading " << path; } try { if (adv_mng_read_signature(f_in) != 0) { throw_png_error(); } do { unsigned char* data_ext; if (adv_png_read_chunk(f_in, &data_ext, &size, &type) != 0) { throw_png_error(); } data_ptr data(data_ext); if (opt_crc) { cout << hex << setw(8) << setfill('0') << crc32(0, data, size); cout << " "; cout << dec << setw(0) << setfill(' ') << size; cout << "\n"; } else { png_print_chunk(type, data, size); } } while (type != ADV_MNG_CN_MEND); } catch (...) { fzclose(f_in); throw; } fzclose(f_in); } void extract(const string& path_src) { adv_fz* f_in; adv_mng* mng; adv_fz* f_out; unsigned counter; string base; unsigned first_tick; base = file_basename(path_src); f_in = fzopen(path_src.c_str(), "rb"); if (!f_in) { throw error() << "Failed open for reading " << path_src; } mng = adv_mng_init(f_in); if (!mng) { throw error() << "Error in the mng stream"; } counter = 0; first_tick = 1; while (1) { unsigned pix_width; unsigned pix_height; unsigned char* pix_ptr; unsigned pix_pixel; unsigned pix_scanline; unsigned char* dat_ptr_ext; unsigned dat_size; unsigned char* pal_ptr_ext; unsigned pal_size; unsigned tick; unsigned char* dst_ptr; unsigned dst_pixel; unsigned dst_scanline; int r; r = adv_mng_read(mng, &pix_width, &pix_height, &pix_pixel, &dat_ptr_ext, &dat_size, &pix_ptr, &pix_scanline, &pal_ptr_ext, &pal_size, &tick, f_in); if (r < 0) { throw_png_error(); } if (r > 0) break; data_ptr dat_ptr(dat_ptr_ext); data_ptr pal_ptr(pal_ptr_ext); if (counter == 0) { first_tick = tick; if (!first_tick) first_tick = 1; } ostringstream path_dst; path_dst << base << "-"; // not optimal code for g++ 2.95.3 path_dst.setf(ios::right, ios::adjustfield); path_dst << setw(8) << setfill('0') << counter; path_dst << ".png"; if (!opt_quiet) { cout << path_dst.str() << endl; } f_out = fzopen(path_dst.str().c_str(), "wb"); if (!f_out) { fzclose(f_in); throw error() << "Failed open for writing " << path_dst.str(); } ++counter; if (!opt_noalpha) { // convert to 4 byte RGBA format. // mencoder 0.90 has problems with 3 byte RGB format. png_convert_4(pix_width, pix_height, pix_pixel, pix_ptr, pix_scanline, pal_ptr, pal_size, &dst_ptr, &dst_pixel, &dst_scanline); png_write(f_out, pix_width, pix_height, dst_pixel, dst_ptr, dst_scanline, 0, 0, 0, 0, opt_level); free(dst_ptr); } else { png_write(f_out, pix_width, pix_height, pix_pixel, pix_ptr, pix_scanline, pal_ptr, pal_size, 0, 0, opt_level); } fzclose(f_out); } cout << adv_mng_frequency_get(mng) / (double)first_tick << endl; if (opt_verbose) { cout << endl; cout << "Example mencoder call:" << endl; cout << "mencoder " << base << "-\\*.png -mf on:w=" << adv_mng_width_get(mng) << ":h=" << adv_mng_height_get(mng) << ":fps=" << adv_mng_frequency_get(mng) / first_tick << ":type=png -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=1000:vhq -o " << base << ".avi" << endl; } adv_mng_done(mng); fzclose(f_in); } void add_all(int argc, char* argv[], unsigned frequency) { unsigned counter; unsigned filec; adv_fz* f_out; string path_dst; adv_scroll_info* info; adv_mng_write* mng_write; bool reduce; bool expand; if (argc < 2) { throw error() << "Missing arguments"; } if (opt_scroll && opt_type == mng_vlc) { throw error() << "The --scroll and --vlc options are incompatible"; } if (opt_scroll && opt_type == mng_lc) { throw error() << "The --scroll and --lc options are incompatible"; } if (opt_scroll) { info = analyze_png(argc - 1, argv + 1); } else { info = 0; } if (opt_reduce) { reduce = is_reducible_png(argc - 1, argv + 1); } else { reduce = false; } if (opt_expand) { expand = true; } else { expand = false; } path_dst = argv[0]; f_out = fzopen(path_dst.c_str(), "wb"); if (!f_out) { throw error() << "Failed open for writing " << path_dst; } mng_write = mng_write_init(opt_type, opt_level, reduce, expand); if (!mng_write) { throw error() << "Error in the mng stream"; } filec = 0; counter = 0; try { for(int i=1;i= info->mac) { throw error() << "Internal error"; } convert_image(mng_write, f_out, &filec, pix_width, pix_height, pix_pixel, pix_ptr, pix_scanline, pal_ptr, pal_size, &info->map[counter]); } else { convert_image(mng_write, f_out, &filec, pix_width, pix_height, pix_pixel, pix_ptr, pix_scanline, pal_ptr, pal_size, 0); } fzclose(f_in); ++counter; if (opt_verbose) { cout << "Compressing "; if (reduce) cout << "and reducing "; if (expand) cout << "and expanding "; cout << "frame " << counter << ", size " << filec << " \r"; cout.flush(); } } catch (...) { fzclose(f_in); throw; } } } catch (...) { if (opt_verbose) { cout << endl; } fzclose(f_out); remove(path_dst.c_str()); if (info) scroll_info_done(info); throw; } mng_write_footer(mng_write, f_out, &filec); mng_write_done(mng_write); fzclose(f_out); if (info) scroll_info_done(info); if (opt_verbose) { clear_line(); } } void remng_single(const string& file, unsigned long long& total_0, unsigned long long& total_1) { unsigned size_0; unsigned size_1; string desc; if (!file_exists(file)) { throw error() << "File " << file << " doesn't exist"; } try { size_0 = file_size(file); try { convert_mng_inplace(file); } catch (error_unsupported& e) { desc = e.desc_get(); } size_1 = file_size(file); } catch (error& e) { throw e << " on " << file; } if (!opt_quiet) { cout << setw(12) << size_0 << setw(12) << size_1 << " "; if (size_0) { unsigned perc = size_1 * 100LL / size_0; cout << setw(3) << perc; } else { cout << " 0"; } cout << "% " << file; if (desc.length()) cout << " (" << desc << ")"; cout << endl; } total_0 += size_0; total_1 += size_1; } void remng_all(int argc, char* argv[]) { unsigned long long total_0 = 0; unsigned long long total_1 = 0; for(int i=0;i 1 && !opt_crc) cout << "File: " << argv[i] << endl; mng_print(argv[i]); } } void extract_all(int argc, char* argv[]) { for(int i=0;i 250) throw error() << "Invalid frequency"; } break; case '0' : opt_level = shrink_none; opt_force = true; break; case '1' : opt_level = shrink_fast; break; case '2' : opt_level = shrink_normal; break; case '3' : opt_level = shrink_extra; break; case '4' : opt_level = shrink_extreme; break; case 's' : { int n; opt_dx = 0; opt_dy = 0; n = sscanf(optarg, "%dx%d", &opt_dx, &opt_dy); if (n != 2) throw error() << "Invalid option -s"; if (opt_dx < 0 || opt_dy < 0 || (opt_dx == 0 && opt_dy == 0) || opt_dx > 128 || opt_dy > 128) throw error() << "Invalid argument for option -s"; opt_scroll = true; opt_limit = opt_dx + opt_dy; } break; case 'S' : { int n; opt_limit = 0; n = sscanf(optarg, "%d", &opt_limit); if (n != 1) throw error() << "Invalid option -S"; if (opt_limit < 1 || opt_limit > 128) throw error() << "Invalid argument for option -S"; opt_scroll = true; opt_dx = opt_limit; opt_dy = opt_limit; } break; case 'r' : opt_reduce = true; opt_expand = false; break; case 'e' : opt_reduce = false; opt_expand = true; break; case 'n' : opt_noalpha = true; break; case 'c' : opt_type = mng_lc; opt_force = true; break; case 'C' : opt_type = mng_vlc; opt_force = true; break; case 'f' : opt_force = true; break; case 'q' : opt_verbose = false; opt_quiet = true; break; case 'v' : opt_verbose = true; opt_quiet = false; break; case 'h' : usage(); return; case 'V' : version(); return; default: { // not optimal code for g++ 2.95.3 string opt; opt = (char)optopt; throw error() << "Unknown option `" << opt << "'"; } } } switch (cmd) { case cmd_recompress : remng_all(argc - optind, argv + optind); break; case cmd_list : list_all(argc - optind, argv + optind); break; case cmd_extract : extract_all(argc - optind, argv + optind); break; case cmd_add : add_all(argc - optind, argv + optind, add_frequency); break; case cmd_unset : throw error() << "No command specified"; } } int main(int argc, char* argv[]) { try { process(argc, argv); } catch (error& e) { cerr << e << endl; exit(EXIT_FAILURE); } catch (std::bad_alloc) { cerr << "Low memory" << endl; exit(EXIT_FAILURE); } catch (...) { cerr << "Unknown error" << endl; exit(EXIT_FAILURE); } return EXIT_SUCCESS; }