Added CUE sheet parsing for chapters.

This commit is contained in:
Moritz Bunkus 2003-11-10 22:16:29 +00:00
parent a88c9d00e4
commit 1dc08ad127
5 changed files with 297 additions and 3 deletions

View File

@ -1,3 +1,7 @@
2003-11-10 Moritz Bunkus <moritz@bunkus.org>
* mkvmerge: new feature: CUE sheets can be used for chapters.
2003-11-09 Moritz Bunkus <moritz@bunkus.org>
* mkvmerge: Added support for --sync for VobSub tracks.

View File

@ -38,7 +38,7 @@ Sets the general title for the output file, e.g. the movie name.
Read global tags from the XML \fIfile\fR. See the section about tags
below for details.
.TP
\fB\-\-command\-line\-charset\fR <\fBcharset\fR>
\fB\-\-command\-line\-charset\fR <\fIcharset\fR>
Sets the charset to convert strings given on the command line from. It defaults
to the charset given by system's current locale. This settings applies to
arguments of the following options: \fB\-\-title\fR, \fB\-\-track\-name\fR and
@ -47,16 +47,35 @@ arguments of the following options: \fB\-\-title\fR, \fB\-\-track\-name\fR and
.LP
Chapter handling: (global options)
.TP
\fB\-\-chapter\-language\fR <\fBlanguage\fR>
\fB\-\-chapter\-language\fR <\fIlanguage\fR>
Sets the ISO639-2 language code that is written for each chapter entry. Applies
only to simple chapter files. Defaults to "eng". See the section about chapters
below for details.
.TP
\fB\-\-chapter\-charset\fR <\fBcharset\fR>
\fB\-\-chapter\-charset\fR <\fIcharset\fR>
Sets the charset that is used for the conversion to UTF-8 for simple chapter
files. Defaults to the current system locale. See the section about chapters
below for details.
.TP
\fB\-\-cue\-chapter\-name\-format\fR <\fIformat\fR>
\fBmkvmerge\fR supports reading CUE sheets for audio files as the input for
chapters. CUE sheets usually contain the entries \fIPERFORMER\fR and
\fITITLE\fR for each index entry. \fBmkvmerge\fR uses these two strings
in order to construct the chapter name. With this option the format used
for this name can be set. The following meta characters are supported:
.br
\fB%p\fR is replaced by the current entry's \fIPERFORMER\fR string,
.br
\fB%t\fR is replaced by the current entry's \fITITLE\fR string and
.br
\fB%n\fR is replaced by the current track number.
.br
Everything else is copied as-is.
.br
If this option is not given then \fBmkvmerge\fR defaults to the
format '\fI%p - %t\fR' (the performer, followed by a space, a dash,
another space and the title).
.TP
\fB\-\-chapters <\fIfile\fR>
Read chapter information from the \fIfile\fR. See the section about chapters
below for details.

View File

@ -27,6 +27,7 @@
#include "chapters.h"
#include "error.h"
#include "mkvmerge.h"
using namespace std;
using namespace libmatroska;
@ -231,6 +232,245 @@ KaxChapters *parse_simple_chapters(mm_text_io_c *in, int64_t min_tc,
// }}}
// PERFORMER "Blackmore's Night"
// TITLE "Fires At Midnight"
// FILE "Range.wav" WAVE
// TRACK 01 AUDIO
// TITLE "Written In The Stars"
// PERFORMER "Blackmore's Night"
// INDEX 01 00:00:00
// TRACK 02 AUDIO
// TITLE "The Times They Are A Changin'"
// PERFORMER "Blackmore's Night"
// INDEX 00 04:46:62
// INDEX 01 04:49:64
bool probe_cue_chapters(mm_text_io_c *in) {
string s;
in->setFilePointer(0);
if (!in->getline2(s))
return false;
if (starts_with_case(s, "performer ") || starts_with_case(s, "title ") ||
starts_with_case(s, "file "))
return true;
return false;
}
char *cue_to_chapter_name_format = NULL;
static void cue_entries_to_chapter_name(string &performer, string &title,
string &global_performer,
string &global_title, string &name,
int num) {
const char *this_char, *next_char;
name = "";
if (title.length() == 0)
title = global_title;
if (performer.length() == 0)
performer = global_performer;
if (cue_to_chapter_name_format == NULL)
this_char = "%p - %t";
else
this_char = cue_to_chapter_name_format;
next_char = this_char + 1;
while (*this_char != 0) {
if (*this_char == '%') {
if (*next_char == 'p')
name += performer;
else if (*next_char == 't')
name += title;
else if (*next_char == 'n')
name += to_string(num);
else {
name += *this_char;
this_char--;
}
this_char++;
} else
name += *this_char;
this_char++;
next_char = this_char + 1;
}
}
KaxChapters *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) {
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;
in->setFilePointer(0);
chapters = new KaxChapters;
if (in->get_byte_order() == BO_NONE) {
do_convert = true;
cc_utf8 = utf8_init(charset);
} else
do_convert = false;
if (language == NULL)
language = "eng";
atom = NULL;
edition = NULL;
num = 0;
line_num = 0;
start = -1;
try {
while (in->getline2(line)) {
line_num++;
strip(line);
if ((line.length() == 0) || starts_with_case(line, "file "))
continue;
if (starts_with_case(line, "performer ")) {
line.erase(0, 10);
strip(line);
if (line.length() < 3)
continue;
if (line[0] == '"')
line.erase(0, 1);
if (line[line.length() - 1] == '"')
line.erase(line.length() - 1);
if (num == 0)
global_performer = line;
else
performer = line;
} else if (starts_with_case(line, "title ")) {
line.erase(0, 6);
strip(line);
if (line.length() < 3)
continue;
if (line[0] == '"')
line.erase(0, 1);
if (line[line.length() - 1] == '"')
line.erase(line.length() - 1);
if (num == 0)
global_title = line;
else
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;
} 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<KaxEditionEntry>(*chapters);
if (atom == NULL)
atom = &GetChild<KaxChapterAtom>(*edition);
else
atom = &GetNextChild<KaxChapterAtom>(*edition, *atom);
*static_cast<EbmlUInteger *>(&GetChild<KaxChapterUID>(*atom)) =
create_unique_uint32();
*static_cast<EbmlUInteger *>(&GetChild<KaxChapterTimeStart>(*atom)) =
(start - offset) * 1000000;
display = &GetChild<KaxChapterDisplay>(*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<EbmlUnicodeString *>
(&GetChild<KaxChapterString>(*display)) = wchar_string;
*static_cast<EbmlString *>(&GetChild<KaxChapterLanguage>(*display)) =
language;
}
num++;
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<KaxEditionEntry>(*chapters);
if (atom == NULL)
atom = &GetChild<KaxChapterAtom>(*edition);
else
atom = &GetNextChild<KaxChapterAtom>(*edition, *atom);
*static_cast<EbmlUInteger *>(&GetChild<KaxChapterUID>(*atom)) =
create_unique_uint32();
*static_cast<EbmlUInteger *>(&GetChild<KaxChapterTimeStart>(*atom)) =
(start - offset) * 1000000;
display = &GetChild<KaxChapterDisplay>(*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<EbmlUnicodeString *>
(&GetChild<KaxChapterString>(*display)) = wchar_string;
*static_cast<EbmlString *>(&GetChild<KaxChapterLanguage>(*display)) =
language;
}
}
} catch(error_c e) {
delete in;
delete chapters;
throw error_c(e);
}
delete in;
if (num == 0) {
delete chapters;
return NULL;
}
return chapters;
}
KaxChapters *parse_chapters(const char *file_name, int64_t min_tc,
int64_t max_tc, int64_t offset,
const char *language, const char *charset,
@ -253,6 +493,11 @@ KaxChapters *parse_chapters(const char *file_name, int64_t min_tc,
*is_simple_format = true;
return parse_simple_chapters(in, min_tc, max_tc, offset, language,
charset, exception_on_error);
} else if (probe_cue_chapters(in)) {
if (is_simple_format != NULL)
*is_simple_format = true;
return parse_cue_chapters(in, min_tc, max_tc, offset, language,
charset, exception_on_error);
} else if (is_simple_format != NULL)
*is_simple_format = false;

View File

@ -49,6 +49,13 @@ KaxChapters *parse_simple_chapters(mm_text_io_c *in, int64_t min_tc,
const char *charset,
bool exception_on_error = false);
extern char *cue_to_chapter_name_format;
bool probe_cue_chapters(mm_text_io_c *in);
KaxChapters *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);
void write_chapters_xml(KaxChapters *chapters, FILE *out);
void write_chapters_simple(int &chapter_num, KaxChapters *chapters, FILE *out);

View File

@ -256,6 +256,9 @@ static void usage() {
" --chapters <file> Read chapter information from the file.\n"
" --chapter-language <lng> Set the 'language' element in chapter entries."
"\n --chapter-charset <cset> Charset for a simple chapter file.\n"
" --cue-chapter-name-format\n"
" Pattern for the conversion from CUE sheet\n"
" entries to chapter names.\n"
"\n General output control (advanced global options):\n"
" --cluster-length <n[ms]> Put at most n data blocks into each cluster.\n"
" If the number is postfixed with 'ms' then\n"
@ -1430,6 +1433,17 @@ static void parse_args(int argc, char **argv) {
chapter_charset = safestrdup(next_arg);
i++;
} else if (!strcmp(this_arg, "--cue-chapter-name-format")) {
if (next_arg == NULL)
mxerror("'--cue-chapter-name-format' lacks the format.\n");
if (chapter_file_name != NULL)
mxerror("'--cue-chapter-name-format' must be given before '--chapters'"
".\n");
safefree(cue_to_chapter_name_format);
cue_to_chapter_name_format = safestrdup(next_arg);
i++;
} else if (!strcmp(this_arg, "--chapters")) {
if (next_arg == NULL)
mxerror("'--chapters' lacks the file name.\n");
@ -1854,6 +1868,11 @@ static void cleanup() {
if (kax_as != NULL)
delete kax_as;
safefree(chapter_file_name);
safefree(chapter_language);
safefree(chapter_charset);
safefree(cue_to_chapter_name_format);
utf8_done();
}