diff --git a/ChangeLog b/ChangeLog index 83f0858ed..55a876ca6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2003-07-14 Moritz Bunkus + * mkvmerge: Added support for attaching files to the output + file(s). + * mkvinfo: Support for the elements dealing with attachments (KaxAttachments, KaxAttached, KaxFileDescription, KaxFileName, KaxMimeType, KaxFileData). diff --git a/doc/mkvmerge.1 b/doc/mkvmerge.1 index 596594252..313fe573c 100644 --- a/doc/mkvmerge.1 +++ b/doc/mkvmerge.1 @@ -1,18 +1,23 @@ .TH MKVMERGE "1" "June 2003" "mkvmerge v0.5.0" "User Commands" + .SH NAME mkvmerge \- Merge multimedia streams into a Matroska file + .SH SYNOPSIS .B mkvmerge [\fIglobal options\fR] \-o \fIout\fR [\fIoptions\fR] [[\fIoptions\fR] ...] [@optionsfile] + .SH DESCRIPTION .LP This program takes the input from several media files and joins their streams (all of them or just a selection) into a Matroska file. .UR http://www.matroska.org/ .UE + + .LP Global options: .TP @@ -87,6 +92,35 @@ section \fBFILE LINKING\fR below for details. \fB\-\-link\-to\-next\fR <\fIUID\fR> Links the last output file to the segment with the given \fIUID\fR. See the section \fBFILE LINKING\fR below for details. +.TP +\fB\-\-attachment\-description\fR <\fIdescription\fR> +Plain text description of the following attachment. Applies to the next +\fB\-\-attach\-file\fR or \fB\-\-attach\-file\-once\fR command. +.TP +\fB\-\-attachment\-mime\-type\fR <\fIMIME type\fR> +MIME type of the following attachment. Applies to the next +\fB\-\-attach\-file\fR or \fB\-\-attach\-file\-once\fR command. +A list of officially recognized MIME types can be found e.g. at +.URL +ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types +The MIME type is mandatory for an attachment. +.TP +\fB\-\-attach\-file\fR <\fIfile name\fR> +.TP +\fB\-\-attach\-file\-once\fR <\fIfile name\fR> +Creates a file attachment inside the Matroska file. The MIME type must have +been set before this option can used. The difference between the two forms +is that during splitting the files attached with \fB\-\-attach\-file\fR are +attached to all output files while the ones attached with +\fB\-\-attach\-file\-once\fR are only attached to the first file created. +If splitting is not used then both do the same. +.br +\fBmkvextract\fR can be used to extract attached files from a Matroska file. +.br +\fBNote:\fR If an input file is a Matroska file then the attached files will +not be copied to the output file(s). This may change in the future. + + .LP Options that can be used for each input file: .TP @@ -167,6 +201,8 @@ listed with the \fB\-\-list\-languages\fR option. .br This option can be used multiple times for an input file applying to several tracks by selecting different track IDs each time. + + .LP Options that only apply to video tracks: .TP @@ -358,6 +394,7 @@ If you do not see the language or default track flags that you've specified in \fBmkvinfo\fR's output then please read the section about \fBDEFAULT VALUES\fR. + .SH SUBTITLES .LP There are several text subtitle formats that can be embedded into Matroska. @@ -382,6 +419,7 @@ limitations: 1) Files saved with more than one byte per character (e.g. all UTF-16 formats) are not supported, UTF-8/ASCII only; 2) The \fB\\fe\fR markup is not supported correctly. + .SH FILE LINKING .LP Matroska supports file linking which simply says that a specific file is the @@ -419,6 +457,7 @@ If splitting is used then the first file is linked to the UID given with with \'\fB\-\-link\-to\-next\fR\'. If splitting is not used then the one output file will be linked to both of the two UIDs. + .SH TRACK IDS .LP Some of the options for \fBmkvmerge\fR need a track ID to specify which track @@ -448,6 +487,7 @@ The options that use the track IDs are: \fB\-\-atracks\fR, \fB\-\-vtracks\fR, \fB\-\-stracks\fR, \fB\-\-sync\fR, \fB\-\-default-track\fR, \fB\-\-cues\fR and \fB\-\-language\fR. + .SH DEFAULT VALUES .LP The Matroska specs say that some elements have a default value. Usually an @@ -459,6 +499,27 @@ and the default value for the \fIdefault track flag\fR is \fItrue\fR. Therefore if you used \fB--language 0:eng\fR for a track then it will not show up in \fBmkvinfo\fR's output. + +.SH ATTACHMENTS +.LP +Maybe you also want to keep some photos along with your Matroska file, or +you're using SSA subtitles and need a special TrueType font that's really +rare. In these cases you can attach those files to the Matroska file. They +will not be just appended to the file but embedded in it. A player can then +show those files (the 'photos' case) or use them to render the subtitles +(the 'TrueType fonts' case). +.LP +Here's an example how to attach a photo and a TrueType font to the output +file: +.br +$ \fBmkvmerge -o output.mkv -A video.avi sound.ogg \-\-attachment\-description +"Me and the band behind the stage in a small get-together" +\-\-attachment\-mime\-type image/jpeg \-\-attach\-file me_and_the_band.jpg +\-\-attachment\-description "The real rare and unbelievably good looking font" +\-\-attachment\-type application/octet\-stream +\-\-attach\-file really_cool_font.ttf + + .SH NOTES .LP What works: @@ -487,6 +548,9 @@ DTS audio files MP3 audio files .TP * +RealVideo and RealAudio from RealMedia files +.TP +* Track selection .TP * diff --git a/src/mkvmerge.cpp b/src/mkvmerge.cpp index 3f4772d0b..14b18945c 100644 --- a/src/mkvmerge.cpp +++ b/src/mkvmerge.cpp @@ -45,6 +45,8 @@ #include #include +#include +#include #include #include #include @@ -110,8 +112,16 @@ typedef struct { generic_packetizer_c *packetizer; } packetizer_t; +typedef struct { + char *name, *mime_type, *description; + int64_t size; + bool to_all_files; +} attachment_t; + vector packetizers; vector files; +vector attachments; +int64_t attachment_sizes_first = 0, attachment_sizes_others = 0; // Variables set by the command line parser. char *outfile = NULL; @@ -207,6 +217,15 @@ static void usage(void) { " --dont-link Don't link splitted files.\n" " --link-to-previous Link the first file to the given UID.\n" " --link-to-next Link the last file to the given UID.\n" + " --attachment-description \n" + " Description for the following attachment.\n" + " --attachment-mime-type \n" + " Mime type for the following attachment.\n" + " --attach-file Creates a file attachment inside the\n" + " Matroska file.\n" + " --attach-file-once \n" + " Creates a file attachment inside the\n" + " firsts Matroska file written.\n" "\n Options for each input file:\n" " -a, --atracks Copy audio tracks n,m etc. Default: copy all\n" " audio tracks.\n" @@ -426,6 +445,8 @@ static float parse_aspect_ratio(char *s) { float w, h; div = strchr(s, '/'); + if (div == NULL) + div = strchr(s, ':'); if (div == NULL) return strtod(s, NULL); @@ -703,6 +724,76 @@ static void render_headers(mm_io_c *out, bool last_file, bool first_file) { } } +static void render_attachments(IOCallback *out) { + KaxAttachements *kax_as; + KaxAttached *kax_a; + KaxFileData *fdata; + attachment_t *attch; + int i; + char *name; + binary *buffer; + int64_t size; + mm_io_c *io; + + if (!(((file_num == 1) && (attachment_sizes_first > 0)) || + (attachment_sizes_others > 0))) + return; + + kax_as = new KaxAttachements(); + kax_a = NULL; + for (i = 0; i < attachments.size(); i++) { + attch = attachments[i]; + + if ((file_num == 1) || attch->to_all_files) { + if (kax_a == NULL) + kax_a = &GetChild(*kax_as); + else + kax_a = &GetNextChild(*kax_as, *kax_a); + + if (attch->description != NULL) + *static_cast + (&GetChild(*kax_a)) = + cstr_to_UTFstring(attch->mime_type);; + + if (attch->mime_type != NULL) + *static_cast(&GetChild(*kax_a)) = + attch->mime_type; + + name = &attch->name[strlen(attch->name) - 1]; + while ((name != attch->name) && (*name != '/')) + name--; + if (*name == '/') + name++; + if (*name == 0) + die("Internal error: *name == 0 on %d.", __LINE__); + + *static_cast + (&GetChild(*kax_a)) = + cstr_to_UTFstring(name); + + try { + io = new mm_io_c(attch->name, MODE_READ); + io->setFilePointer(0, seek_end); + size = io->getFilePointer(); + io->setFilePointer(0, seek_beginning); + + buffer = new binary[size]; + io->read(buffer, size); + delete io; + + fdata = &GetChild(*kax_a); + fdata->SetBuffer(buffer, size); + } catch (...) { + mxprint(stderr, "Error: Could not open the attachment '%s'.\n", + attch->name); + } + } + } + + kax_as->Render(*out); + delete kax_as; +} + static void create_readers() { filelist_t *file; int i; @@ -851,6 +942,8 @@ static void parse_args(int argc, char **argv) { cue_creation_t cues; int64_t id; language_t lang; + attachment_t *attachment; + mm_io_c *io; memset(&ti, 0, sizeof(track_info_t)); ti.audio_syncs = new vector; @@ -862,6 +955,8 @@ static void parse_args(int argc, char **argv) { ti.atracks = new vector; ti.vtracks = new vector; ti.stracks = new vector; + attachment = (attachment_t *)safemalloc(sizeof(attachment_t)); + memset(attachment, 0, sizeof(attachment_t)); // Check if only information about the file is wanted. In this mode only // two parameters are allowed: the --identify switch and the file. @@ -1034,6 +1129,62 @@ static void parse_args(int argc, char **argv) { else if (!strcmp(argv[i], "--no-lacing")) no_lacing = true; + else if (!strcmp(argv[i], "--attachment-description")) { + if ((i + 1) >= argc) { + mxprint(stderr, "Error: --attachment-description lacks the " + "description.\n"); + exit(1); + } + safefree(attachment->description); + attachment->description = safestrdup(argv[i + 1]); + i++; + + } else if (!strcmp(argv[i], "--attachment-mime-type")) { + if ((i + 1) >= argc) { + mxprint(stderr, "Error: --attachment-mime-type lacks the " + "MIME type.\n"); + exit(1); + } + safefree(attachment->mime_type); + attachment->mime_type = safestrdup(argv[i + 1]); + i++; + + } else if (!strcmp(argv[i], "--attach-file") || + !strcmp(argv[i], "--attach-file-once")) { + if ((i + 1) >= argc) { + mxprint(stderr, "Error: %s lacks the file name.\n", argv[i]); + exit(1); + } + + if (attachment->mime_type == NULL) { + mxprint(stderr, "Error: No MIME type was set for the attachment '%s'." + "\n", argv[i + 1]); + exit(1); + } + + attachment->name = safestrdup(argv[i + 1]); + if (!strcmp(argv[i], "--attach-file")) + attachment->to_all_files = true; + try { + io = new mm_io_c(attachment->name, MODE_READ); + io->setFilePointer(0, seek_end); + attachment->size = io->getFilePointer(); + delete io; + if (attachment->size == 0) + throw exception(); + } catch (...) { + mxprint(stderr, "Error: Could not open the attachment '%s', or its " + "size is 0.\n", attachment->name); + exit(1); + } + + attachments.push_back(attachment); + attachment = (attachment_t *)safemalloc(sizeof(attachment_t)); + memset(attachment, 0, sizeof(attachment_t)); + + i++; + } + // Options that apply to the next input file only. else if (!strcmp(argv[i], "-A") || !strcmp(argv[i], "--noaudio")) ti.no_audio = true; @@ -1213,6 +1364,12 @@ static void parse_args(int argc, char **argv) { mxprint(stderr, "Warning: '--dont-link' is only useful in combination " "with '--split'.\n"); } + + for (i = 0; i < attachments.size(); i++) { + attachment_sizes_first += attachments[i]->size; + if (attachments[i]->to_all_files) + attachment_sizes_others += attachments[i]->size; + } } static char **add_string(int &num, char **values, char *new_string) { @@ -1442,6 +1599,7 @@ void create_next_output_file(bool last_file, bool first_file) { cluster_helper->set_output(out); render_headers(out, last_file, first_file); + render_attachments(out); return; } @@ -1464,6 +1622,7 @@ void create_next_output_file(bool last_file, bool first_file) { cluster_helper->set_output(out); render_headers(out, last_file, first_file); + render_attachments(out); file_num++; }