From bfbf39edaa05b9ffea98425e54baf6c2070cfe67 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 17 Nov 2025 13:45:02 -0500 Subject: [PATCH] update language choice processing --- lib/airplay_video.c | 188 ++++++++++++++++++++++++++------------------ 1 file changed, 112 insertions(+), 76 deletions(-) diff --git a/lib/airplay_video.c b/lib/airplay_video.c index 28543a9..c532260 100644 --- a/lib/airplay_video.c +++ b/lib/airplay_video.c @@ -105,7 +105,6 @@ airplay_video_t *airplay_video_init(raop_t *raop, unsigned short http_port, void airplay_video_destroy(airplay_video_t *airplay_video) { - if (airplay_video->uri_prefix) { free(airplay_video->uri_prefix); } @@ -118,10 +117,9 @@ airplay_video_destroy(airplay_video_t *airplay_video) if (airplay_video->media_data_store) { destroy_media_data_store(airplay_video); } - if (airplay_video->master_playlist) { + if (airplay_video->master_playlist){ free (airplay_video->master_playlist); } - free (airplay_video); } @@ -178,7 +176,7 @@ const char *get_language_name(airplay_video_t *airplay_video) { return (const char *)airplay_video->language_name; } -void set_language_code(airplay_video_t *airplay_video, char *language_code) { +void set_language_code(airplay_video_t *airplay_video, char *language_code) {; if (airplay_video->language_code) { free (airplay_video->language_code); } @@ -213,16 +211,17 @@ void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist } typedef struct language_s { - char *start; + const char *start; int len; - char type; + bool is_default; char code[6]; + char *name; } language_t; -language_t* master_playlist_process_language(char * data, int *slices, int *language_count) { +language_t* master_playlist_process_language(const char * data, int *slices, int *language_count) { *language_count = 0; - char *ptr = data; - int count = 0, count1 = 0, count2 = 0, count3 = 0; + const char *ptr = data; + int count = 0, count1 = 0; while (ptr) { ptr = strstr(ptr,"#EXT-X-MEDIA:URI="); if(!ptr) { @@ -242,76 +241,80 @@ language_t* master_playlist_process_language(char * data, int *slices, int *lang return NULL; } language_t *languages = (language_t *) calloc(count + 2, sizeof(language_t)); - languages[0].start = data; + size_t length = 0; ptr = data; for (int i = 1; i <= count; i++) { char *end; + int len_name; if (!(ptr = strstr(ptr, "#EXT-X-MEDIA"))) { break; } - count1++; if (i == 1) { - languages[0].len = (int) (ptr - data); - languages[0].type = ' '; + length = (int) (ptr - data); + languages[0].start = data; + languages[0].len = length; + *languages[0].code = '\0'; + languages[0].name = NULL; } languages[i].start = ptr; - if (!(ptr = strstr(ptr, "LANGUAGE="))) { + + if (!(ptr = strstr(ptr, "DEFAULT="))) { break; } - if (!strncmp(ptr - strlen("dubbed-auto") - 2, "dubbed-auto", strlen("dubbed-auto"))) { - languages[i].type = 'd'; - } else if (!strncmp(ptr - strlen("dubbed") - 2, "dubbed", strlen("dubbed"))) { - languages[i].type = 'd'; - } else if (!strncmp(ptr - strlen("original") - 2, "original", strlen("original"))) { - languages[i].type = 'o'; - } else { - languages[i].type = 'u'; + ptr += strlen("DEFAULT="); + languages[i].is_default = !strncmp(ptr, "YES", strlen("YES")); + if (!(ptr = strstr(ptr, "NAME="))) { + break; + } + ptr += strlen("NAME="); + end = strchr(++ptr,'"'); + if (!end) { + break; + } + len_name = end - ptr; + languages[i].name = (char *) calloc(len_name + 1, sizeof(char)); + memcpy(languages[i].name, ptr, len_name *sizeof(char)); + if (!(ptr = strstr(ptr, "LANGUAGE="))) { + break; } - count2++; if (!(ptr = strchr(ptr,'"'))) { break; } - ptr++; - if (!(end = strchr(ptr,'"'))) { + if (!(end = strchr(++ptr,'"'))) { break; - } + } strncpy(languages[i].code, ptr, end - ptr); if (!(ptr = strchr(ptr,'\n'))) { break; } - count3++; + count1++; languages[i].len = (int) (ptr + 1 - languages[i].start); + length += languages[i].len; } - assert (count1 == count && count2 == count && count3 == count); + assert (count1 == count); + languages[count + 1].start = ++ptr; languages[count + 1].len = strlen(ptr); - languages[count + 1].type = ' '; + *languages[count + 1].code = '\0'; + languages[count + 1].name = NULL; + + length += languages[count + 1].len; + assert(length == strlen(data)); *slices = count + 2; - int len = 0; + int copies = 0; - for (int i = 0; i < *slices; i++) { + for (int i = 1; i < count; i++) { if (!strcmp(languages[i].code, languages[1].code)) { copies++; } - len += languages[i].len; } - if (copies == count) { - /* only one language is offered, nothing to do */ - free (languages); - return NULL; - } *language_count = count/copies; assert(count == *language_count * copies); - assert(len == (int) strlen(data)); + /* verify expected structure of language choice information */ for (int i = 1; i <= count; i++) { - if (i % *language_count) { - assert(languages[i].type != 'o'); - } else { - assert(languages[i].type == 'o'); - } - int j = i - *language_count; + int j = i - *language_count; if (j > 0) { assert (!strcmp(languages[i].code, languages[j].code)); } @@ -322,38 +325,49 @@ language_t* master_playlist_process_language(char * data, int *slices, int *lang char * select_master_playlist_language(airplay_video_t *airplay_video, char *master_playlist) { int language_count, slices; language_t *languages; + assert(master_playlist); if (!(languages = master_playlist_process_language(master_playlist, &slices, &language_count))) { return master_playlist; } - /* audio is offered in multiple languages */ - char *str = calloc(6 * language_count, sizeof(char)); - int i; - char *ptr = str; - for (i = 0; i < language_count; i++) { - sprintf(ptr,"%s ", languages[i + 1].code); - ptr += strlen(languages[i + 1].code); - ptr++; - if ( i % 16 == 15) { - sprintf(ptr++,"\n"); - } - } - if (i % 16 != 15) { - sprintf(ptr++,"\n"); - } - printf("%d available languages: %s", language_count, str); - free(str); - const char *ptrc = airplay_video->lang; - char *lang = NULL; - while (ptrc) { + /* audio is offered in multiple languages */ + + char *code = NULL; + char *name = NULL; + + assert(airplay_video); + printf("%d available languages:\n\n", language_count); + int i_default = -1; + + const char *language_name = get_language_name(airplay_video); + for (int i = 1; i <= language_count; i ++) { + if (language_name) { + if (!strcmp(language_name, languages[i].name)) { + i_default = i; + } + } else if (languages[i].is_default) { + i_default = i; + } + printf("%2d %-5.5s \"%s\" %s\n",i, languages[i].code, languages[i].name, (languages[i].is_default ? "(DEFAULT)" : "")); + } + printf("\n"); + assert(i_default >= 0); + + const char *ptrc = airplay_video->lang;; + code = NULL; + name = NULL; + while (ptrc){ for (int i = 1; i <= language_count; i++) { if (!strncmp(languages[i].code, ptrc, 2)) { - lang = languages[i].code; + code = languages[i].code; + name = languages[i].name; + printf("language choice: %s \"%s\" (based on prefered languages list %s)\n\n", + code, name, airplay_video->lang); break; } } - if (lang) { + if (code) { break; } ptrc = strchr(ptrc,':'); @@ -364,32 +378,54 @@ char * select_master_playlist_language(airplay_video_t *airplay_video, char *mas } } } - if (lang) { - printf("language choice: %s (based on prefered languages list %s)\n\n", - lang, airplay_video->lang); - } else { + + if (!code) { + code = languages[i_default].code; + name = languages[i_default].name; if (airplay_video->lang) { printf("no match with prefered language list %s\n", airplay_video->lang); } - lang = languages[language_count].code; - printf("default language choice: %s\n\n", lang); + if (language_name) { + printf("using HLS-specified language choice: %s \"%s\"\n\n", code, name); + } else { + printf("using default language choice: %s \"%s\"\n\n", code, name); + } + } + + /* update stored language code, name if changed */ + if (name != language_name) { /* compare addresses */ + int len = strlen(name); + char *new_language_name = (char *) calloc(len + 1, sizeof(char)); + memcpy(new_language_name, name, len); + set_language_name(airplay_video, new_language_name); + len = strlen(code); + char *new_language_code = (char *) calloc(len + 1, sizeof(char)); + memcpy(new_language_code, code, len); + set_language_code(airplay_video, new_language_code); } + int len = 0; for (int i = 0; i < slices; i++) { - if (strlen(languages[i].code) == 0 || !strcmp(languages[i].code, lang)) { + if (strlen(languages[i].code) == 0 || !strcmp(languages[i].code, code)) { len += languages[i].len; } } char *new_master_playlist = (char *) calloc(len + 1, sizeof(char)); - ptr = new_master_playlist; + + char *ptr = new_master_playlist; for (int i = 0; i < slices; i++) { - if (strlen(languages[i].code) == 0 || !strcmp(languages[i].code, lang)) { + if (strlen(languages[i].code) == 0 || !strcmp(languages[i].code, code)) { strncpy(ptr, languages[i].start, languages[i].len); ptr += languages[i].len; } } + + for (int i = 1; i <= slices - 2 ; i++) { + free (languages[i].name); + } free (languages); - free(master_playlist); + free (master_playlist); + return new_master_playlist; }