From df93674bf7e11d7f4e780f07ccbfda7be9afeed1 Mon Sep 17 00:00:00 2001 From: Matt Mascarenhas Date: Sat, 25 Mar 2023 00:04:34 +0000 Subject: [PATCH] cinera: Increase precision to milliseconds This commit simply adds millisecond precision of timecodes. It includes small changes to hmmlib.h, cinera.c and the frontend JS files. --- cinera/cinera.c | 55 ++++++++++++++++++++----------------- cinera/cinera_player_pre.js | 14 +++++----- cinera/cinera_search_pre.js | 8 +++--- hmmlib2/hmmlib.h | 34 ++++++++++++++++++++--- 4 files changed, 71 insertions(+), 40 deletions(-) diff --git a/cinera/cinera.c b/cinera/cinera.c index 27a49e6..a4dbd6a 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -23,7 +23,7 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, .Minor = 10, - .Patch = 26 + .Patch = 27 }; #define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW @@ -210,7 +210,12 @@ typedef struct int C; int Seconds; }; -} v3; + union + { + int D; + int Milliseconds; + }; +} v4; void Clear(void *V, uint64_t Size) @@ -2167,7 +2172,7 @@ IndexingError(string Filename, uint64_t LineNumber, severity Severity, char *Mes } void -PrintTimecode(FILE *Dest, v3 Timecode) +PrintTimecode(FILE *Dest, v4 Timecode) { Colourise(CS_BLUE_BOLD); if(Timecode.Hours) @@ -2182,7 +2187,7 @@ PrintTimecode(FILE *Dest, v3 Timecode) } void -IndexingChronologyError(string *Filename, uint64_t LineNumber, v3 ThisTimecode, v3 PrevTimecode) +IndexingChronologyError(string *Filename, uint64_t LineNumber, v4 ThisTimecode, v4 PrevTimecode) { severity Severity = S_ERROR; ErrorFilenameAndLineNumber(Filename, LineNumber, Severity, ED_INDEXING); @@ -4304,7 +4309,7 @@ typedef struct typedef struct { - v3 Timecode; + v4 Timecode; int Identifier; } identifier; @@ -4430,7 +4435,7 @@ CopyStringToBuffer_(int LineNumber, buffer *Dest, char *Format, ...) } int -DigitsInTimecode(v3 Timecode) +DigitsInTimecode(v4 Timecode) { int Result = 0; int ColonChar = 1; @@ -4453,7 +4458,7 @@ DigitsInTimecode(v3 Timecode) #define CopyTimecodeToBuffer(Dest, Timecode) CopyTimecodeToBuffer_(__LINE__, Dest, Timecode) void -CopyTimecodeToBuffer_(int LineNumber, buffer *Dest, v3 Timecode) +CopyTimecodeToBuffer_(int LineNumber, buffer *Dest, v4 Timecode) { if(DigitsInTimecode(Timecode) + (Dest->Ptr - Dest->Location) >= Dest->Size) { @@ -5463,17 +5468,17 @@ InitTemplate(template *Template, string Location, template_type Type) Template->Metadata.NavBuffer = InitBook(sizeof(navigation_buffer), 4); } -v3 -V3(int A, int B, int C) +v4 +V4(int A, int B, int C, int D) { - v3 Result = { .A = A, .B = B, .C = C }; + v4 Result = { .A = A, .B = B, .C = C, .D = D }; return Result; } -int -TimecodeToSeconds(v3 Timecode) +float +TimecodeToDottedSeconds(v4 Timecode) { - return Timecode.Hours * SECONDS_PER_HOUR + Timecode.Minutes * SECONDS_PER_MINUTE + Timecode.Seconds; + return (float)Timecode.Hours * SECONDS_PER_HOUR + (float)Timecode.Minutes * SECONDS_PER_MINUTE + (float)Timecode.Seconds + (float)Timecode.Milliseconds / 1000; } typedef struct @@ -10816,9 +10821,9 @@ HMMLOutputLocationIs(neighbourhood *N, char *OutputLocation) } bool -TimecodeIs(v3 Timecode, int Hours, int Minutes, int Seconds) +TimecodeIs(v4 Timecode, int Hours, int Minutes, int Seconds, int Milliseconds) { - return Timecode.Hours == Hours && Timecode.Minutes == Minutes && Timecode.Seconds == Seconds; + return Timecode.Hours == Hours && Timecode.Minutes == Minutes && Timecode.Seconds == Seconds && Timecode.Milliseconds == Milliseconds; } rc @@ -10829,14 +10834,14 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m bool *HasQuoteMenu, bool *HasReferenceMenu, bool *HasFilterMenu, bool *RequiresCineraJS, int *QuoteIdentifier, int *RefIdentifier, _memory_book(category_info) *Topics, _memory_book(category_info) *Media, - HMML_Timestamp *Timestamp, v3 *PreviousTimecode) + HMML_Timestamp *Timestamp, v4 *PreviousTimecode) { MEM_TEST_TOP(); // TODO(matt): Introduce and use a SystemError() in here rc Result = RC_SUCCESS; - v3 Timecode = V3(Timestamp->h, Timestamp->m, Timestamp->s); - if(TimecodeToSeconds(Timecode) >= TimecodeToSeconds(*PreviousTimecode)) + v4 Timecode = V4(Timestamp->h, Timestamp->m, Timestamp->s, Timestamp->ms); + if(TimecodeToDottedSeconds(Timecode) >= TimecodeToDottedSeconds(*PreviousTimecode)) { *PreviousTimecode = Timecode; @@ -10857,8 +10862,8 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m CopyStringToBuffer(&IndexBuffers->Header, - "
Class, " class=\"marker"); @@ -11115,10 +11120,10 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m " \n" " \n" "
\n" - " [&#%d;]", + " [&#%d;]", (int)QuoteUsername.Length, QuoteUsername.Base, (int)DateString.Length, DateString.Base, // TODO(matt): Convert Unixtime to date-string - TimecodeToSeconds(Timecode), + TimecodeToDottedSeconds(Timecode), *QuoteIdentifier); CopyTimecodeToBuffer(&MenuBuffers->Quote, Timecode); CopyStringToBuffer(&MenuBuffers->Quote, "\n" @@ -11142,7 +11147,7 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m if(Result == RC_SUCCESS) { - CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"%d\": \"", TimecodeToSeconds(Timecode)); + CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"%.3f\": \"", TimecodeToDottedSeconds(Timecode)); if(Timestamp->quote.present && !Timestamp->text[0]) { CopyStringToBuffer(&CollationBuffers->SearchEntry, "\u201C"); @@ -11831,7 +11836,7 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF Print(stdout, "\n\n --- Entering Timestamps Loop ---\n\n\n\n"); #endif - v3 PreviousTimecode = {}; + v4 PreviousTimecode = {}; for(int TimestampIndex = 0; TimestampIndex < HMML.timestamp_count; ++TimestampIndex) { // TODO(matt): Thoroughly test this reorganisation @@ -11918,7 +11923,7 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF { identifier *ThisIdentifier = GetPlaceInBook(&This->Identifier, j); CopyStringToBuffer(&MenuBuffers.Reference, - "[%d]", TimecodeToSeconds(ThisIdentifier->Timecode), ThisIdentifier->Identifier); + "[%d]", TimecodeToDottedSeconds(ThisIdentifier->Timecode), ThisIdentifier->Identifier); CopyTimecodeToBuffer(&MenuBuffers.Reference, ThisIdentifier->Timecode); CopyStringToBuffer(&MenuBuffers.Reference, ""); } diff --git a/cinera/cinera_player_pre.js b/cinera/cinera_player_pre.js index f781269..1604797 100644 --- a/cinera/cinera_player_pre.js +++ b/cinera/cinera_player_pre.js @@ -163,9 +163,9 @@ function Player(cineraElement, refsCallback) { for (var i = 0; i < markerEls.length; ++i) { var markerEl = markerEls[i]; var marker = { - timestamp: parseInt(markerEl.getAttribute("data-timestamp"), 10), + timestamp: parseFloat(markerEl.getAttribute("data-timestamp")), ref: markerEl.getAttribute("data-ref"), - endTime: (i < markerEls.length - 1 ? parseInt(markerEls[i+1].getAttribute("data-timestamp"), 10) : null), + endTime: (i < markerEls.length - 1 ? parseFloat(markerEls[i+1].getAttribute("data-timestamp")) : null), el: markerEl, fadedProgress: markerEl.querySelector(".progress.faded"), progress: markerEl.querySelector(".progress.main"), @@ -2015,12 +2015,12 @@ Player.prototype.handleKey = function(key) { case menu_id.MARKERS: { var time = this.MenusFocused.Item.getAttribute("data-timestamp"); - this.setTimeThenPlay(parseInt(time)); + this.setTimeThenPlay(parseFloat(time)); } break; case menu_id.QUOTES: { var time = this.MenusFocused.Item.querySelector(".timecode").getAttribute("data-timestamp"); - this.setTimeThenPlay(parseInt(time)); + this.setTimeThenPlay(parseFloat(time)); if(this.currentMarker) { this.setScroller(this.Menus[menu_id.MARKERS], this.currentMarker.el, true, false); @@ -2030,7 +2030,7 @@ Player.prototype.handleKey = function(key) { case menu_id.REFERENCES: { var time = this.MenusFocused.Identifier.getAttribute("data-timestamp"); - this.setTimeThenPlay(parseInt(time)); + this.setTimeThenPlay(parseFloat(time)); if(this.currentMarker) { this.setScroller(this.Menus[menu_id.MARKERS], this.currentMarker.el, true, false); @@ -2052,7 +2052,7 @@ Player.prototype.handleKey = function(key) { if(this.currentMarker && this.currentMarker.el) { var time = this.currentMarker.el.getAttribute("data-timestamp"); - this.setTimeThenPlay(parseInt(time)); + this.setTimeThenPlay(parseFloat(time)); this.setScroller(this.Menus[menu_id.MARKERS], this.currentMarker.el, true, false); } } @@ -2912,7 +2912,7 @@ Player.prototype.mouseOverCredits = function(item) { function mouseSkipToTimecode(player, time, ev) { - player.setTimeThenPlay(parseInt(time, 10)); + player.setTimeThenPlay(parseFloat(time)); ev.preventDefault(); ev.stopPropagation(); return false; diff --git a/cinera/cinera_search_pre.js b/cinera/cinera_search_pre.js index 0d0073b..7c90074 100644 --- a/cinera/cinera_search_pre.js +++ b/cinera/cinera_search_pre.js @@ -213,11 +213,11 @@ function prepareToParseIndexFile(project) mode = "markers"; episode.markers = []; } else if (mode == "markers") { - var match = line.match(/"(\d+)": "(.+)"/); + var match = line.match(/"(\d+.\d+)": "(.+)"/); if (match == null) { console.log(name, line); } else { - var totalTime = parseInt(line.slice(1)); + var totalTime = parseFloat(line.slice(1)); var marker = { totalTime: totalTime, prettyTime: markerTime(totalTime), @@ -292,7 +292,7 @@ function markerTime(totalTime) { var markTime = "("; var hours = Math.floor(totalTime / 60 / 60); var minutes = Math.floor(totalTime / 60) % 60; - var seconds = totalTime % 60; + var seconds = Math.floor(totalTime) % 60; if (hours > 0) { markTime += padTimeComponent(hours) + ":"; } @@ -460,7 +460,7 @@ function runSearch(refresh) { Search.ResultsSummary.style.display = "none"; } - var totalTime = Math.floor(totalSeconds/60/60) + "h " + Math.floor(totalSeconds/60)%60 + "m " + totalSeconds%60 + "s "; + var totalTime = Math.floor(totalSeconds/60/60) + "h " + Math.floor(totalSeconds/60)%60 + "m " + Math.floor(totalSeconds)%60 + "s "; Search.ResultsSummary.textContent = "Found: " + numEpisodes + " episodes, " + numMarkers + " markers, " + totalTime + "total."; } diff --git a/hmmlib2/hmmlib.h b/hmmlib2/hmmlib.h index 6591f1c..b74f63f 100644 --- a/hmmlib2/hmmlib.h +++ b/hmmlib2/hmmlib.h @@ -76,7 +76,7 @@ typedef struct { typedef struct { int line; - int h, m, s; + int h, m, s, ms; char* text; char* author; @@ -471,7 +471,7 @@ next_attr: static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts) { - unsigned int h = 0, m = 0, s = 0; + unsigned int h = 0, m = 0, s = 0, ms = 0; int offset = 0; int count = sscanf(p->cursor, "[%u:%u%n", &m, &s, &offset); @@ -485,7 +485,7 @@ static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts) if(c == ':') { unsigned int tmp; offset = 0; - if(sscanf(p->cursor, ":%u]%n", &tmp, &offset) != 1 || offset == 0) { + if(sscanf(p->cursor, ":%u%n", &tmp, &offset) != 1 || offset == 0) { _hmml_err(p, "Unable to parse 3-part timecode"); } @@ -494,6 +494,27 @@ static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts) s = tmp; p->cursor += offset; + c = *p->cursor; + } + + if(c == '.') { + unsigned int tmp; + offset = 0; + + int non_number_chars = 2; + int digits_in_100 = 3; + int max_chars_to_parse = non_number_chars + digits_in_100; + + if(sscanf(p->cursor, ".%u]%n", &tmp, &offset) != 1 || offset == 0 || offset > max_chars_to_parse) { + _hmml_err(p, "Unable to parse %u.5-part timecode", h ? 3 : 2); + } + + for(int i = offset - non_number_chars; i < digits_in_100; ++i) { + tmp *= 10; + } + ms = tmp; + + p->cursor += offset; } else if(c != ']') { _hmml_err(p, "Unable to parse timecode"); @@ -501,6 +522,10 @@ static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts) ++p->cursor; } + if(ms >= 1000) { + _hmml_err(p, "Milliseconds cannot exceed 999"); + } + if(s >= 60) { _hmml_err(p, "Seconds cannot exceed 59"); } @@ -512,6 +537,7 @@ static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts) ts->h = h; ts->m = m; ts->s = s; + ts->ms = ms; } static void _hmml_store_marker(struct _hmml_parser* p, HMML_Timestamp* ts, char** out, char* text_mem, size_t text_mem_size) @@ -824,7 +850,7 @@ void hmml_free(HMML_Output* out) } const struct HMML_Version hmml_version = { - 2, 0, 14 + 2, 0, 15 }; #undef HSTX