youtube: output improvements - frontends - front-ends for some sites (experimen… | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 4eef4fb9b890ae71f554e996a3a7a542302e68f4 | |
parent 11f745425e13385e5a69cf3f8cdceaa3027dad64 | |
Author: Hiltjo Posthuma <[email protected]> | |
Date: Fri, 24 Feb 2023 22:39:39 +0100 | |
youtube: output improvements | |
- pre-parse numbers to long long. | |
- show duration as a string %H:%M:%S. | |
- show filesize in bytes and MB. | |
- etc... | |
Diffstat: | |
M util.c | 17 +++++++++++++++++ | |
M util.h | 1 + | |
M youtube/cli.c | 110 +++++++++++++++++------------… | |
M youtube/youtube.c | 41 ++++++++++++++++++++---------… | |
M youtube/youtube.h | 31 ++++++++++++++++-------------… | |
5 files changed, 122 insertions(+), 78 deletions(-) | |
--- | |
diff --git a/util.c b/util.c | |
@@ -204,3 +204,20 @@ gophertext(FILE *fp, const char *s, size_t len) | |
} | |
} | |
} | |
+ | |
+/* seconds to duration string: "%H:%M:%S" or "%H:%M:%S" */ | |
+int | |
+durationstr(long secs, char *buf, size_t bufsiz) | |
+{ | |
+ int h, m, s, r; | |
+ | |
+ h = secs / 3600; | |
+ m = secs / 60; | |
+ s = secs; | |
+ if (h <= 0) | |
+ r = snprintf(buf, bufsiz, "%02d:%02d", m % 60, s % 60); | |
+ else | |
+ r = snprintf(buf, bufsiz, "%d:%02d:%02d", h, m % 60, s % 60); | |
+ | |
+ return r; | |
+} | |
diff --git a/util.h b/util.h | |
@@ -9,6 +9,7 @@ size_t strlcat(char *, const char *, size_t); | |
size_t strlcpy(char *, const char *, size_t); | |
int decodeparam(char *buf, size_t bufsiz, const char *s); | |
+int durationstr(long secs, char *buf, size_t bufsiz); | |
int friendlytime(time_t now, time_t t); | |
char *getparam(const char *query, const char *s); | |
void gophertext(FILE *fp, const char *s, size_t len); | |
diff --git a/youtube/cli.c b/youtube/cli.c | |
@@ -157,12 +157,11 @@ int | |
render_video(struct video_response *r) | |
{ | |
struct video_format *f; | |
- long l; | |
+ char buf[256]; | |
int i; | |
OUT("URL: "); | |
- OUTESCAPE(r->id); | |
- OUT(", https://www.youtube.com/embed/"); | |
+ OUT("https://www.youtube.com/embed/"); | |
OUTESCAPE(r->id); | |
OUT("\n"); | |
@@ -170,21 +169,28 @@ render_video(struct video_response *r) | |
OUTESCAPE(r->title); | |
OUT("\n"); | |
- OUT("Views: "); | |
- OUTESCAPE(r->viewcount); | |
- OUT("\n"); | |
+ if (r->lengthseconds > 0) { | |
+ OUT("Length: "); | |
+ if (durationstr(r->lengthseconds, buf, sizeof(buf)) < sizeof(b… | |
+ OUTESCAPE(buf); | |
+ OUT("\n"); | |
+ } | |
- OUT("Length: "); | |
- OUTESCAPE(r->lengthseconds); | |
+ OUT("Views: "); | |
+ printf("%ld", r->viewcount); | |
OUT("\n"); | |
- OUT("Published: "); | |
- OUTESCAPE(r->publishdate); | |
- OUT("\n"); | |
+ if (r->publishdate[0]) { | |
+ OUT("Published: "); | |
+ OUTESCAPE(r->publishdate); | |
+ OUT("\n"); | |
+ } | |
- OUT("Uploaded: "); | |
- OUTESCAPE(r->uploaddate); | |
- OUT("\n"); | |
+ if (r->uploaddate[0]) { | |
+ OUT("Uploaded: "); | |
+ OUTESCAPE(r->uploaddate); | |
+ OUT("\n"); | |
+ } | |
if (r->author[0]) { | |
OUT("Channel: "); | |
@@ -208,9 +214,9 @@ render_video(struct video_response *r) | |
OUT("\n\nFormats:\n\n"); | |
/* links expiration */ | |
- if (r->expiresinseconds[0]) { | |
+ if (r->expiresinseconds > 0) { | |
OUT("Expires in "); | |
- OUTESCAPE(r->expiresinseconds); | |
+ printf("%ld", r->expiresinseconds); | |
OUT(" seconds\n"); | |
} | |
@@ -218,68 +224,76 @@ render_video(struct video_response *r) | |
f = &(r->formats[i]); | |
#if 0 | |
- l = strtol(f->width, NULL, 10); | |
- if (l < 1280) | |
- continue; | |
- l = strtol(f->height, NULL, 10); | |
- if (l < 720) | |
+ if (f->width < 1280 || f->height < 720) | |
continue; | |
#endif | |
#if 0 | |
- OUT("\titag: "); | |
+ OUT("itag: "); | |
OUTESCAPE(f->itag); | |
OUT("\n"); | |
- OUT("\tLast modified: "); | |
+ OUT("Last modified: "); | |
OUTESCAPE(f->lastmodified); | |
OUT("\n"); | |
- OUT("\tContent-Length: "); | |
- OUTESCAPE(f->contentlength); | |
- OUT("\n"); | |
+ | |
#endif | |
- OUT("\tURL: "); | |
+ OUT("URL: "); | |
OUTESCAPE(f->url); | |
OUT("\n"); | |
- OUT("\tMime-type: "); | |
- OUTESCAPE(f->mimetype); | |
- OUT("\n"); | |
- | |
- OUT("\tBitrate: "); | |
- OUTESCAPE(f->bitrate); | |
- OUT("\n"); | |
+ if (f->mimetype[0]) { | |
+ OUT("Mime-type: "); | |
+ OUTESCAPE(f->mimetype); | |
+ OUT("\n"); | |
+ } | |
- OUT("\tQuality: "); | |
- if (f->qualitylabel[0]) | |
+ if (f->qualitylabel[0]) { | |
+ OUT("Quality: "); | |
OUTESCAPE(f->qualitylabel); | |
- else if (f->quality[0]) | |
+ } else if (f->quality[0]) { | |
+ OUT("Quality: "); | |
OUTESCAPE(f->quality); | |
+ } | |
- if (f->width[0]) { | |
+ if (f->width > 0) { | |
OUT(", "); | |
- OUTESCAPE(f->width); | |
+ printf("%ld", f->width); | |
OUT("x"); | |
- OUTESCAPE(f->height); | |
+ printf("%ld", f->height); | |
OUT(""); | |
} | |
- if (f->fps[0]) { | |
+ if (f->fps > 0) { | |
OUT(", "); | |
- OUTESCAPE(f->fps); | |
+ printf("%ld", f->fps); | |
OUT(" FPS"); | |
} | |
OUT("\n"); | |
- if (f->audiochannels[0]) { | |
- OUT("\tAudio channels: "); | |
- OUTESCAPE(f->audiochannels); | |
+ if (f->bitrate > 0) { | |
+ OUT("Bitrate: "); | |
+ printf("%ld", f->bitrate); | |
+ if (f->averagebitrate > 0) | |
+ printf(", average: %ld", f->averagebitrate); | |
OUT("\n"); | |
} | |
- if (f->audiosamplerate[0]) { | |
- OUT("\tAudio sample rate: "); | |
- OUTESCAPE(f->audiosamplerate); | |
+ | |
+ if (f->contentlength > 0) { | |
+ OUT("Size: "); | |
+ printf("%lld bytes (%.2f MB)\n", f->contentlength, f->… | |
+ } | |
+ | |
+ if (f->audiochannels > 0 || f->audiosamplerate) { | |
+ OUT("Audio: "); | |
+ if (f->audiochannels > 0) | |
+ printf("%ld channels", f->audiochannels); | |
+ if (f->audiosamplerate > 0) { | |
+ if (f->audiochannels > 0) | |
+ OUT(", "); | |
+ printf("%ld sample rate", f->audiosamplerate); | |
+ } | |
OUT("\n"); | |
} | |
diff --git a/youtube/youtube.c b/youtube/youtube.c | |
@@ -15,6 +15,17 @@ | |
#include "util.h" | |
#include "youtube.h" | |
+static long long | |
+getnum(const char *s) | |
+{ | |
+ long long l; | |
+ | |
+ l = strtoll(s, 0, 10); | |
+ if (l < 0) | |
+ l = 0; | |
+ return l; | |
+} | |
+ | |
static char * | |
youtube_request(const char *path) | |
{ | |
@@ -286,7 +297,6 @@ processnode_video(struct json_node *nodes, size_t depth, co… | |
{ | |
struct video_response *r = (struct video_response *)pp; | |
struct video_format *f; | |
- static struct item *item; | |
if (depth > 1) { | |
if (nodes[0].type == JSON_TYPE_OBJECT && | |
@@ -296,7 +306,7 @@ processnode_video(struct json_node *nodes, size_t depth, co… | |
if (depth == 2 && | |
nodes[2].type == JSON_TYPE_STRING && | |
!strcmp(nodes[2].name, "expiresInSeconds")) { | |
- strlcpy(r->expiresinseconds, value, sizeof(r->… | |
+ r->expiresinseconds = getnum(value); | |
} | |
if (depth >= 3 && | |
@@ -306,9 +316,8 @@ processnode_video(struct json_node *nodes, size_t depth, co… | |
if (r->nformats > MAX_FORMATS) | |
return; /* ignore: don't add too many … | |
- if (depth == 4 && nodes[3].type == JSON_TYPE_O… | |
+ if (depth == 4 && nodes[3].type == JSON_TYPE_O… | |
r->nformats++; | |
- } | |
if (r->nformats == 0) | |
return; | |
@@ -321,9 +330,9 @@ processnode_video(struct json_node *nodes, size_t depth, co… | |
nodes[4].type == JSON_TYPE_NUMBER || | |
nodes[4].type == JSON_TYPE_BOOL)) { | |
if (!strcmp(nodes[4].name, "width")) { | |
- strlcpy(f->width, value, sizeo… | |
+ f->width = getnum(value); | |
} else if (!strcmp(nodes[4].name, "hei… | |
- strlcpy(f->height, value, size… | |
+ f->height = getnum(value); | |
} else if (!strcmp(nodes[4].name, "url… | |
strlcpy(f->url, value, sizeof(… | |
} else if (!strcmp(nodes[4].name, "qua… | |
@@ -331,21 +340,23 @@ processnode_video(struct json_node *nodes, size_t depth, … | |
} else if (!strcmp(nodes[4].name, "qua… | |
strlcpy(f->quality, value, siz… | |
} else if (!strcmp(nodes[4].name, "fps… | |
- strlcpy(f->fps, value, sizeof(… | |
+ f->fps = getnum(value); | |
} else if (!strcmp(nodes[4].name, "bit… | |
- strlcpy(f->bitrate, value, siz… | |
+ f->bitrate = getnum(value); | |
+ } else if (!strcmp(nodes[4].name, "ave… | |
+ f->averagebitrate = getnum(val… | |
} else if (!strcmp(nodes[4].name, "mim… | |
strlcpy(f->mimetype, value, si… | |
} else if (!strcmp(nodes[4].name, "ita… | |
- strlcpy(f->itag, value, sizeof… | |
+ f->itag = getnum(value); | |
} else if (!strcmp(nodes[4].name, "con… | |
- strlcpy(f->contentlength, valu… | |
+ f->contentlength = getnum(valu… | |
} else if (!strcmp(nodes[4].name, "las… | |
- strlcpy(f->lastmodified, value… | |
+ f->lastmodified = getnum(value… | |
} else if (!strcmp(nodes[4].name, "aud… | |
- strlcpy(f->audiochannels, valu… | |
+ f->audiochannels = getnum(valu… | |
} else if (!strcmp(nodes[4].name, "aud… | |
- strlcpy(f->audiosamplerate, va… | |
+ f->audiosamplerate = getnum(va… | |
} | |
} | |
} | |
@@ -375,11 +386,11 @@ processnode_video(struct json_node *nodes, size_t depth, … | |
} else if (!strcmp(nodes[2].name, "videoId")) { | |
strlcpy(r->id, value, sizeof(r->id)); | |
} else if (!strcmp(nodes[2].name, "lengthSeconds")) { | |
- strlcpy(r->lengthseconds, value, sizeof(r->len… | |
+ r->lengthseconds = getnum(value); | |
} else if (!strcmp(nodes[2].name, "author")) { | |
strlcpy(r->author, value, sizeof(r->author)); | |
} else if (!strcmp(nodes[2].name, "viewCount")) { | |
- strlcpy(r->viewcount, value, sizeof(r->viewcou… | |
+ r->viewcount = getnum(value); | |
} else if (!strcmp(nodes[2].name, "channelId")) { | |
strlcpy(r->channelid, value, sizeof(r->channel… | |
} else if (!strcmp(nodes[2].name, "shortDescription"))… | |
diff --git a/youtube/youtube.h b/youtube/youtube.h | |
@@ -5,9 +5,9 @@ struct item { | |
char channeltitle[1024]; | |
char channelid[256]; | |
char userid[256]; | |
- char publishedat[32]; | |
- char viewcount[32]; | |
- char duration[32]; | |
+ char publishedat[32]; /* "human-friendly" string */ | |
+ char viewcount[32]; /* view count string, formatted */ | |
+ char duration[32]; /* duration string */ | |
#ifdef neinneinnein | |
char shortdescription[4096]; | |
@@ -21,19 +21,20 @@ struct search_response { | |
}; | |
struct video_format { | |
- char itag[32]; /* video id */ | |
+ long itag; | |
char url[2048]; | |
char mimetype[256]; /* mime-type and video codecs, etc */ | |
- char bitrate[256]; | |
- char width[32]; /* pixel width */ | |
- char height[32]; /* pixel width */ | |
- char fps[16]; /* frames-per-second */ | |
+ long bitrate; | |
+ long averagebitrate; | |
+ long width; /* pixel width */ | |
+ long height; /* pixel width */ | |
+ long fps; /* frames-per-second */ | |
char qualitylabel[64]; | |
char quality[64]; | |
- char contentlength[64]; /* content length in bytes */ | |
- char lastmodified[64]; | |
- char audiosamplerate[32]; | |
- char audiochannels[16]; | |
+ long long contentlength; /* content length in bytes */ | |
+ long lastmodified; /* timestamp */ | |
+ long audiosamplerate; | |
+ long audiochannels; | |
}; | |
#define MAX_FORMATS 50 | |
@@ -44,14 +45,14 @@ struct video_response { | |
char channelid[256]; | |
char publishdate[32]; /* YYYY-mm-dd */ | |
char uploaddate[32]; /* YYYY-mm-dd */ | |
- char viewcount[32]; | |
- char lengthseconds[32]; | |
+ long viewcount; | |
+ long lengthseconds; | |
char shortdescription[4096 * 4]; | |
int isfound; | |
/* expiration for URLs in video formats */ | |
- char expiresinseconds[32]; | |
+ long expiresinseconds; | |
struct video_format formats[MAX_FORMATS + 1]; | |
int nformats; | |
}; |