mirror of
https://gitlab.com/mbunkus/mkvtoolnix.git
synced 2024-12-24 11:54:01 +00:00
Added CUE sheet parsing for chapters.
This commit is contained in:
parent
a88c9d00e4
commit
1dc08ad127
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user