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 */