diff --git a/src/mmg/Makefile.am b/src/mmg/Makefile.am index ce15af4bf..b11dd1fdc 100644 --- a/src/mmg/Makefile.am +++ b/src/mmg/Makefile.am @@ -5,14 +5,18 @@ bin_PROGRAMS = mmg INCLUDES = -I.. -I../.. mmg_SOURCES = mmg.cpp mmg.h \ + ../chapters.cpp ../chapters.h \ + ../chapter_parser_xml.cpp \ ../common.cpp ../common.h \ extern_data.cpp extern_data.h \ ../iso639.cpp ../iso639.h \ + ../mm_io.cpp ../mm_io.h \ mux_dialog.cpp \ tab_attachments.cpp \ + tab_chapters.cpp \ tab_input.cpp \ tab_global.cpp \ tab_settings.cpp -mmg_LDADD = @PROFILING_LIBS@ @EBML_LIBS@ @WXWINDOWS_LIBS@ @MINGW_LIBS@ \ - @MINGW_GUIAPP@ @ICONV_LIBS@ +mmg_LDADD = @PROFILING_LIBS@ @MATROSKA_LIBS@ @EBML_LIBS@ @EXPAT_LIBS@ \ + @WXWINDOWS_LIBS@ @MINGW_LIBS@ @MINGW_GUIAPP@ @ICONV_LIBS@ diff --git a/src/mmg/mmg.cpp b/src/mmg/mmg.cpp index c722b0bd9..4f06c5435 100644 --- a/src/mmg/mmg.cpp +++ b/src/mmg/mmg.cpp @@ -153,6 +153,22 @@ mmg_dialog::mmg_dialog(): wxFrame(NULL, -1, "mkvmerge GUI v" VERSION, _T("Sa&ve command line\tCtrl-V"), _T("Save the command line to a file")); + wxMenu *chapter_menu = new wxMenu(); + chapter_menu->Append(ID_M_CHAPTERS_NEW, _T("&New"), + _T("Create a new chapter file")); + chapter_menu->Append(ID_M_CHAPTERS_LOAD, _T("&Load"), + _T("Load a chapter file (simple/OGM format or XML " + "format)")); + chapter_menu->Append(ID_M_CHAPTERS_SAVE, _T("&Save"), + _T("Save the current chapters to a XML file")); + chapter_menu->Append(ID_M_CHAPTERS_SAVEAS, _T("Save &as"), + _T("Save the current chapters to a file with another " + "name")); + chapter_menu->AppendSeparator(); + chapter_menu->Append(ID_M_CHAPTERS_VERIFY, _T("&Verify"), + _T("Verify the current chapter entries to see if there " + "are any errors")); + wxMenu *help_menu = new wxMenu(); help_menu->Append(ID_M_HELP_ABOUT, _T("&About\tF1"), _T("Show program information")); @@ -160,6 +176,7 @@ mmg_dialog::mmg_dialog(): wxFrame(NULL, -1, "mkvmerge GUI v" VERSION, wxMenuBar *menu_bar = new wxMenuBar(); menu_bar->Append(file_menu, _T("&File")); menu_bar->Append(muxing_menu, _T("&Muxing")); + menu_bar->Append(chapter_menu, _T("&Chapter Editor")); menu_bar->Append(help_menu, _T("&Help")); SetMenuBar(menu_bar); @@ -180,11 +197,13 @@ mmg_dialog::mmg_dialog(): wxFrame(NULL, -1, "mkvmerge GUI v" VERSION, attachments_page = new tab_attachments(notebook); global_page = new tab_global(notebook); settings_page = new tab_settings(notebook); + chapter_editor_page = new tab_chapters(notebook, chapter_menu); notebook->AddPage(input_page, _("Input")); notebook->AddPage(attachments_page, _("Attachments")); notebook->AddPage(global_page, _("Global")); notebook->AddPage(settings_page, _("Settings")); + notebook->AddPage(chapter_editor_page, _("Chapter Editor")); bs_main->Add(notebook, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5); @@ -759,6 +778,26 @@ void mmg_dialog::update_file_menu() { } } +void mmg_dialog::on_new_chapters(wxCommandEvent &evt) { + chapter_editor_page->on_new_chapters(evt); +} + +void mmg_dialog::on_load_chapters(wxCommandEvent &evt) { + chapter_editor_page->on_load_chapters(evt); +} + +void mmg_dialog::on_save_chapters(wxCommandEvent &evt) { + chapter_editor_page->on_save_chapters(evt); +} + +void mmg_dialog::on_save_chapters_as(wxCommandEvent &evt) { + chapter_editor_page->on_save_chapters_as(evt); +} + +void mmg_dialog::on_verify_chapters(wxCommandEvent &evt) { + chapter_editor_page->on_verify_chapters(evt); +} + IMPLEMENT_CLASS(mmg_dialog, wxFrame); BEGIN_EVENT_TABLE(mmg_dialog, wxFrame) EVT_BUTTON(ID_B_BROWSEOUTPUT, mmg_dialog::on_browse_output) @@ -775,6 +814,11 @@ BEGIN_EVENT_TABLE(mmg_dialog, wxFrame) EVT_MENU(ID_M_FILE_LOADLAST2, mmg_dialog::on_file_load_last) EVT_MENU(ID_M_FILE_LOADLAST3, mmg_dialog::on_file_load_last) EVT_MENU(ID_M_FILE_LOADLAST4, mmg_dialog::on_file_load_last) + EVT_MENU(ID_M_CHAPTERS_NEW, mmg_dialog::on_new_chapters) + EVT_MENU(ID_M_CHAPTERS_LOAD, mmg_dialog::on_load_chapters) + EVT_MENU(ID_M_CHAPTERS_SAVE, mmg_dialog::on_save_chapters) + EVT_MENU(ID_M_CHAPTERS_SAVEAS, mmg_dialog::on_save_chapters_as) + EVT_MENU(ID_M_CHAPTERS_VERIFY, mmg_dialog::on_verify_chapters) END_EVENT_TABLE(); bool mmg_app::OnInit() { diff --git a/src/mmg/mmg.h b/src/mmg/mmg.h index 5913de281..a0ae6fd26 100644 --- a/src/mmg/mmg.h +++ b/src/mmg/mmg.h @@ -25,11 +25,15 @@ #include #include +#include "matroska/KaxChapters.h" + #include "os.h" #include "wx/confbase.h" #include "wx/process.h" +#include "wx/treectrl.h" using namespace std; +using namespace libmatroska; #ifdef SYS_WINDOWS #define ALLFILES "All Files (*.*)|*.*" @@ -98,6 +102,15 @@ using namespace std; #define ID_B_MUX_ABORT 10057 #define ID_T_ATTACHMENTVALUES 10058 #define ID_T_INPUTVALUES 10059 +#define ID_TRC_CHAPTERS 10060 +#define ID_B_ADDCHAPTER 10061 +#define ID_B_REMOVECHAPTER 10062 +#define ID_T_CHAPTERVALUES 10063 +#define ID_TC_CHAPTERNAME 10064 +#define ID_TC_CHAPTERLANGUAGES 10065 +#define ID_TC_CHAPTERCOUNTRYCODES 10066 +#define ID_TC_CHAPTERSTART 10067 +#define ID_TC_CHAPTEREND 10068 #define ID_M_FILE_LOAD 20000 #define ID_M_FILE_SAVE 20001 @@ -112,7 +125,13 @@ using namespace std; #define ID_M_MUXING_COPY_CMDLINE 20101 #define ID_M_MUXING_SAVE_CMDLINE 20102 -#define ID_M_HELP_ABOUT 20200 +#define ID_M_CHAPTERS_NEW 20200 +#define ID_M_CHAPTERS_LOAD 20201 +#define ID_M_CHAPTERS_SAVE 20202 +#define ID_M_CHAPTERS_SAVEAS 20203 +#define ID_M_CHAPTERS_VERIFY 20204 + +#define ID_M_HELP_ABOUT 29900 typedef struct { char type; @@ -272,6 +291,42 @@ public: bool validate_settings(); }; +class tab_chapters: public wxPanel { + DECLARE_CLASS(tab_chapters); + DECLARE_EVENT_TABLE(); +public: + wxTreeCtrl *tc_chapters; + wxTreeItemId tid_root; + wxButton *b_add_chapter, *b_remove_chapter; + wxMenu *m_chapters; + + wxTextCtrl *tc_chapter_name, *tc_language_codes, *tc_country_codes; + wxTextCtrl *tc_start_time, *tc_end_time; + + wxTimer value_copy_timer; + + wxString file_name; + + KaxChapters *chapters; + +public: + tab_chapters(wxWindow *parent, wxMenu *nm_chapters); + ~tab_chapters(); + + void on_new_chapters(wxCommandEvent &evt); + void on_load_chapters(wxCommandEvent &evt); + void on_save_chapters(wxCommandEvent &evt); + void on_save_chapters_as(wxCommandEvent &evt); + void on_verify_chapters(wxCommandEvent &evt); + void on_add_chapter(wxCommandEvent &evt); + void on_remove_chapter(wxCommandEvent &evt); + void on_copy_values(wxTimerEvent &evt); + void on_entry_selected(wxTreeEvent &evt); + + void add_recursively(wxTreeItemId &parent, EbmlMaster &master); + wxString create_chapter_label(KaxChapterAtom &chapter); +}; + class mux_dialog: public wxDialog { DECLARE_CLASS(mux_dialog); DECLARE_EVENT_TABLE(); @@ -326,6 +381,7 @@ protected: tab_attachments *attachments_page; tab_global *global_page; tab_settings *settings_page; + tab_chapters *chapter_editor_page; public: mmg_dialog(); @@ -354,6 +410,12 @@ public: void set_last_settings_in_menu(wxString name); void on_file_load_last(wxCommandEvent &evt); void update_file_menu(); + + void on_new_chapters(wxCommandEvent &evt); + void on_load_chapters(wxCommandEvent &evt); + void on_save_chapters(wxCommandEvent &evt); + void on_save_chapters_as(wxCommandEvent &evt); + void on_verify_chapters(wxCommandEvent &evt); }; class mmg_app: public wxApp { diff --git a/src/mmg/tab_chapters.cpp b/src/mmg/tab_chapters.cpp new file mode 100644 index 000000000..7c61e9e8f --- /dev/null +++ b/src/mmg/tab_chapters.cpp @@ -0,0 +1,378 @@ +/* + mkvmerge GUI -- utility for splicing together matroska files + from component media subtypes + + tab_chapters.cpp + + Written by Moritz Bunkus + Parts of this code were written by Florian Wager + + Distributed under the GPL + see the file COPYING for details + or visit http://www.gnu.org/copyleft/gpl.html +*/ + +/*! + \file + \version $Id: tab_chapters.cpp 1045 2003-09-17 11:56:16Z mosu $ + \brief "chapter editor" tab + \author Moritz Bunkus +*/ + +#include "wx/wxprec.h" + +#include "wx/wx.h" +#include "wx/notebook.h" +#include "wx/listctrl.h" +#include "wx/statline.h" + +#include "mmg.h" +#include "chapters.h" +#include "common.h" +#include "error.h" +#include "extern_data.h" + +class tree_node_data: public wxTreeItemData { +public: + bool is_atom; + KaxChapterAtom *chapter; + KaxEditionEntry *eentry; + + tree_node_data(KaxChapterAtom *nchapter): + is_atom(true), + chapter(nchapter) { + }; + + tree_node_data(KaxEditionEntry *neentry): + is_atom(false), + eentry(neentry) { + }; +}; + +void expand_subtree(wxTreeCtrl &tree, wxTreeItemId &root, bool expand = true) { + wxTreeItemId child; + long cookie; + + if (!expand) + tree.Collapse(root); + child = tree.GetFirstChild(root, cookie); + while (child > 0) { + expand_subtree(tree, child, expand); + child = tree.GetNextChild(root, cookie); + } + if (expand) + tree.Expand(root); +} + +#define Y1 30 +#define X1 140 +#define XS 360 + +tab_chapters::tab_chapters(wxWindow *parent, wxMenu *nm_chapters): + wxPanel(parent, -1, wxDefaultPosition, wxSize(100, 400), wxTAB_TRAVERSAL) { + + m_chapters = nm_chapters; + + new wxStaticText(this, wxID_STATIC, _("Chapters:"), wxPoint(10, 5), + wxDefaultSize, 0); + tc_chapters = + new wxTreeCtrl(this, ID_TRC_CHAPTERS, wxPoint(10, 24), wxSize(350, 250), + wxSIMPLE_BORDER | wxTR_HAS_BUTTONS); + + b_add_chapter = + new wxButton(this, ID_B_ADDCHAPTER, _("Add chapter"), wxPoint(370, 24), + wxSize(120, -1)); + b_remove_chapter = + new wxButton(this, ID_B_REMOVECHAPTER, _("Remove chapter"), + wxPoint(370, 50), wxSize(120, -1)); + + new wxStaticText(this, wxID_STATIC, _("Chapter name:"), wxPoint(10, 285)); + tc_chapter_name = + new wxTextCtrl(this, ID_TC_CHAPTERNAME, _(""), wxPoint(X1, 285 + YOFF), + wxSize(XS - X1, -1)); + + new wxStaticText(this, wxID_STATIC, _("Start time:"), + wxPoint(10, 285 + Y1)); + tc_start_time = + new wxTextCtrl(this, ID_TC_CHAPTERSTART, _(""), + wxPoint(X1, 285 + Y1 + YOFF), wxSize(XS - X1, -1)); + + new wxStaticText(this, wxID_STATIC, _("End time:"), + wxPoint(10, 285 + 2 * Y1)); + tc_end_time = + new wxTextCtrl(this, ID_TC_CHAPTEREND, _(""), + wxPoint(X1, 285 + 2 * Y1 + YOFF), wxSize(XS - X1, -1)); + + new wxStaticText(this, wxID_STATIC, _("Language codes:"), + wxPoint(10, 285 + 3 * Y1)); + tc_language_codes = + new wxTextCtrl(this, ID_TC_CHAPTERLANGUAGES, _(""), + wxPoint(X1, 285 + 3 * Y1 + YOFF), wxSize(XS - X1, -1)); + + new wxStaticText(this, wxID_STATIC, _("Country codes:"), + wxPoint(10, 285 + 4 * Y1)); + tc_country_codes = + new wxTextCtrl(this, ID_TC_CHAPTERCOUNTRYCODES, _(""), + wxPoint(X1, 285 + 4 * Y1 + YOFF), wxSize(XS - X1, -1)); + + m_chapters->Enable(ID_M_CHAPTERS_SAVE, false); + m_chapters->Enable(ID_M_CHAPTERS_SAVEAS, false); + m_chapters->Enable(ID_M_CHAPTERS_VERIFY, false); + b_add_chapter->Enable(false); + b_remove_chapter->Enable(false); + + file_name = ""; + chapters = NULL; + + value_copy_timer.SetOwner(this, ID_T_CHAPTERVALUES); + value_copy_timer.Start(333); +} + +tab_chapters::~tab_chapters() { + if (chapters != NULL) + delete chapters; +} + +void tab_chapters::on_new_chapters(wxCommandEvent &evt) { + file_name = ""; + if (chapters != NULL) + delete chapters; + tc_chapters->DeleteAllItems(); + tid_root = tc_chapters->AddRoot(_T("(new chapter file)")); + chapters = new KaxChapters; + + m_chapters->Enable(ID_M_CHAPTERS_SAVE, true); + m_chapters->Enable(ID_M_CHAPTERS_SAVEAS, true); + m_chapters->Enable(ID_M_CHAPTERS_VERIFY, true); + b_add_chapter->Enable(true); + b_remove_chapter->Enable(true); + + mdlg->set_status_bar("New chapters created."); +} + +wxString tab_chapters::create_chapter_label(KaxChapterAtom &chapter) { + wxString label, s; + char *tmpstr; + KaxChapterDisplay *display; + KaxChapterString *cstring; + KaxChapterTimeStart *tstart; + KaxChapterTimeEnd *tend; + KaxChapterLanguage *language; + int64_t timestamp; + + label = "(unnamed chapter)"; + language = NULL; + display = FindChild(chapter); + if (display != NULL) { + cstring = FindChild(*display); + if (cstring != NULL) { + tmpstr = UTFstring_to_cstr(*static_cast(cstring)); + label = tmpstr; + safefree(tmpstr); + } + language = FindChild(*display); + } + label += " ["; + tstart = FindChild(chapter); + if (tstart != NULL) { + timestamp = uint64(*static_cast(tstart)) / 1000000; + s.Printf("%02d:%02d:%02d.%03d", + (int)(timestamp / 1000 / 60 / 60), + (int)((timestamp / 1000 / 60) % 60), + (int)((timestamp / 1000) % 60), + (int)(timestamp % 1000)); + label += s; + + tend = FindChild(chapter); + if (tend != NULL) { + timestamp = uint64(*static_cast(tend)) / 1000000; + s.Printf("%02d:%02d:%02d.%03d", + (int)(timestamp / 1000 / 60 / 60), + (int)((timestamp / 1000 / 60) % 60), + (int)((timestamp / 1000) % 60), + (int)(timestamp % 1000)); + label += " - " + s; + } + + label += "; "; + } + if (language != NULL) + label += string(*static_cast(language)).c_str(); + else + label += "eng"; + + label += "]"; + + return label; +} + +void tab_chapters::add_recursively(wxTreeItemId &parent, EbmlMaster &master) { + uint32_t i; + wxString s; + EbmlElement *e; + KaxChapterAtom *chapter; + KaxEditionEntry *eentry; + wxTreeItemId this_item; + + for (i = 0; i < master.ListSize(); i++) { + e = master[i]; + if (EbmlId(*e) == KaxEditionEntry::ClassInfos.GlobalId) { + s.Printf("Edition %d", tc_chapters->GetChildrenCount(tid_root, false) + + 1); + eentry = static_cast(e); + this_item = + tc_chapters->AppendItem(parent, s, -1, -1, new tree_node_data(eentry)); + add_recursively(this_item, *static_cast(e)); + + } else if (EbmlId(*e) == KaxChapterAtom::ClassInfos.GlobalId) { + chapter = static_cast(e); + s = create_chapter_label(*chapter); + this_item = tc_chapters->AppendItem(parent, s, -1, -1, + new tree_node_data(chapter)); + add_recursively(this_item, *static_cast(e)); + } + } +} + +void tab_chapters::on_load_chapters(wxCommandEvent &evt) { + KaxChapters *new_chapters; + wxString s; + wxFileDialog dlg(NULL, "Choose a chapter file", last_open_dir, "", + _T("Chapter files (*.xml;*.txt)|*.xml;*.txt|" ALLFILES), + wxOPEN); + + if(dlg.ShowModal() == wxID_OK) { + last_open_dir = dlg.GetDirectory(); + try { + new_chapters = parse_chapters(dlg.GetPath().c_str(), 0, -1, 0, NULL, + NULL, true); + } catch (error_c e) { + s = (const char *)e; + break_line(s); + while (s[s.Length() - 1] == '\n') + s.Remove(s.Length() - 1); + wxMessageBox(s, _T("Error parsing the chapter file"), + wxOK | wxCENTER | wxICON_ERROR); + return; + } + + if (chapters != NULL) + delete chapters; + tc_chapters->DeleteAllItems(); + chapters = new_chapters; + m_chapters->Enable(ID_M_CHAPTERS_SAVE, true); + m_chapters->Enable(ID_M_CHAPTERS_SAVEAS, true); + m_chapters->Enable(ID_M_CHAPTERS_VERIFY, true); + b_add_chapter->Enable(true); + b_remove_chapter->Enable(true); + + file_name = dlg.GetPath(); + tid_root = tc_chapters->AddRoot(file_name); + add_recursively(tid_root, *chapters); + expand_subtree(*tc_chapters, tid_root); + + mdlg->set_status_bar("Chapters loaded."); + } +} + +void tab_chapters::on_save_chapters(wxCommandEvent &evt) { +} + +void tab_chapters::on_save_chapters_as(wxCommandEvent &evt) { +} + +void tab_chapters::on_verify_chapters(wxCommandEvent &evt) { +} + +void tab_chapters::on_add_chapter(wxCommandEvent &evt) { +} + +void tab_chapters::on_remove_chapter(wxCommandEvent &evt) { +} + +void tab_chapters::on_copy_values(wxTimerEvent &evt) { +} + +void tab_chapters::on_entry_selected(wxTreeEvent &evt) { + tree_node_data *t; + KaxChapterDisplay *display; + KaxChapterString *cstring; + KaxChapterTimeStart *tstart; + KaxChapterTimeEnd *tend; + EbmlElement *e; + wxString label, languages, countries; + int64_t timestamp; + uint32_t i; + char *tmpstr; + + t = (tree_node_data *)tc_chapters->GetItemData(evt.GetItem()); + + if ((evt.GetItem() == tid_root) || (t == NULL) || !t->is_atom) { + tc_chapter_name->SetValue(""); + tc_start_time->SetValue(""); + tc_end_time->SetValue(""); + tc_language_codes->SetValue(""); + tc_country_codes->SetValue(""); + return; + } + + label = "(unnamed chapter)"; + languages = ""; + countries = ""; + display = FindChild(*t->chapter); + if (display != NULL) { + cstring = FindChild(*display); + if (cstring != NULL) { + tmpstr = UTFstring_to_cstr(*static_cast(cstring)); + label = tmpstr; + safefree(tmpstr); + } + for (i = 0; i < display->ListSize(); i++) { + e = (*display)[i]; + if (EbmlId(*e) == KaxChapterLanguage::ClassInfos.GlobalId) { + if (languages.length() > 0) + languages += " "; + languages += string(*static_cast(e)).c_str(); + } else if (EbmlId(*e) == KaxChapterCountry::ClassInfos.GlobalId) { + if (countries.length() > 0) + countries += " "; + countries += string(*static_cast(e)).c_str(); + } + } + } + tc_chapter_name->SetValue(label); + tc_language_codes->SetValue(languages); + tc_country_codes->SetValue(countries); + + tstart = FindChild(*t->chapter); + if (tstart != NULL) { + timestamp = uint64(*static_cast(tstart)) / 1000000; + label.Printf("%02d:%02d:%02d.%03d", + (int)(timestamp / 1000 / 60 / 60), + (int)((timestamp / 1000 / 60) % 60), + (int)((timestamp / 1000) % 60), + (int)(timestamp % 1000)); + tc_start_time->SetValue(label); + } else + tc_start_time->SetValue(""); + + tend = FindChild(*t->chapter); + if (tend != NULL) { + timestamp = uint64(*static_cast(tend)) / 1000000; + label.Printf("%02d:%02d:%02d.%03d", + (int)(timestamp / 1000 / 60 / 60), + (int)((timestamp / 1000 / 60) % 60), + (int)((timestamp / 1000) % 60), + (int)(timestamp % 1000)); + tc_end_time->SetValue(label); + } else + tc_end_time->SetValue(""); +} + +IMPLEMENT_CLASS(tab_chapters, wxPanel); +BEGIN_EVENT_TABLE(tab_chapters, wxPanel) + EVT_BUTTON(ID_B_ADDCHAPTER, tab_chapters::on_add_chapter) + EVT_BUTTON(ID_B_REMOVECHAPTER, tab_chapters::on_remove_chapter) + EVT_TIMER(ID_T_CHAPTERVALUES, tab_chapters::on_copy_values) + EVT_TREE_SEL_CHANGED(ID_TRC_CHAPTERS, tab_chapters::on_entry_selected) +END_EVENT_TABLE();