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>
|
2003-11-09 Moritz Bunkus <moritz@bunkus.org>
|
||||||
|
|
||||||
* mkvmerge: Added support for --sync for VobSub tracks.
|
* 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
|
Read global tags from the XML \fIfile\fR. See the section about tags
|
||||||
below for details.
|
below for details.
|
||||||
.TP
|
.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
|
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
|
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
|
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
|
.LP
|
||||||
Chapter handling: (global options)
|
Chapter handling: (global options)
|
||||||
.TP
|
.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
|
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
|
only to simple chapter files. Defaults to "eng". See the section about chapters
|
||||||
below for details.
|
below for details.
|
||||||
.TP
|
.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
|
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
|
files. Defaults to the current system locale. See the section about chapters
|
||||||
below for details.
|
below for details.
|
||||||
.TP
|
.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>
|
\fB\-\-chapters <\fIfile\fR>
|
||||||
Read chapter information from the \fIfile\fR. See the section about chapters
|
Read chapter information from the \fIfile\fR. See the section about chapters
|
||||||
below for details.
|
below for details.
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include "chapters.h"
|
#include "chapters.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
|
#include "mkvmerge.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace libmatroska;
|
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,
|
KaxChapters *parse_chapters(const char *file_name, int64_t min_tc,
|
||||||
int64_t max_tc, int64_t offset,
|
int64_t max_tc, int64_t offset,
|
||||||
const char *language, const char *charset,
|
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;
|
*is_simple_format = true;
|
||||||
return parse_simple_chapters(in, min_tc, max_tc, offset, language,
|
return parse_simple_chapters(in, min_tc, max_tc, offset, language,
|
||||||
charset, exception_on_error);
|
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)
|
} else if (is_simple_format != NULL)
|
||||||
*is_simple_format = false;
|
*is_simple_format = false;
|
||||||
|
|
||||||
|
@ -49,6 +49,13 @@ KaxChapters *parse_simple_chapters(mm_text_io_c *in, int64_t min_tc,
|
|||||||
const char *charset,
|
const char *charset,
|
||||||
bool exception_on_error = false);
|
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_xml(KaxChapters *chapters, FILE *out);
|
||||||
void write_chapters_simple(int &chapter_num, 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"
|
" --chapters <file> Read chapter information from the file.\n"
|
||||||
" --chapter-language <lng> Set the 'language' element in chapter entries."
|
" --chapter-language <lng> Set the 'language' element in chapter entries."
|
||||||
"\n --chapter-charset <cset> Charset for a simple chapter file.\n"
|
"\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"
|
"\n General output control (advanced global options):\n"
|
||||||
" --cluster-length <n[ms]> Put at most n data blocks into each cluster.\n"
|
" --cluster-length <n[ms]> Put at most n data blocks into each cluster.\n"
|
||||||
" If the number is postfixed with 'ms' then\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);
|
chapter_charset = safestrdup(next_arg);
|
||||||
i++;
|
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")) {
|
} else if (!strcmp(this_arg, "--chapters")) {
|
||||||
if (next_arg == NULL)
|
if (next_arg == NULL)
|
||||||
mxerror("'--chapters' lacks the file name.\n");
|
mxerror("'--chapters' lacks the file name.\n");
|
||||||
@ -1854,6 +1868,11 @@ static void cleanup() {
|
|||||||
if (kax_as != NULL)
|
if (kax_as != NULL)
|
||||||
delete kax_as;
|
delete kax_as;
|
||||||
|
|
||||||
|
safefree(chapter_file_name);
|
||||||
|
safefree(chapter_language);
|
||||||
|
safefree(chapter_charset);
|
||||||
|
safefree(cue_to_chapter_name_format);
|
||||||
|
|
||||||
utf8_done();
|
utf8_done();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user