From 27c5169e600034acf32504863cc082d8a7400b42 Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Wed, 7 Jul 2004 18:51:19 +0000 Subject: [PATCH] Automatically convert tags found in CUE sheets that are used as chapter files. --- ChangeLog | 5 + src/common/chapters.cpp | 328 +++++++++++++++++++++++++++------------- src/common/chapters.h | 14 +- src/mkvmerge.cpp | 55 ++++++- src/pr_generic.cpp | 12 ++ src/pr_generic.h | 2 + 6 files changed, 304 insertions(+), 112 deletions(-) diff --git a/ChangeLog b/ChangeLog index ffb558321..b67dde893 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2004-07-07 Moritz Bunkus + + * mkvmerge: new feature: When using a CUE sheet as a chapter file + mkvmerge will automatically convert some of the entries to tags. + 2004-07-03 Moritz Bunkus * mkvmerge: bug fix: The default track feature did not work diff --git a/src/common/chapters.cpp b/src/common/chapters.cpp index 18d66d2dd..a7cd47283 100644 --- a/src/common/chapters.cpp +++ b/src/common/chapters.cpp @@ -24,6 +24,7 @@ #include #include +#include #include "chapters.h" #include "commonebml.h" @@ -282,7 +283,9 @@ probe_cue_chapters(mm_text_io_c *in) { if (!in->getline2(s)) return false; if (starts_with_case(s, "performer ") || starts_with_case(s, "title ") || - starts_with_case(s, "file ") || starts_with_case(s, "catalog ")) + starts_with_case(s, "file ") || starts_with_case(s, "catalog ") || + starts_with_case(s, "rem date") || starts_with_case(s, "rem genre") || + starts_with_case(s, "rem discid")) return true; return false; } @@ -333,6 +336,152 @@ cue_entries_to_chapter_name(string &performer, } } +typedef struct { + int num; + int64_t start; + int64_t end; + int64_t min_tc; + int64_t max_tc; + int64_t offset; + KaxChapters *chapters; + KaxEditionEntry *edition; + KaxChapterAtom *atom; + bool do_convert; + string performer; + string title; + string global_performer; + string global_title; + string name; + string date; + string genre; + string disc_id; + string isrc; + const char *language; + int line_num; + int cc_utf8; +} cue_parser_args_t; + +static UTFstring +cue_str_internal_to_utf(cue_parser_args_t &a, + const string &s) { + if (a.do_convert) { + UTFstring wchar_string; + char *recoded_string; + + recoded_string = to_utf8(a.cc_utf8, s.c_str()); + wchar_string = cstrutf8_to_UTFstring(recoded_string); + safefree(recoded_string); + + return wchar_string; + } else + return cstrutf8_to_UTFstring(s.c_str()); +} + +static KaxTagSimple * +create_simple_tag(cue_parser_args_t &a, + const string &name, + const string &value) { + KaxTagSimple *simple; + + simple = new KaxTagSimple; + *static_cast(&GetChild(*simple)) = + cue_str_internal_to_utf(a, name); + *static_cast(&GetChild(*simple)) = + cue_str_internal_to_utf(a, value); + + return simple; +} + +static bool +set_string2(string &dst, + const string &s1, + const string &s2) { + if (s1.length() != 0) { + dst = s1; + return true; + } else if (s2.length() != 0) { + dst = s2; + return true; + } else + return false; +} + +static void +add_tag_for_cue_entry(cue_parser_args_t &a, + KaxTags **tags, + uint32_t cuid) { + KaxTag *tag; + KaxTagTargets *targets; + string s; + + if (tags == NULL) + return; + + if (*tags == NULL) + *tags = new KaxTags; + + tag = new KaxTag; + targets = &GetChild(*tag); + *static_cast(&GetChild(*targets)) = cuid; + + tag->PushElement(*create_simple_tag(a, "TITLE", a.title)); + tag->PushElement(*create_simple_tag(a, "TRACKNUMBER", + mxsprintf("%d", a.num + 1))); + if (set_string2(s, a.performer, a.global_performer)) + tag->PushElement(*create_simple_tag(a, "ARTIST", s)); + if (set_string2(s, a.title, a.global_title)) + tag->PushElement(*create_simple_tag(a, "ALBUM", s)); + if (a.date.length() > 0) + tag->PushElement(*create_simple_tag(a, "DATE", a.date)); + if (a.genre.length() > 0) + tag->PushElement(*create_simple_tag(a, "GENRE", a.genre)); + if (a.disc_id.length() > 0) + tag->PushElement(*create_simple_tag(a, "DISCID", a.disc_id)); + if (a.isrc.length() > 0) + tag->PushElement(*create_simple_tag(a, "ISRC", a.isrc)); + + (*tags)->PushElement(*tag); +} + +static void +add_elements_for_cue_entry(cue_parser_args_t &a, + KaxTags **tags) { + KaxChapterDisplay *display; + UTFstring wchar_string; + uint32_t cuid; + + if (a.start == -1) + mxerror("Cue sheet parser: No INDEX entry found for the previous " + "TRACK entry (current line: %d)\n", a.line_num); + if (!((a.start >= a.min_tc) && ((a.start <= a.max_tc) || (a.max_tc == -1)))) + return; + + if (a.edition == NULL) + a.edition = &GetChild(*a.chapters); + if (a.atom == NULL) + a.atom = &GetChild(*a.edition); + else + a.atom = &GetNextChild(*a.edition, *a.atom); + + cuid = create_unique_uint32(); + *static_cast(&GetChild(*a.atom)) = cuid; + + *static_cast(&GetChild(*a.atom)) = + (a.start - a.offset) * 1000000; + + display = &GetChild(*a.atom); + + cue_entries_to_chapter_name(a.performer, a.title, a.global_performer, + a.global_title, a.name, a.num); + *static_cast (&GetChild(*display)) = + cue_str_internal_to_utf(a, a.name); + + *static_cast(&GetChild(*display)) = + a.language; + + add_tag_for_cue_entry(a, tags, cuid); +} + KaxChapters * parse_cue_chapters(mm_text_io_c *in, int64_t min_tc, @@ -340,32 +489,28 @@ parse_cue_chapters(mm_text_io_c *in, int64_t offset, const char *language, const char *charset, - bool exception_on_error) { - KaxChapters *chapters; - KaxEditionEntry *edition; - KaxChapterAtom *atom; - KaxChapterDisplay *display; - int line_num, num, index, min, sec, csec, cc_utf8; - int64_t start; - string global_title, global_performer, title, performer, line, name; - UTFstring wchar_string; - bool do_convert; - char *recoded_string; + bool exception_on_error, + KaxTags **tags) { + int index, min, sec, csec; + cue_parser_args_t a; + string line; in->setFilePointer(0); - chapters = new KaxChapters; + a.chapters = new KaxChapters; if (in->get_byte_order() == BO_NONE) { - do_convert = true; - cc_utf8 = utf8_init(charset); + a.do_convert = true; + a.cc_utf8 = utf8_init(charset); } else { - do_convert = false; - cc_utf8 = 0; + a.do_convert = false; + a.cc_utf8 = 0; } if (language == NULL) - language = "eng"; + a.language = "eng"; + else + a.language = language; // The core now uses ns precision timecodes. if (min_tc > 0) @@ -374,15 +519,18 @@ parse_cue_chapters(mm_text_io_c *in, max_tc /= 1000000; if (offset > 0) offset /= 1000000; + a.min_tc = min_tc; + a.max_tc = max_tc; + a.offset = offset; - atom = NULL; - edition = NULL; - num = 0; - line_num = 0; - start = -1; + a.atom = NULL; + a.edition = NULL; + a.num = 0; + a.line_num = 0; + a.start = -1; try { while (in->getline2(line)) { - line_num++; + a.line_num++; strip(line); if ((line.length() == 0) || starts_with_case(line, "file ")) continue; @@ -396,10 +544,10 @@ parse_cue_chapters(mm_text_io_c *in, line.erase(0, 1); if (line[line.length() - 1] == '"') line.erase(line.length() - 1); - if (num == 0) - global_performer = line; + if (a.num == 0) + a.global_performer = line; else - performer = line; + a.performer = line; } else if (starts_with_case(line, "title ")) { line.erase(0, 6); @@ -410,117 +558,82 @@ parse_cue_chapters(mm_text_io_c *in, line.erase(0, 1); if (line[line.length() - 1] == '"') line.erase(line.length() - 1); - if (num == 0) - global_title = line; + if (a.num == 0) + a.global_title = line; else - title = line; + a.title = line; } else if (starts_with_case(line, "index ")) { line.erase(0, 6); strip(line); if (sscanf(line.c_str(), "%d %d:%d:%d", &index, &min, &sec, &csec) < 4) mxerror("Cue sheet parser: Invalid INDEX entry in line %d.\n", - line_num); - if ((start == -1) || (index == 1)) - start = (min * 60 * 100 + sec * 100 + csec) * 10; + a.line_num); + if ((a.start == -1) || (index == 1)) + a.start = (min * 60 * 100 + sec * 100 + csec) * 10; } else if (starts_with_case(line, "track ")) { if ((line.length() < 5) || strcasecmp(&line[line.length() - 5], "audio")) continue; - if (num >= 1) { - if (start == -1) - mxerror("Cue sheet parser: No INDEX entry found for the previous " - "TRACK entry (current line: %d)\n", line_num); - if (!((start >= min_tc) && ((start <= max_tc) || (max_tc == -1)))) - continue; - if (edition == NULL) - edition = &GetChild(*chapters); - if (atom == NULL) - atom = &GetChild(*edition); - else - atom = &GetNextChild(*edition, *atom); + if (a.num >= 1) + add_elements_for_cue_entry(a, tags); - *static_cast(&GetChild(*atom)) = - create_unique_uint32(); + a.num++; - *static_cast(&GetChild(*atom)) = - (start - offset) * 1000000; + a.start = -1; + a.performer = ""; + a.title = ""; + a.isrc = ""; - display = &GetChild(*atom); + } else if (starts_with_case(line, "rem date ")) { + line.erase(0, 9); + strip(line); + if ((line.length() > 0) && (line[0] == '"')) + line.erase(0, 1); + if ((line.length() > 0) && (line[line.length() - 1] == '"')) + line.erase(line.length() - 1); + a.date = line; - cue_entries_to_chapter_name(performer, title, global_performer, - global_title, name, num); - if (do_convert) { - recoded_string = to_utf8(cc_utf8, name.c_str()); - wchar_string = cstrutf8_to_UTFstring(recoded_string); - safefree(recoded_string); - } else - wchar_string = cstrutf8_to_UTFstring(name.c_str()); - *static_cast - (&GetChild(*display)) = wchar_string; + } else if (starts_with_case(line, "rem genre ")) { + line.erase(0, 10); + strip(line); + if ((line.length() > 0) && (line[0] == '"')) + line.erase(0, 1); + if ((line.length() > 0) && (line[line.length() - 1] == '"')) + line.erase(line.length() - 1); + a.genre = line; - *static_cast(&GetChild(*display)) = - language; - } - num++; + } else if (starts_with_case(line, "rem discid ")) { + line.erase(0, 11); + strip(line); + if ((line.length() > 0) && (line[0] == '"')) + line.erase(0, 1); + if ((line.length() > 0) && (line[line.length() - 1] == '"')) + line.erase(line.length() - 1); + a.disc_id = line; - start = -1; - performer = ""; - title = ""; } } - if (num >= 1) { - if (start == -1) - mxerror("Cue sheet parser: No INDEX entry found for the previous " - "TRACK entry (current line: %d)\n", line_num); - if ((start >= min_tc) && ((start <= max_tc) || (max_tc == -1))) { - if (edition == NULL) - edition = &GetChild(*chapters); - if (atom == NULL) - atom = &GetChild(*edition); - else - atom = &GetNextChild(*edition, *atom); + if (a.num >= 1) + add_elements_for_cue_entry(a, tags); - *static_cast(&GetChild(*atom)) = - create_unique_uint32(); - - *static_cast(&GetChild(*atom)) = - (start - offset) * 1000000; - - display = &GetChild(*atom); - - cue_entries_to_chapter_name(performer, title, global_performer, - global_title, name, num); - if (do_convert) { - recoded_string = to_utf8(cc_utf8, name.c_str()); - wchar_string = cstrutf8_to_UTFstring(recoded_string); - safefree(recoded_string); - } else - wchar_string = cstrutf8_to_UTFstring(name.c_str()); - *static_cast - (&GetChild(*display)) = wchar_string; - - *static_cast(&GetChild(*display)) = - language; - } - } } catch(error_c e) { delete in; - delete chapters; + delete a.chapters; throw error_c(e); } delete in; - if (num == 0) { - delete chapters; + if (a.num == 0) { + delete a.chapters; return NULL; } - return chapters; + return a.chapters; } KaxChapters * @@ -531,7 +644,8 @@ parse_chapters(const char *file_name, const char *language, const char *charset, bool exception_on_error, - bool *is_simple_format) { + bool *is_simple_format, + KaxTags **tags) { mm_text_io_c *in; in = NULL; @@ -555,7 +669,7 @@ parse_chapters(const char *file_name, if (is_simple_format != NULL) *is_simple_format = true; return parse_cue_chapters(in, min_tc, max_tc, offset, language, - charset, exception_on_error); + charset, exception_on_error, tags); } else if (is_simple_format != NULL) *is_simple_format = false; diff --git a/src/common/chapters.h b/src/common/chapters.h index b030b42e5..53fa7423f 100644 --- a/src/common/chapters.h +++ b/src/common/chapters.h @@ -23,11 +23,14 @@ #include -#include - #include "common.h" #include "mm_io.h" +namespace libmatroska { + class KaxChapters; + class KaxTags; +}; + using namespace libmatroska; KaxChapters *MTX_DLL_API @@ -36,7 +39,8 @@ parse_chapters(const char *file_name, int64_t min_tc = 0, const char *language = NULL, const char *charset = NULL, bool exception_on_error = false, - bool *is_simple_format = NULL); + bool *is_simple_format = NULL, + KaxTags **tags = NULL); bool MTX_DLL_API probe_xml_chapters(mm_text_io_c *in); KaxChapters *MTX_DLL_API parse_xml_chapters(mm_text_io_c *in, int64_t min_tc, @@ -56,7 +60,9 @@ KaxChapters *MTX_DLL_API parse_cue_chapters(mm_text_io_c *in, int64_t min_tc, int64_t max_tc, int64_t offset, const char *language, const char *charset, - bool exception_on_error = false); + bool exception_on_error = false, + KaxTags **tags = NULL); + void MTX_DLL_API write_chapters_xml(KaxChapters *chapters, FILE *out); void MTX_DLL_API write_chapters_simple(int &chapter_num, KaxChapters *chapters, diff --git a/src/mkvmerge.cpp b/src/mkvmerge.cpp index 47de992dd..6ebb4e11d 100644 --- a/src/mkvmerge.cpp +++ b/src/mkvmerge.cpp @@ -175,6 +175,7 @@ EbmlVoid *kax_sh_void = NULL; KaxDuration *kax_duration; KaxSeekHead *kax_sh_main = NULL, *kax_sh_cues = NULL; KaxTags *kax_tags = NULL; +KaxTags *tags_from_cue_chapters = NULL; KaxAttachments *kax_as = NULL; KaxChapters *kax_chapters = NULL; EbmlVoid *kax_chapters_void = NULL; @@ -1918,7 +1919,8 @@ parse_args(int argc, if (kax_chapters != NULL) delete kax_chapters; kax_chapters = parse_chapters(chapter_file_name, 0, -1, 0, - chapter_language, chapter_charset); + chapter_language, chapter_charset, false, + false, &tags_from_cue_chapters); i++; } else if (!strcmp(this_arg, "--no-chapters")) { @@ -2369,6 +2371,8 @@ cleanup() { safefree(outfile); if (kax_tags != NULL) delete kax_tags; + if (tags_from_cue_chapters != NULL) + delete tags_from_cue_chapters; if (kax_chapters != NULL) delete kax_chapters; if (kax_as != NULL) @@ -2443,6 +2447,53 @@ create_output_name() { return s; } +void +add_tags_from_cue_chapters() { + int i; + uint32_t tuid; + bool found; + + if (tags_from_cue_chapters == NULL) + return; + + found = false; + for (i = 0; i < ptzrs_in_header_order.size(); i++) + if (ptzrs_in_header_order[i]->get_track_type() == 'v') { + found = true; + tuid = ptzrs_in_header_order[i]->get_uid(); + break; + } + if (!found) { + for (i = 0; i < ptzrs_in_header_order.size(); i++) + if (ptzrs_in_header_order[i]->get_track_type() == 'a') { + found = true; + tuid = ptzrs_in_header_order[i]->get_uid(); + break; + } + } + if (!found) + tuid = ptzrs_in_header_order[0]->get_uid(); + + for (i = 0; i < tags_from_cue_chapters->ListSize(); i++) { + KaxTagTargets *targets; + + targets = &GetChild + (*static_cast((*tags_from_cue_chapters)[i])); + *static_cast(&GetChild(*targets)) = tuid; + } + + if (kax_tags == NULL) + kax_tags = tags_from_cue_chapters; + else { + while (tags_from_cue_chapters->ListSize() > 0) { + kax_tags->PushElement(*(*tags_from_cue_chapters)[0]); + tags_from_cue_chapters->Remove(0); + } + delete tags_from_cue_chapters; + } + tags_from_cue_chapters = NULL; +} + /** \brief Creates the next output file * * Creates a new file name depending on the split settings. Opens that @@ -2497,6 +2548,8 @@ create_next_output_file() { } } + add_tags_from_cue_chapters(); + if (kax_tags != NULL) { if (!kax_tags->CheckMandatory()) mxerror("Some tag elements are missing (this error " diff --git a/src/pr_generic.cpp b/src/pr_generic.cpp index 9df9b49bb..3fd170451 100644 --- a/src/pr_generic.cpp +++ b/src/pr_generic.cpp @@ -37,6 +37,8 @@ using namespace std; +vector ptzrs_in_header_order; + generic_packetizer_c::generic_packetizer_c(generic_reader_c *nreader, track_info_c *nti) throw(error_c) { @@ -483,6 +485,16 @@ void generic_packetizer_c::set_headers() { int idx, disp_width, disp_height; KaxTag *tag; + bool found; + + found = false; + for (idx = 0; idx < ptzrs_in_header_order.size(); idx++) + if (ptzrs_in_header_order[idx] == this) { + found = true; + break; + } + if (!found) + ptzrs_in_header_order.push_back(this); if (track_entry == NULL) { if (kax_last_entry == NULL) diff --git a/src/pr_generic.h b/src/pr_generic.h index 017164f04..714dde446 100644 --- a/src/pr_generic.h +++ b/src/pr_generic.h @@ -475,4 +475,6 @@ protected: virtual void dump_packet(const void *buffer, int size); }; +extern vector ptzrs_in_header_order; + #endif // __PR_GENERIC_H