mirror of
https://github.com/morgan9e/UxPlay
synced 2026-04-15 00:34:05 +09:00
add -md <file> option to output audio-mode metadata text to file
This commit is contained in:
19
README.html
19
README.html
@@ -57,9 +57,9 @@ alt="Current Packaging status" /></a>.</p>
|
||||
<li><p>Install uxplay on Debian-based Linux systems with
|
||||
“<code>sudo apt install uxplay</code>”; on FreeBSD with
|
||||
“<code>sudo pkg install uxplay</code>”; on OpenBSD with
|
||||
“<code>doas pkg_add uxplay</code>”. Also available on Arch-based
|
||||
systems through AUR. Since v. 1.66, uxplay is now also packaged in RPM
|
||||
format by Fedora 38 (“<code>sudo dnf install uxplay</code>”).</p></li>
|
||||
“<code>doas pkg_add uxplay</code>”. Also available on Arch-based systems
|
||||
through AUR. Since v. 1.66, uxplay is now also packaged in RPM format by
|
||||
Fedora 38 (“<code>sudo dnf install uxplay</code>”).</p></li>
|
||||
<li><p>For other RPM-based distributions which have not yet packaged
|
||||
UxPlay, a RPM “specfile” <strong>uxplay.spec</strong> is now provided
|
||||
with recent <a
|
||||
@@ -467,8 +467,8 @@ gstreamer1-plugins, gstreamer1-plugins-* (* = core, good, bad, x, gtk,
|
||||
gl, vulkan, pulse, v4l2, …), (+ gstreamer1-vaapi for Intel/AMD
|
||||
graphics).</p></li>
|
||||
<li><p><strong>OpenBSD:</strong> Install gstreamer1-libav,
|
||||
gstreamer1-plugins-* (* = core, bad, base, good). avahi-main must also
|
||||
be installed for the avahi_daemon rc startup script.</p></li>
|
||||
gstreamer-plugins-* (* = core, bad, base, good). avahi-main must also be
|
||||
installed for the avahi_daemon rc startup script.</p></li>
|
||||
</ul>
|
||||
<h3 id="starting-and-running-uxplay">Starting and running UxPlay</h3>
|
||||
<p>Since UxPlay-1.64, UxPlay can be started with options read from a
|
||||
@@ -1174,6 +1174,11 @@ then run the the image viewer in the foreground. Example, using
|
||||
in which uxplay was put into the background). To quit, use
|
||||
<code>ctrl-C fg ctrl-C</code> to terminate the image viewer, bring
|
||||
<code>uxplay</code> into the foreground, and terminate it too.</p>
|
||||
<p><strong>-md <em>filename</em></strong> Like the -ca option, but
|
||||
exports audio metadata text (Artist, Title, Genre, etc.) to file for
|
||||
possible display by a process that watches the file for changes.
|
||||
Previous text is overwritten as new metadata is received, and the file
|
||||
is deleted when uxplay terminates.</p>
|
||||
<p><strong>-reset n</strong> sets a limit of <em>n</em> consecutive
|
||||
failures of the client to send feedback requests (these “heartbeat
|
||||
signals” are sent by the client once per second to ask for a response
|
||||
@@ -1620,7 +1625,9 @@ introduced 2017, running tvOS 12.2.1), so it does not seem to matter
|
||||
what version UxPlay claims to be.</p>
|
||||
<h1 id="changelog">Changelog</h1>
|
||||
<p>1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially
|
||||
only for YouTube movies. Fix issue with NTP timeout on Windows.</p>
|
||||
only for YouTube movies. Fix issue with NTP timeout on Windows. Add
|
||||
requested option -md <filename> to output audio metadata text to a
|
||||
file for possible display (complements -ca option).</p>
|
||||
<p>1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x
|
||||
2160). Fix issue with GStreamer >= 1.24 when client sleeps, then
|
||||
wakes.</p>
|
||||
|
||||
@@ -1195,6 +1195,11 @@ uxplay was put into the background). To quit, use `ctrl-C fg ctrl-C` to
|
||||
terminate the image viewer, bring `uxplay` into the foreground, and
|
||||
terminate it too.
|
||||
|
||||
**-md *filename*** Like the -ca option, but exports audio metadata text
|
||||
(Artist, Title, Genre, etc.) to file for possible display by a process that watches
|
||||
the file for changes. Previous text is overwritten as new metadata is received,
|
||||
and the file is deleted when uxplay terminates.
|
||||
|
||||
**-reset n** sets a limit of *n* consecutive failures of the
|
||||
client to send feedback requests (these "heartbeat signals" are sent by the client
|
||||
once per second to ask for a response showing that the server is still online).
|
||||
@@ -1666,6 +1671,8 @@ what version UxPlay claims to be.
|
||||
|
||||
1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially
|
||||
only for YouTube movies. Fix issue with NTP timeout on Windows.
|
||||
Add requested option -md \<filename\> to output audio metadata text to a file
|
||||
for possible display (complements -ca option).
|
||||
|
||||
1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x
|
||||
2160). Fix issue with GStreamer \>= 1.24 when client sleeps, then wakes.
|
||||
|
||||
15
README.txt
15
README.txt
@@ -459,9 +459,9 @@ repositories for those distributions.
|
||||
gstreamer1-plugins-\* (\* = core, good, bad, x, gtk, gl, vulkan,
|
||||
pulse, v4l2, ...), (+ gstreamer1-vaapi for Intel/AMD graphics).
|
||||
|
||||
- **OpenBSD:** Install gstreamer1-libav, gstreamer-plugins-\*
|
||||
(\* = core, bad, base, good). avahi-main must also be installed for
|
||||
the avahi_daemon rc startup script.
|
||||
- **OpenBSD:** Install gstreamer1-libav, gstreamer-plugins-\* (\* =
|
||||
core, bad, base, good). avahi-main must also be installed for the
|
||||
avahi_daemon rc startup script.
|
||||
|
||||
### Starting and running UxPlay
|
||||
|
||||
@@ -1196,6 +1196,11 @@ uxplay was put into the background). To quit, use `ctrl-C fg ctrl-C` to
|
||||
terminate the image viewer, bring `uxplay` into the foreground, and
|
||||
terminate it too.
|
||||
|
||||
**-md *filename*** Like the -ca option, but exports audio metadata text
|
||||
(Artist, Title, Genre, etc.) to file for possible display by a process
|
||||
that watches the file for changes. Previous text is overwritten as new
|
||||
metadata is received, and the file is deleted when uxplay terminates.
|
||||
|
||||
**-reset n** sets a limit of *n* consecutive failures of the client to
|
||||
send feedback requests (these "heartbeat signals" are sent by the client
|
||||
once per second to ask for a response showing that the server is still
|
||||
@@ -1665,7 +1670,9 @@ what version UxPlay claims to be.
|
||||
# Changelog
|
||||
|
||||
1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially
|
||||
only for YouTube movies. Fix issue with NTP timeout on Windows.
|
||||
only for YouTube movies. Fix issue with NTP timeout on Windows. Add
|
||||
requested option -md \<filename\> to output audio metadata text to a
|
||||
file for possible display (complements -ca option).
|
||||
|
||||
1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x
|
||||
2160). Fix issue with GStreamer \>= 1.24 when client sleeps, then wakes.
|
||||
|
||||
2
uxplay.1
2
uxplay.1
@@ -108,6 +108,8 @@ UxPlay 1.71: An open\-source AirPlay mirroring (+ audio streaming) server:
|
||||
.TP
|
||||
\fB\-ca\fI fn \fR In Airplay Audio (ALAC) mode, write cover-art to file fn.
|
||||
.TP
|
||||
\fB\-md\fI fn \fR In Airplay Audio (ALAC) mode, write metadata text to file fn.
|
||||
.TP
|
||||
\fB\-reset\fR n Reset after n seconds client silence (default n=15, 0=never).
|
||||
.TP
|
||||
\fB\-nofreeze\fR Do NOT leave frozen screen in place after reset.
|
||||
|
||||
96
uxplay.cpp
96
uxplay.cpp
@@ -125,6 +125,7 @@ static unsigned char audio_type = 0x00;
|
||||
static unsigned char previous_audio_type = 0x00;
|
||||
static bool fullscreen = false;
|
||||
static std::string coverart_filename = "";
|
||||
static std::string metadata_filename = "";
|
||||
static bool do_append_hostname = true;
|
||||
static bool use_random_hw_addr = false;
|
||||
static unsigned short display[5] = {0}, tcp[3] = {0}, udp[3] = {0};
|
||||
@@ -232,6 +233,13 @@ static size_t write_coverart(const char *filename, const void *image, size_t len
|
||||
return count;
|
||||
}
|
||||
|
||||
static size_t write_metadata(const char *filename, const char *text) {
|
||||
FILE *fp = fopen(filename, "wb");
|
||||
size_t count = fwrite(text, sizeof(char), strlen(text) + 1, fp);
|
||||
fclose(fp);
|
||||
return count;
|
||||
}
|
||||
|
||||
static char *create_pin_display(char *pin_str, int margin, int gap) {
|
||||
char *ptr;
|
||||
char num[2] = { 0 };
|
||||
@@ -695,6 +703,7 @@ static void print_info (char *name) {
|
||||
printf("-as 0 (or -a) Turn audio off, streamed video only\n");
|
||||
printf("-al x Audio latency in seconds (default 0.25) reported to client.\n");
|
||||
printf("-ca <fn> In Airplay Audio (ALAC) mode, write cover-art to file <fn>\n");
|
||||
printf("-md <fn> In Airplay Audio (ALAC) mode, write metadata text to file <fn>\n");
|
||||
printf("-reset n Reset after n seconds of client silence (default n=%d, 0=never)\n", MISSED_FEEDBACK_LIMIT);
|
||||
printf("-nofreeze Do NOT leave frozen screen in place after reset\n");
|
||||
printf("-nc Do NOT Close video window when client stops mirroring\n");
|
||||
@@ -1137,6 +1146,19 @@ static void parse_arguments (int argc, char *argv[]) {
|
||||
fprintf(stderr,"option -ca must be followed by a filename for cover-art output\n");
|
||||
exit(1);
|
||||
}
|
||||
} else if (arg == "-md" ) {
|
||||
if (option_has_value(i, argc, arg, argv[i+1])) {
|
||||
metadata_filename.erase();
|
||||
metadata_filename.append(argv[++i]);
|
||||
const char *fn = metadata_filename.c_str();
|
||||
if (!file_has_write_access(fn)) {
|
||||
fprintf(stderr, "%s cannot be written to:\noption \"-ca <fn>\" must be to a file with write access\n", fn);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"option -md must be followed by a filename for metadata text output\n");
|
||||
exit(1);
|
||||
}
|
||||
} else if (arg == "-bt709") {
|
||||
bt709_fix = true;
|
||||
} else if (arg == "-srgb") {
|
||||
@@ -1262,7 +1284,7 @@ static void parse_arguments (int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
static void process_metadata(int count, const char *dmap_tag, const unsigned char* metadata, int datalen) {
|
||||
static void process_metadata(int count, const char *dmap_tag, const unsigned char* metadata, int datalen, std::string *metadata_text) {
|
||||
int dmap_type = 0;
|
||||
/* DMAP metadata items can be strings (dmap_type = 9); other types are byte, short, int, long, date, and list. *
|
||||
* The DMAP item begins with a 4-character (4-letter) "dmap_tag" string that identifies the type. */
|
||||
@@ -1284,13 +1306,13 @@ static void process_metadata(int count, const char *dmap_tag, const unsigned cha
|
||||
case 'a':
|
||||
switch (dmap_tag[3]) {
|
||||
case 'a':
|
||||
printf("Album artist: "); /*asaa*/
|
||||
metadata_text->append("Album artist: "); /*asaa*/
|
||||
break;
|
||||
case 'l':
|
||||
printf("Album: "); /*asal*/
|
||||
metadata_text->append("Album: "); /*asal*/
|
||||
break;
|
||||
case 'r':
|
||||
printf("Artist: "); /*asar*/
|
||||
metadata_text->append("Artist: "); /*asar*/
|
||||
break;
|
||||
default:
|
||||
dmap_type = 0;
|
||||
@@ -1300,16 +1322,16 @@ static void process_metadata(int count, const char *dmap_tag, const unsigned cha
|
||||
case 'c':
|
||||
switch (dmap_tag[3]) {
|
||||
case 'm':
|
||||
printf("Comment: "); /*ascm*/
|
||||
metadata_text->append("Comment: "); /*ascm*/
|
||||
break;
|
||||
case 'n':
|
||||
printf("Content description: "); /*ascn*/
|
||||
metadata_text->append("Content description: "); /*ascn*/
|
||||
break;
|
||||
case 'p':
|
||||
printf("Composer: "); /*ascp*/
|
||||
metadata_text->append("Composer: "); /*ascp*/
|
||||
break;
|
||||
case 't':
|
||||
printf("Category: "); /*asct*/
|
||||
metadata_text->append("Category: "); /*asct*/
|
||||
break;
|
||||
default:
|
||||
dmap_type = 0;
|
||||
@@ -1319,22 +1341,22 @@ static void process_metadata(int count, const char *dmap_tag, const unsigned cha
|
||||
case 's':
|
||||
switch (dmap_tag[3]) {
|
||||
case 'a':
|
||||
printf("Sort Artist: "); /*assa*/
|
||||
metadata_text->append("Sort Artist: "); /*assa*/
|
||||
break;
|
||||
case 'c':
|
||||
printf("Sort Composer: "); /*assc*/
|
||||
metadata_text->append("Sort Composer: "); /*assc*/
|
||||
break;
|
||||
case 'l':
|
||||
printf("Sort Album artist: "); /*assl*/
|
||||
metadata_text->append("Sort Album artist: "); /*assl*/
|
||||
break;
|
||||
case 'n':
|
||||
printf("Sort Name: "); /*assn*/
|
||||
metadata_text->append("Sort Name: "); /*assn*/
|
||||
break;
|
||||
case 's':
|
||||
printf("Sort Series: "); /*asss*/
|
||||
metadata_text->append("Sort Series: "); /*asss*/
|
||||
break;
|
||||
case 'u':
|
||||
printf("Sort Album: "); /*assu*/
|
||||
metadata_text->append("Sort Album: "); /*assu*/
|
||||
break;
|
||||
default:
|
||||
dmap_type = 0;
|
||||
@@ -1343,15 +1365,15 @@ static void process_metadata(int count, const char *dmap_tag, const unsigned cha
|
||||
break;
|
||||
default:
|
||||
if (strcmp(dmap_tag, "asdt") == 0) {
|
||||
printf("Description: ");
|
||||
metadata_text->append("Description: ");
|
||||
} else if (strcmp (dmap_tag, "asfm") == 0) {
|
||||
printf("Format: ");
|
||||
metadata_text->append("Format: ");
|
||||
} else if (strcmp (dmap_tag, "asgn") == 0) {
|
||||
printf("Genre: ");
|
||||
metadata_text->append("Genre: ");
|
||||
} else if (strcmp (dmap_tag, "asky") == 0) {
|
||||
printf("Keywords: ");
|
||||
metadata_text->append("Keywords: ");
|
||||
} else if (strcmp (dmap_tag, "aslc") == 0) {
|
||||
printf("Long Content Description: ");
|
||||
metadata_text->append("Long Content Description: ");
|
||||
} else {
|
||||
dmap_type = 0;
|
||||
}
|
||||
@@ -1359,21 +1381,27 @@ static void process_metadata(int count, const char *dmap_tag, const unsigned cha
|
||||
}
|
||||
} else if (strcmp (dmap_tag, "minm") == 0) {
|
||||
dmap_type = 9;
|
||||
printf("Title: ");
|
||||
metadata_text->append("Title: ");
|
||||
}
|
||||
|
||||
if (dmap_type == 9) {
|
||||
char *str = (char *) calloc(1, datalen + 1);
|
||||
char *str = (char *) calloc(datalen + 1, sizeof(char));
|
||||
memcpy(str, metadata, datalen);
|
||||
printf("%s", str);
|
||||
metadata_text->append(str);
|
||||
metadata_text->append("\n");
|
||||
free(str);
|
||||
} else if (debug_log) {
|
||||
std::string md = "";
|
||||
char hex[4];
|
||||
for (int i = 0; i < datalen; i++) {
|
||||
if (i > 0 && i % 16 == 0) printf("\n");
|
||||
printf("%2.2x ", (int) metadata[i]);
|
||||
if (i > 0 && i % 16 == 0) {
|
||||
md.append("\n");
|
||||
}
|
||||
snprintf(hex, 4, "%2.2x ", (int) metadata[i]);
|
||||
md.append(hex);
|
||||
}
|
||||
LOGI("%s", md.c_str());
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static int parse_dmap_header(const unsigned char *metadata, char *tag, int *len) {
|
||||
@@ -1833,6 +1861,9 @@ extern "C" void audio_get_format (void *cls, unsigned char *ct, unsigned short *
|
||||
if (coverart_filename.length()) {
|
||||
write_coverart(coverart_filename.c_str(), (const void *) empty_image, sizeof(empty_image));
|
||||
}
|
||||
if (metadata_filename.length()) {
|
||||
write_metadata(metadata_filename.c_str(), "no data\n");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void video_report_size(void *cls, float *width_source, float *height_source, float *width, float *height) {
|
||||
@@ -1879,6 +1910,7 @@ extern "C" void audio_set_metadata(void *cls, const void *buffer, int buflen) {
|
||||
dmap_tag, datalen, buflen);
|
||||
return;
|
||||
}
|
||||
std::string metadata_text = "";
|
||||
while (buflen >= 8) {
|
||||
count++;
|
||||
if (parse_dmap_header(metadata, dmap_tag, &datalen)) {
|
||||
@@ -1887,10 +1919,14 @@ extern "C" void audio_set_metadata(void *cls, const void *buffer, int buflen) {
|
||||
}
|
||||
metadata += 8;
|
||||
buflen -= 8;
|
||||
process_metadata(count, (const char *) dmap_tag, metadata, datalen);
|
||||
process_metadata(count, (const char *) dmap_tag, metadata, datalen, &metadata_text);
|
||||
metadata += datalen;
|
||||
buflen -= datalen;
|
||||
}
|
||||
LOGI("%s", metadata_text.c_str());
|
||||
if (metadata_filename.length()) {
|
||||
write_metadata(metadata_filename.c_str(), metadata_text.c_str());
|
||||
}
|
||||
if (buflen != 0) {
|
||||
LOGE("%d bytes of metadata were not processed", buflen);
|
||||
}
|
||||
@@ -2381,6 +2417,11 @@ int main (int argc, char *argv[]) {
|
||||
write_coverart(coverart_filename.c_str(), (const void *) empty_image, sizeof(empty_image));
|
||||
}
|
||||
|
||||
if (metadata_filename.length()) {
|
||||
LOGI("any AirPlay audio metadata text will be written to file %s",metadata_filename.c_str());
|
||||
write_metadata(metadata_filename.c_str(), "no data\n");
|
||||
}
|
||||
|
||||
/* set default resolutions for h264 or h265*/
|
||||
if (!display[0] && !display[1]) {
|
||||
if (h265_support) {
|
||||
@@ -2459,4 +2500,7 @@ int main (int argc, char *argv[]) {
|
||||
if (coverart_filename.length()) {
|
||||
remove (coverart_filename.c_str());
|
||||
}
|
||||
if (metadata_filename.length()) {
|
||||
remove (metadata_filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user