libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

vlc_extractor.c (11725B)


      1 /*
      2      This file is part of libextractor.
      3      Copyright (C) 2021 Christian Grothoff
      4 
      5      libextractor is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 3, or (at your
      8      option) any later version.
      9 
     10      libextractor is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with libextractor; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19 
     20 */
     21 /**
     22  * @file plugins/vlc_extractor.c
     23  * @brief plugin to extract metadata using libvlc
     24  * @author Christian Grothoff
     25  */
     26 #include "platform.h"
     27 #include "extractor.h"
     28 #include <vlc/vlc.h>
     29 #include <signal.h>
     30 #include <semaphore.h>
     31 #include <pthread.h>
     32 
     33 struct IORequest
     34 {
     35   enum { IO_NONE, IO_READ, IO_SEEK, IO_OPEN, IO_CLOSE, IO_DONE } op;
     36   uint64_t offset;
     37   unsigned char *buf;
     38   size_t len;
     39   ssize_t result;
     40   uint64_t size;
     41   int open_ok;
     42   pthread_mutex_t mu;
     43   pthread_cond_t request_ready;
     44   pthread_cond_t response_ready;
     45   struct EXTRACTOR_ExtractContext *ec;
     46   int io_pending;
     47   int stop_requested;   /* player event fired; stop thread should act */
     48   int player_stopped;   /* set when MediaPlayerStopped fires */
     49   libvlc_media_player_t *player;  /* needed by the stop thread */
     50 };
     51 
     52 
     53 /* ------------------------------------------------------------------ */
     54 /* VLC callbacks — these run in VLC's threads.                         */
     55 /* They post an I/O request and sleep until the main thread services it */
     56 /* ------------------------------------------------------------------ */
     57 
     58 
     59 static int
     60 open_cb (void *cls,
     61          void **datap,
     62          uint64_t *sizep)
     63 {
     64   struct IORequest *io = cls;
     65   int ok;
     66 
     67   pthread_mutex_lock (&io->mu);
     68   io->op = IO_OPEN;
     69   io->io_pending = 1;
     70   pthread_cond_signal (&io->request_ready);
     71   pthread_cond_wait (&io->response_ready,
     72                      &io->mu);
     73   *datap = io;
     74   *sizep = io->size;
     75   ok = io->open_ok;
     76   pthread_mutex_unlock (&io->mu);
     77   return ok ? 0 : 1;
     78 }
     79 
     80 
     81 static ssize_t
     82 read_cb (void *cls,
     83          unsigned char *buf,
     84          size_t len)
     85 {
     86   struct IORequest *io = cls;
     87   ssize_t ret;
     88 
     89   pthread_mutex_lock (&io->mu);
     90   io->op  = IO_READ;
     91   io->buf = buf;
     92   io->len = len;
     93   io->io_pending = 1;
     94   pthread_cond_signal (&io->request_ready);
     95   pthread_cond_wait (&io->response_ready,
     96                      &io->mu);
     97   ret = io->result;
     98   pthread_mutex_unlock (&io->mu);
     99   return ret;
    100 }
    101 
    102 
    103 static int
    104 seek_cb (void *cls,
    105          uint64_t offset)
    106 {
    107   struct IORequest *io = cls;
    108   int ret;
    109 
    110   if (offset > INT64_MAX)
    111     return -1;
    112   pthread_mutex_lock (&io->mu);
    113   io->op     = IO_SEEK;
    114   io->offset = offset;
    115   io->io_pending = 1;
    116   pthread_cond_signal (&io->request_ready);
    117   pthread_cond_wait (&io->response_ready,
    118                      &io->mu);
    119   ret = (int) io->result;
    120   pthread_mutex_unlock (&io->mu);
    121   return ret;
    122 }
    123 
    124 
    125 /* close_cb IS the termination signal — no stop() needed */
    126 static void
    127 close_cb (void *cls)
    128 {
    129   struct IORequest *io = cls;
    130 
    131   pthread_mutex_lock (&io->mu);
    132   io->op = IO_DONE;
    133   io->io_pending = 1;
    134   pthread_cond_signal (&io->request_ready);
    135   pthread_cond_wait (&io->response_ready,
    136                      &io->mu);
    137   pthread_mutex_unlock (&io->mu);
    138 }
    139 
    140 
    141 static void
    142 player_stopped_event (const struct libvlc_event_t *p_event,
    143                       void *p_data)
    144 {
    145   struct IORequest *io = p_data;
    146 
    147   pthread_mutex_lock (&io->mu);
    148   io->player_stopped = 1;
    149   pthread_cond_broadcast (&io->request_ready);
    150   pthread_mutex_unlock (&io->mu);
    151 }
    152 
    153 
    154 /* ------------------------------------------------------------------ */
    155 /* Main-thread I/O dispatcher loop                                     */
    156 /* Runs on the calling thread so ec->read / ec->seek are single-thread */
    157 /* ------------------------------------------------------------------ */
    158 
    159 static void
    160 run_io_loop (struct IORequest *io)
    161 {
    162   struct EXTRACTOR_ExtractContext *ec = io->ec;
    163 
    164   pthread_mutex_lock (&io->mu);
    165   while (1)
    166   {
    167     /* wait for VLC to post a request */
    168     while (! io->io_pending)
    169       pthread_cond_wait (&io->request_ready,
    170                          &io->mu);
    171     io->io_pending = 0;
    172 
    173     switch (io->op)
    174     {
    175     case IO_OPEN:
    176       {
    177         uint64_t sz = ec->get_size (ec->cls);
    178 
    179         io->size = sz;
    180         io->open_ok = (sz != UINT64_MAX);
    181         io->op = IO_NONE;
    182         pthread_cond_signal (&io->response_ready);
    183         break;
    184       }
    185     case IO_READ:
    186       {
    187         void *data;
    188         ssize_t r = ec->read (ec->cls,
    189                               &data,
    190                               io->len);
    191 
    192         if (r > 0)
    193           memcpy (io->buf,
    194                   data,
    195                   (size_t) r);
    196         io->result = r;
    197         io->op = IO_NONE;
    198         pthread_cond_signal (&io->response_ready);
    199         break;
    200       }
    201     case IO_SEEK:
    202       {
    203         io->result = ec->seek (ec->cls,
    204                                (int64_t) io->offset,
    205                                SEEK_SET);
    206         io->op = IO_NONE;
    207         pthread_cond_signal (&io->response_ready);
    208         break;
    209       }
    210     case IO_CLOSE:
    211       io->op = IO_NONE;
    212       pthread_cond_signal (&io->response_ready);   /* ack close */
    213       break;
    214 
    215     case IO_DONE:
    216       io->op = IO_NONE;
    217       pthread_cond_signal (&io->response_ready);  /* ack close_cb */
    218       pthread_mutex_unlock (&io->mu);
    219       return;
    220 
    221     default:
    222       io->op = IO_NONE;
    223       break;
    224     }
    225   }
    226 }
    227 
    228 
    229 /* ------------------------------------------------------------------ */
    230 /* extract() and my_logger unchanged from your version                 */
    231 /* ------------------------------------------------------------------ */
    232 
    233 static void
    234 my_logger (void *data,
    235            int level,
    236            const libvlc_log_t *ctx,
    237            const char *fmt,
    238            va_list args)
    239 {
    240 #if 0
    241   vfprintf (stderr,
    242             fmt,
    243             args);
    244   fprintf (stderr, "\n");
    245 #endif
    246 }
    247 
    248 
    249 static void
    250 extract (struct EXTRACTOR_ExtractContext *ec,
    251          libvlc_media_t *media)
    252 {
    253   struct
    254   {
    255     enum libvlc_meta_t vt;
    256     enum EXTRACTOR_MetaType mt;
    257   } map[] = {
    258     { libvlc_meta_Title,
    259       EXTRACTOR_METATYPE_TITLE },
    260     { libvlc_meta_Artist,
    261       EXTRACTOR_METATYPE_ARTIST },
    262     { libvlc_meta_Genre,
    263       EXTRACTOR_METATYPE_GENRE },
    264     { libvlc_meta_Copyright,
    265       EXTRACTOR_METATYPE_COPYRIGHT },
    266     { libvlc_meta_Album,
    267       EXTRACTOR_METATYPE_ALBUM },
    268     { libvlc_meta_TrackNumber,
    269       EXTRACTOR_METATYPE_TRACK_NUMBER },
    270     { libvlc_meta_Description,
    271       EXTRACTOR_METATYPE_DESCRIPTION },
    272     { libvlc_meta_Rating,
    273       EXTRACTOR_METATYPE_RATING },
    274     { libvlc_meta_Date,
    275       EXTRACTOR_METATYPE_CREATION_TIME },
    276     { libvlc_meta_Setting,
    277       EXTRACTOR_METATYPE_UNKNOWN },
    278     { libvlc_meta_URL,
    279       EXTRACTOR_METATYPE_URL },
    280     { libvlc_meta_Language,
    281       EXTRACTOR_METATYPE_LANGUAGE },
    282     { libvlc_meta_NowPlaying,
    283       EXTRACTOR_METATYPE_UNKNOWN },
    284     { libvlc_meta_Publisher,
    285       EXTRACTOR_METATYPE_PUBLISHER },
    286     { libvlc_meta_EncodedBy,
    287       EXTRACTOR_METATYPE_ENCODED_BY },
    288     { libvlc_meta_ArtworkURL,
    289       EXTRACTOR_METATYPE_URL },
    290     { libvlc_meta_TrackID,
    291       EXTRACTOR_METATYPE_TRACK_NUMBER },
    292     { libvlc_meta_TrackTotal,
    293       EXTRACTOR_METATYPE_UNKNOWN },
    294     { libvlc_meta_Director,
    295       EXTRACTOR_METATYPE_MOVIE_DIRECTOR },
    296     { libvlc_meta_Season,
    297       EXTRACTOR_METATYPE_SHOW_SEASON_NUMBER },
    298     { libvlc_meta_Episode,
    299       EXTRACTOR_METATYPE_SHOW_EPISODE_NUMBER },
    300     { libvlc_meta_ShowName,
    301       EXTRACTOR_METATYPE_SHOW_NAME },
    302     { libvlc_meta_Actors,
    303       EXTRACTOR_METATYPE_PERFORMER },
    304     { libvlc_meta_AlbumArtist,
    305       EXTRACTOR_METATYPE_ARTIST },
    306     { libvlc_meta_DiscNumber,
    307       EXTRACTOR_METATYPE_DISC_NUMBER },
    308     { libvlc_meta_DiscTotal,
    309       EXTRACTOR_METATYPE_UNKNOWN },
    310     { 0, 0 }
    311   };
    312 
    313   for (unsigned int i = 0;
    314        0 != map[i].mt;
    315        i++)
    316   {
    317     char *meta;
    318 
    319     meta = libvlc_media_get_meta (media,
    320                                   map[i].vt);
    321     if (NULL == meta)
    322       continue;
    323     ec->proc (ec->cls,
    324               "vlc",
    325               map[i].mt,
    326               EXTRACTOR_METAFORMAT_UTF8, /* ??? */
    327               "text/plain",
    328               meta,
    329               strlen (meta) + 1);
    330     free (meta);
    331   }
    332 }
    333 
    334 
    335 /**
    336  * Extract information using libvlc
    337  *
    338  * @param ec extraction context
    339  */
    340 void
    341 EXTRACTOR_vlc_extract_method (struct EXTRACTOR_ExtractContext *ec);
    342 
    343 void
    344 EXTRACTOR_vlc_extract_method (struct EXTRACTOR_ExtractContext *ec)
    345 {
    346   struct IORequest io = {
    347     .op = IO_NONE,
    348     .ec = ec,
    349   };
    350   libvlc_instance_t *vlc;
    351   libvlc_media_t *media;
    352   libvlc_media_player_t *player;
    353 
    354   pthread_mutex_init (&io.mu,
    355                       NULL);
    356   pthread_cond_init (&io.request_ready,
    357                      NULL);
    358   pthread_cond_init (&io.response_ready,
    359                      NULL);
    360 
    361   {
    362     sigset_t set;
    363 
    364     signal (SIGCHLD,
    365             SIG_DFL);
    366     sigemptyset (&set);
    367     sigaddset (&set,
    368                SIGPIPE);
    369     pthread_sigmask (SIG_BLOCK,
    370                      &set,
    371                      NULL);
    372   }
    373 
    374   {
    375     const char *argv[] = {
    376       "--no-video",
    377       "--no-audio",
    378       "--no-plugins-cache",
    379       NULL
    380     };
    381 
    382     vlc = libvlc_new (3,
    383                       argv);
    384   }
    385   if (NULL == vlc)
    386     goto cleanup;
    387 
    388   libvlc_log_set (vlc,
    389                   &my_logger,
    390                   NULL);
    391 
    392   /* Pass &io as closure — callbacks use it to marshal I/O to main thread */
    393   media = libvlc_media_new_callbacks (vlc,
    394                                       &open_cb,
    395                                       &read_cb,
    396                                       &seek_cb,
    397                                       &close_cb,
    398                                       &io);
    399   if (NULL == media)
    400   {
    401     libvlc_release (vlc);
    402     goto cleanup;
    403   }
    404 
    405   /* Create a player — this is what actually drives the input thread */
    406   player = libvlc_media_player_new_from_media (media);
    407   libvlc_media_release (media);   /* player holds its own reference */
    408   if (NULL == player)
    409   {
    410     libvlc_release (vlc);
    411     goto cleanup;
    412   }
    413 
    414   {
    415     libvlc_event_manager_t *pem = libvlc_media_player_event_manager (player);
    416 
    417     libvlc_event_attach (pem,
    418                          libvlc_MediaPlayerStopped,
    419                          &player_stopped_event,
    420                          &io);
    421   }
    422 
    423   /* Mute and suppress output completely */
    424   libvlc_audio_set_mute (player,
    425                          1);
    426 
    427   /* play() causes VLC to actually open and read the media */
    428   libvlc_media_player_play (player);
    429 
    430   /* Service I/O until close_cb fires (input thread fully exited) */
    431   run_io_loop (&io);
    432 
    433   /* Stop immediately — we got what we needed */
    434   libvlc_media_player_stop (player);
    435 
    436   /* Wait for the player to confirm it has fully stopped */
    437   pthread_mutex_lock (&io.mu);
    438   while (! io.player_stopped)
    439     pthread_cond_wait (&io.request_ready, &io.mu);
    440   pthread_mutex_unlock (&io.mu);
    441 
    442   /* Get the player's internal media copy to read metadata from */
    443   {
    444     libvlc_media_t *played_media = libvlc_media_player_get_media (player);
    445 
    446     if (NULL != played_media)
    447     {
    448       extract (ec,
    449                played_media);
    450       libvlc_media_release (played_media);
    451     }
    452   }
    453   libvlc_media_player_release (player);
    454   libvlc_release (vlc);
    455 
    456 cleanup:
    457   pthread_cond_destroy  (&io.response_ready);
    458   pthread_cond_destroy  (&io.request_ready);
    459   pthread_mutex_destroy (&io.mu);
    460 }
    461 
    462 
    463 /* end of vlc_extractor.c */