youtube: output improvements - frontends - front-ends for some sites (experiment)
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 4eef4fb9b890ae71f554e996a3a7a542302e68f4
 (DIR) parent 11f745425e13385e5a69cf3f8cdceaa3027dad64
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       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(-)
       ---
 (DIR) 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;
       +}
 (DIR) 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);
 (DIR) 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(buf))
       +                        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->contentlength / 1024.0 / 1024.0);
       +                }
       +
       +                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");
                        }
        
 (DIR) 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, const char *value,
        {
                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, const char *value,
                                if (depth == 2 &&
                                    nodes[2].type == JSON_TYPE_STRING &&
                                    !strcmp(nodes[2].name, "expiresInSeconds")) {
       -                                strlcpy(r->expiresinseconds, value, sizeof(r->expiresinseconds));
       +                                r->expiresinseconds = getnum(value);
                                }
        
                                if (depth >= 3 &&
       @@ -306,9 +316,8 @@ processnode_video(struct json_node *nodes, size_t depth, const char *value,
                                        if (r->nformats > MAX_FORMATS)
                                                return; /* ignore: don't add too many formats */
        
       -                                if (depth == 4 && nodes[3].type == JSON_TYPE_OBJECT) {
       +                                if (depth == 4 && nodes[3].type == JSON_TYPE_OBJECT)
                                                r->nformats++;
       -                                }
        
                                        if (r->nformats == 0)
                                                return;
       @@ -321,9 +330,9 @@ processnode_video(struct json_node *nodes, size_t depth, const char *value,
                                            nodes[4].type == JSON_TYPE_NUMBER ||
                                            nodes[4].type == JSON_TYPE_BOOL)) {
                                                if (!strcmp(nodes[4].name, "width")) {
       -                                                strlcpy(f->width, value, sizeof(f->width));
       +                                                f->width = getnum(value);
                                                } else if (!strcmp(nodes[4].name, "height")) {
       -                                                strlcpy(f->height, value, sizeof(f->height));
       +                                                f->height = getnum(value);
                                                } else if (!strcmp(nodes[4].name, "url")) {
                                                        strlcpy(f->url, value, sizeof(f->url));
                                                } else if (!strcmp(nodes[4].name, "qualityLabel")) {
       @@ -331,21 +340,23 @@ processnode_video(struct json_node *nodes, size_t depth, const char *value,
                                                } else if (!strcmp(nodes[4].name, "quality")) {
                                                        strlcpy(f->quality, value, sizeof(f->quality));
                                                } else if (!strcmp(nodes[4].name, "fps")) {
       -                                                strlcpy(f->fps, value, sizeof(f->fps));
       +                                                f->fps = getnum(value);
                                                } else if (!strcmp(nodes[4].name, "bitrate")) {
       -                                                strlcpy(f->bitrate, value, sizeof(f->bitrate));
       +                                                f->bitrate = getnum(value);
       +                                        } else if (!strcmp(nodes[4].name, "averageBitrate")) {
       +                                                f->averagebitrate = getnum(value);
                                                } else if (!strcmp(nodes[4].name, "mimeType")) {
                                                        strlcpy(f->mimetype, value, sizeof(f->mimetype));
                                                } else if (!strcmp(nodes[4].name, "itag")) {
       -                                                strlcpy(f->itag, value, sizeof(f->itag));
       +                                                f->itag = getnum(value);
                                                } else if (!strcmp(nodes[4].name, "contentLength")) {
       -                                                strlcpy(f->contentlength, value, sizeof(f->contentlength));
       +                                                f->contentlength = getnum(value);
                                                } else if (!strcmp(nodes[4].name, "lastModified")) {
       -                                                strlcpy(f->lastmodified, value, sizeof(f->lastmodified));
       +                                                f->lastmodified = getnum(value);
                                                } else if (!strcmp(nodes[4].name, "audioChannels")) {
       -                                                strlcpy(f->audiochannels, value, sizeof(f->audiochannels));
       +                                                f->audiochannels = getnum(value);
                                                } else if (!strcmp(nodes[4].name, "audioSampleRate")) {
       -                                                strlcpy(f->audiosamplerate, value, sizeof(f->audiosamplerate));
       +                                                f->audiosamplerate = getnum(value);
                                                }
                                        }
                                }
       @@ -375,11 +386,11 @@ processnode_video(struct json_node *nodes, size_t depth, const char *value,
                                } 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->lengthseconds));
       +                                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->viewcount));
       +                                r->viewcount = getnum(value);
                                } else if (!strcmp(nodes[2].name, "channelId")) {
                                        strlcpy(r->channelid, value, sizeof(r->channelid));
                                } else if (!strcmp(nodes[2].name, "shortDescription")) {
 (DIR) 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;
        };