Index: libhb/muxmp4.c =================================================================== --- libhb/muxmp4.c (revision 1364) +++ libhb/muxmp4.c (working copy) @@ -9,6 +9,8 @@ #include "hb.h" +#include "srt.h" + void AddIPodUUID(MP4FileHandle, MP4TrackId); struct hb_mux_object_s @@ -27,6 +29,9 @@ MP4TrackId chapter_track; int current_chapter; uint64_t chapter_duration; + + /* Subtitle stuff for muxing */ + MP4TrackId subtitle_track; }; struct hb_mux_data_s @@ -119,7 +124,114 @@ } +void MP4SetTrackYTranslation(const MP4FileHandle file, + const MP4TrackId track, + const u_int16_t translation) +{ + uint8_t* val; + uint8_t nval[38]; + uint32_t *ptr32 = (uint32_t*) (nval + 2); + uint32_t size; + MP4GetTrackBytesProperty(file, track, "tkhd.reserved3", &val, &size); + + memcpy(nval, val, size); + + const uint32_t ytranslation = translation * 0x10000; + +#ifdef WORDS_BIGENDIAN + ptr32[7] = ytranslation; +#else + /* we need to switch the endianness, as the file format expects big endian */ + ptr32[7] = ((ytranslation & 0x000000FF) << 24) + ((ytranslation & 0x0000FF00) << 8) + ((ytranslation & 0x00FF0000) >> 8) + ((ytranslation & 0xFF000000) >> 24); +#endif + + MP4SetTrackBytesProperty(file, track, "tkhd.reserved3", nval, size); +} + /********************************************************************** + * MP4CreateSubtitleTrack + ********************************************************************** + * Creates a new subtitle track + *********************************************************************/ +MP4TrackId MP4CreateSubtitleTrack(const MP4FileHandle file, + const MP4TrackId refTrack, + const uint16_t height, + const uint16_t width, + const uint16_t translation, + const uint16_t language_code) +{ + const MP4TrackId subtitleTrack = MP4AddSubtitleTrack(file, refTrack); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.mdhd.language", language_code); + MP4SetTrackFloatProperty(file, subtitleTrack, "tkhd.width", width); + MP4SetTrackFloatProperty(file, subtitleTrack, "tkhd.height", height); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "tkhd.alternate_group", 2); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.defTextBoxTop", 0); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.defTextBoxLeft", 0); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.defTextBoxBottom", 50); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.defTextBoxRight", 0); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontID", 1); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontSize", 12); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.horizontalJustification", 1); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.verticalJustification", 255); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontColorRed", 255); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontColorGreen", 255); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontColorBlue", 255); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontColorAlpha", 255); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.bgColorRed", 0); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.bgColorGreen", 0); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.bgColorBlue", 0); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.bgColorAlpha", 255); + + MP4SetTrackYTranslation(file, subtitleTrack, translation); + + MP4SetTrackTimeScale(file, subtitleTrack, 1000); // Set to ms + + return subtitleTrack; +} + +void MP4WriteSubtitleSample(const MP4FileHandle file, + const MP4TrackId track, + const char* string, + const long duration, + const long offset) +{ + if (duration <= 0) + return; + + if (offset) { + // Write the offset... + u_int8_t empty[2] = {0,0}; + MP4WriteSample(file, + track, + empty, + 2, + duration, + 0, true); + } + + // .. and write the actual sample + const size_t stringLength = strlen(string); + u_int8_t buffer[1024]; + memcpy(buffer+2, string, strlen(string)); // strlen > 1024 -> booom + buffer[0] = (stringLength >> 8) & 0xff; + buffer[1] = stringLength & 0xff; + + MP4WriteSample(file, + track, + buffer, + stringLength + 2, + duration, + 0, true); +} + +/********************************************************************** * MP4Init ********************************************************************** * Allocates hb_mux_data_t structures, create file and write headers @@ -335,7 +447,26 @@ m->chapter_duration = 0; m->current_chapter = job->chapter_start; } - + + const uint16_t subtitleHeight = 60; + m->subtitle_track = MP4CreateSubtitleTrack(m->file, + mux_data->track, + subtitleHeight, + m->job->width, + m->job->height - subtitleHeight, + language_code); + + + char filename[] = "/Users/ben/Developer/SrtParser/test.srt"; + struct srt_entry_s* subtitles = srt_readfile(filename); + while (subtitles) { + MP4WriteSubtitleSample(m->file, m->subtitle_track, subtitles->text, subtitles->offset, subtitles->duration); + subtitles = subtitles->next; + } + + srt_free_entries(subtitles); + + /* Add encoded-by metadata listing version and build date */ char *tool_string; tool_string = (char *)malloc(80); @@ -387,7 +518,8 @@ hb_error("Failed to write to output file, disk full?"); *job->die = 1; } - free(sample); + + free(sample); m->current_chapter++; m->chapter_duration += duration; } @@ -465,9 +597,10 @@ hb_error("Failed to write to output file, disk full?"); *job->die = 1; } - free(sample); + + free(sample); } - + if (job->areBframes) { // Insert track edit to get A/V back in sync. The edit amount is @@ -507,4 +640,3 @@ m->job = job; return m; } - Index: libhb/srt.c =================================================================== --- libhb/srt.c (revision 0) +++ libhb/srt.c (revision 0) @@ -0,0 +1,134 @@ +#include "srt.h" +#include +#include +#include + +struct start_and_end { + unsigned long start, end; +}; + +static struct start_and_end read_time_from_string(const char* timeString) { + // for ex. 00:00:15,248 --> 00:00:16,545 + + long houres1, minutes1, seconds1, milliseconds1, + houres2, minutes2, seconds2, milliseconds2; + + sscanf(timeString, "%d:%d:%d,%d --> %d:%d:%d,%d\n", &houres1, &minutes1, &seconds1, &milliseconds1, + &houres2, &minutes2, &seconds2, &milliseconds2); + + struct start_and_end result = {milliseconds1 + seconds1*1000 + minutes1*60000 + houres1*36000, + milliseconds2 + seconds2*1000 + minutes2*60000 + houres2*36000}; + return result; +} + +enum +{ + k_state_inEntry, + k_state_potential_new_entry, + k_state_timecode, +}; + +struct srt_entry_s* srt_readfile(const char* filename) +{ + FILE *file = fopen ( filename, "r" ); + + if (file == NULL) + return NULL; + + char line_buffer[1024]; + unsigned long current_time = 0, + number_of_entries = 0, + current_state = k_state_potential_new_entry; + + struct srt_entry_s *first_entry = malloc(sizeof(struct srt_entry_s)), + *current_entry = first_entry; + memset(first_entry, 0, sizeof(struct srt_entry_s)); + + while (fgets(line_buffer,sizeof(line_buffer),file)) { + switch (current_state) + { + case k_state_timecode: + { + struct start_and_end timing = read_time_from_string(line_buffer); + + current_entry->duration = timing.end - timing.start; + current_entry->offset = timing.start - current_time; + + current_time = timing.end; + + current_state = k_state_inEntry; + continue; + } + + case k_state_inEntry: + { + // If the current line is empty, we assume this is the + // seperation betwene two entries. In case we are wrong, + // the mistake is corrected in the next state. + if (strcmp(line_buffer, "\n") == 0 || strcmp(line_buffer, "\r\n") == 0) { + current_state = k_state_potential_new_entry; + continue; + } + + // Append the current line to the entry's text buffer; + strncat(current_entry->text, line_buffer, 1024); + + break; + } + + case k_state_potential_new_entry: + { + char endpoint[] = "\0"; + const unsigned long potential_entry_number = strtol(line_buffer, (char**)&endpoint, 10); + + // Is this really new next entry begin? + if (potential_entry_number == number_of_entries + 1) { + // We actuall found the next entry - or a really rare error condition + + // The first entry was preallocated - all others are alloced here + // plus we overwrite zero-duration entries + if (number_of_entries != 0) { + current_entry->next = malloc(sizeof(struct srt_entry_s)); + current_entry = current_entry->next; + } + + memset(current_entry, 0, sizeof(struct srt_entry_s)); + + ++number_of_entries; + current_state = k_state_timecode; + continue; + } else { + // Well.. looks like we are in the wrong mode.. lets add the + // newline we misinterpreted... + strncat(current_entry->text, "\n", 1024); + current_state = k_state_inEntry; + } + + break; + } + } + } + + fclose ( file ); + + // Since the number of entries is incremented when we find the next entry, + // we missed the lastone + if (strlen(current_entry->text)) { + ++number_of_entries; + } + + // No entries found - free the preallocaed firstone + if (number_of_entries == 0) { + free(first_entry); + return NULL; + } + + return first_entry; +} + +void srt_free_entries(struct srt_entry_s* entries) +{ + if (entries && entries->next) + srt_free_entries(entries->next); + free(entries); +} \ No newline at end of file Index: libhb/srt.h =================================================================== --- libhb/srt.h (revision 0) +++ libhb/srt.h (revision 0) @@ -0,0 +1,11 @@ +#pragma once + +struct srt_entry_s { + long offset, duration; + char text[1024]; + + struct srt_entry_s* next; +}; + +struct srt_entry_s* srt_readfile(const char* filename); +void srt_free_entries(struct srt_entry_s* entries); \ No newline at end of file