/* Get metadata from the EXTINF tag */
static int
extinf_get(char *string, struct media_file_info *mfi, int *extinf)
{
  char *ptr;

  if (strncmp(string, "#EXTINF:", strlen("#EXTINF:")) != 0)
    return 0;

  ptr = strchr(string, ',');
  if (!ptr || strlen(ptr) < 2)
    return 0;

  /* New extinf found, so clear old data */
  free_mfi(mfi, 1);

  *extinf = 1;
  mfi->artist = strdup(ptr + 1);

  ptr = strstr(mfi->artist, " -");
  if (ptr && strlen(ptr) > 3)
    mfi->title = strdup(ptr + 3);
  else
    mfi->title = strdup("");
  if (ptr)
    *ptr = '\0';

  return 1;
}
示例#2
0
/* Thread: httpd */
void
httpd_stream_file(struct evhttp_request *req, int id)
{
  struct media_file_info *mfi;
  struct stream_ctx *st;
  void (*stream_cb)(int fd, short event, void *arg);
  struct stat sb;
  struct timeval tv;
  struct evhttp_connection *evcon;
  struct evkeyvalq *input_headers;
  struct evkeyvalq *output_headers;
  const char *param;
  const char *param_end;
  const char *ua;
  const char *client_codecs;
  char buf[64];
  int64_t offset;
  int64_t end_offset;
  off_t pos;
  int transcode;
  int ret;

  offset = 0;
  end_offset = 0;

  input_headers = evhttp_request_get_input_headers(req);

  param = evhttp_find_header(input_headers, "Range");
  if (param)
    {
      DPRINTF(E_DBG, L_HTTPD, "Found Range header: %s\n", param);

      /* Start offset */
      ret = safe_atoi64(param + strlen("bytes="), &offset);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_HTTPD, "Invalid start offset, will stream whole file (%s)\n", param);
	  offset = 0;
	}
      /* End offset, if any */
      else
	{
	  param_end = strchr(param, '-');
	  if (param_end && (strlen(param_end) > 1))
	    {
	      ret = safe_atoi64(param_end + 1, &end_offset);
	      if (ret < 0)
		{
		  DPRINTF(E_LOG, L_HTTPD, "Invalid end offset, will stream to end of file (%s)\n", param);
		  end_offset = 0;
		}

	      if (end_offset < offset)
		{
		  DPRINTF(E_LOG, L_HTTPD, "End offset < start offset, will stream to end of file (%" PRIi64 " < %" PRIi64 ")\n", end_offset, offset);
		  end_offset = 0;
		}
	    }
	}
    }

  mfi = db_file_fetch_byid(id);
  if (!mfi)
    {
      DPRINTF(E_LOG, L_HTTPD, "Item %d not found\n", id);

      evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
      return;
    }

  if (mfi->data_kind != DATA_KIND_FILE)
    {
      evhttp_send_error(req, 500, "Cannot stream radio station");

      goto out_free_mfi;
    }

  st = (struct stream_ctx *)malloc(sizeof(struct stream_ctx));
  if (!st)
    {
      DPRINTF(E_LOG, L_HTTPD, "Out of memory for struct stream_ctx\n");

      evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");

      goto out_free_mfi;
    }
  memset(st, 0, sizeof(struct stream_ctx));
  st->fd = -1;

  ua = evhttp_find_header(input_headers, "User-Agent");
  client_codecs = evhttp_find_header(input_headers, "Accept-Codecs");

  transcode = transcode_needed(ua, client_codecs, mfi->codectype);

  output_headers = evhttp_request_get_output_headers(req);

  if (transcode)
    {
      DPRINTF(E_INFO, L_HTTPD, "Preparing to transcode %s\n", mfi->path);

      stream_cb = stream_chunk_xcode_cb;

      st->xcode = transcode_setup(mfi, XCODE_PCM16_HEADER, &st->size);
      if (!st->xcode)
	{
	  DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");

	  evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");

	  goto out_free_st;
	}

      if (!evhttp_find_header(output_headers, "Content-Type"))
	evhttp_add_header(output_headers, "Content-Type", "audio/wav");
    }
  else
    {
      /* Stream the raw file */
      DPRINTF(E_INFO, L_HTTPD, "Preparing to stream %s\n", mfi->path);

      st->buf = (uint8_t *)malloc(STREAM_CHUNK_SIZE);
      if (!st->buf)
	{
	  DPRINTF(E_LOG, L_HTTPD, "Out of memory for raw streaming buffer\n");

	  evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");

	  goto out_free_st;
	}

      stream_cb = stream_chunk_raw_cb;

      st->fd = open(mfi->path, O_RDONLY);
      if (st->fd < 0)
	{
	  DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", mfi->path, strerror(errno));

	  evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");

	  goto out_cleanup;
	}

      ret = stat(mfi->path, &sb);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", mfi->path, strerror(errno));

	  evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");

	  goto out_cleanup;
	}
      st->size = sb.st_size;

      pos = lseek(st->fd, offset, SEEK_SET);
      if (pos == (off_t) -1)
	{
	  DPRINTF(E_LOG, L_HTTPD, "Could not seek into %s: %s\n", mfi->path, strerror(errno));

	  evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");

	  goto out_cleanup;
	}
      st->offset = offset;
      st->end_offset = end_offset;

      /* Content-Type for video files is different than for audio files
       * and overrides whatever may have been set previously, like
       * application/x-dmap-tagged when we're speaking DAAP.
       */
      if (mfi->has_video)
	{
	  /* Front Row and others expect video/<type> */
	  ret = snprintf(buf, sizeof(buf), "video/%s", mfi->type);
	  if ((ret < 0) || (ret >= sizeof(buf)))
	    DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n");
	  else
	    {
	      evhttp_remove_header(output_headers, "Content-Type");
	      evhttp_add_header(output_headers, "Content-Type", buf);
	    }
	}
      /* If no Content-Type has been set and we're streaming audio, add a proper
       * Content-Type for the file we're streaming. Remember DAAP streams audio
       * with application/x-dmap-tagged as the Content-Type (ugh!).
       */
      else if (!evhttp_find_header(output_headers, "Content-Type") && mfi->type)
	{
	  ret = snprintf(buf, sizeof(buf), "audio/%s", mfi->type);
	  if ((ret < 0) || (ret >= sizeof(buf)))
	    DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n");
	  else
	    evhttp_add_header(output_headers, "Content-Type", buf);
	}
    }

  st->evbuf = evbuffer_new();
  if (!st->evbuf)
    {
      DPRINTF(E_LOG, L_HTTPD, "Could not allocate an evbuffer for streaming\n");

      evhttp_clear_headers(output_headers);
      evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");

      goto out_cleanup;
    }

  ret = evbuffer_expand(st->evbuf, STREAM_CHUNK_SIZE);
  if (ret != 0)
    {
      DPRINTF(E_LOG, L_HTTPD, "Could not expand evbuffer for streaming\n");

      evhttp_clear_headers(output_headers);
      evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");

      goto out_cleanup;
    }

  st->ev = event_new(evbase_httpd, -1, EV_TIMEOUT, stream_cb, st);
  evutil_timerclear(&tv);
  if (!st->ev || (event_add(st->ev, &tv) < 0))
    {
      DPRINTF(E_LOG, L_HTTPD, "Could not add one-shot event for streaming\n");

      evhttp_clear_headers(output_headers);
      evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");

      goto out_cleanup;
    }

  st->id = mfi->id;
  st->start_offset = offset;
  st->stream_size = st->size;
  st->req = req;

  if ((offset == 0) && (end_offset == 0))
    {
      /* If we are not decoding, send the Content-Length. We don't do
       * that if we are decoding because we can only guesstimate the
       * size in this case and the error margin is unknown and variable.
       */
      if (!transcode)
	{
	  ret = snprintf(buf, sizeof(buf), "%" PRIi64, (int64_t)st->size);
	  if ((ret < 0) || (ret >= sizeof(buf)))
	    DPRINTF(E_LOG, L_HTTPD, "Content-Length too large for buffer, dropping\n");
	  else
	    evhttp_add_header(output_headers, "Content-Length", buf);
	}

      evhttp_send_reply_start(req, HTTP_OK, "OK");
    }
  else
    {
      if (offset > 0)
	st->stream_size -= offset;
      if (end_offset > 0)
	st->stream_size -= (st->size - end_offset);

      DPRINTF(E_DBG, L_HTTPD, "Stream request with range %" PRIi64 "-%" PRIi64 "\n", offset, end_offset);

      ret = snprintf(buf, sizeof(buf), "bytes %" PRIi64 "-%" PRIi64 "/%" PRIi64,
		     offset, (end_offset) ? end_offset : (int64_t)st->size, (int64_t)st->size);
      if ((ret < 0) || (ret >= sizeof(buf)))
	DPRINTF(E_LOG, L_HTTPD, "Content-Range too large for buffer, dropping\n");
      else
	evhttp_add_header(output_headers, "Content-Range", buf);

      ret = snprintf(buf, sizeof(buf), "%" PRIi64, ((end_offset) ? end_offset + 1 : (int64_t)st->size) - offset);
      if ((ret < 0) || (ret >= sizeof(buf)))
	DPRINTF(E_LOG, L_HTTPD, "Content-Length too large for buffer, dropping\n");
      else
	evhttp_add_header(output_headers, "Content-Length", buf);

      evhttp_send_reply_start(req, 206, "Partial Content");
    }

#ifdef HAVE_POSIX_FADVISE
  if (!transcode)
    {
      /* Hint the OS */
      posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_WILLNEED);
      posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_SEQUENTIAL);
      posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_NOREUSE);
    }
#endif

  evcon = evhttp_request_get_connection(req);

  evhttp_connection_set_closecb(evcon, stream_fail_cb, st);

  DPRINTF(E_INFO, L_HTTPD, "Kicking off streaming for %s\n", mfi->path);

  free_mfi(mfi, 0);

  return;

 out_cleanup:
  if (st->evbuf)
    evbuffer_free(st->evbuf);
  if (st->xcode)
    transcode_cleanup(st->xcode);
  if (st->buf)
    free(st->buf);
  if (st->fd > 0)
    close(st->fd);
 out_free_st:
  free(st);
 out_free_mfi:
  free_mfi(mfi, 0);
}
void
scan_playlist(char *file, time_t mtime)
{
  FILE *fp;
  struct media_file_info mfi;
  struct playlist_info *pli;
  struct stat sb;
  char buf[PATH_MAX];
  char *path;
  char *entry;
  char *filename;
  char *ptr;
  size_t len;
  int extinf;
  int pl_id;
  int pl_format;
  int mfi_id;
  int ret;
  char virtual_path[PATH_MAX];
  int i;

  DPRINTF(E_LOG, L_SCAN, "Processing static playlist: %s\n", file);

  ptr = strrchr(file, '.');
  if (!ptr)
    return;

  if (strcasecmp(ptr, ".m3u") == 0)
    pl_format = PLAYLIST_M3U;
  else if (strcasecmp(ptr, ".pls") == 0)
    pl_format = PLAYLIST_PLS;
  else
    return;

  filename = strrchr(file, '/');
  if (!filename)
    filename = file;
  else
    filename++;

  ret = stat(file, &sb);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno));

      return;
    }

  fp = fopen(file, "r");
  if (!fp)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not open playlist '%s': %s\n", file, strerror(errno));

      return;
    }

  /* Fetch or create playlist */
  pli = db_pl_fetch_bypath(file);
  if (pli)
    {
      DPRINTF(E_DBG, L_SCAN, "Found playlist '%s', updating\n", file);

      pl_id = pli->id;

      db_pl_ping(pl_id);
      db_pl_clear_items(pl_id);
    }
  else
    {
      pli = (struct playlist_info *)malloc(sizeof(struct playlist_info));
      if (!pli)
	{
	  DPRINTF(E_LOG, L_SCAN, "Out of memory\n");

	  return;
	}

      memset(pli, 0, sizeof(struct playlist_info));

      pli->type = PL_PLAIN;

      /* Get only the basename, to be used as the playlist title */
      ptr = strrchr(filename, '.');
      if (ptr)
	*ptr = '\0';

      pli->title = strdup(filename);

      /* Restore the full filename */
      if (ptr)
	*ptr = '.';

      pli->path = strdup(file);
      snprintf(virtual_path, PATH_MAX, "/file:%s", file);
      pli->virtual_path = strdup(virtual_path);

      ret = db_pl_add(pli, &pl_id);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_SCAN, "Error adding playlist '%s'\n", file);

	  free_pli(pli, 0);
	  return;
	}

      DPRINTF(E_INFO, L_SCAN, "Added playlist as id %d\n", pl_id);
    }

  free_pli(pli, 0);

  extinf = 0;
  memset(&mfi, 0, sizeof(struct media_file_info));

  while (fgets(buf, sizeof(buf), fp) != NULL)
    {
      len = strlen(buf);

      /* rtrim and check that length is sane (ignore blank lines) */
      while ((len > 0) && isspace(buf[len - 1]))
	{
	  len--;
	  buf[len] = '\0';
	}
      if (len < 1)
	continue;

      /* Saves metadata in mfi if EXTINF metadata line */
      if ((pl_format == PLAYLIST_M3U) && extinf_get(buf, &mfi, &extinf))
	continue;

      /* For pls files we are only interested in the part after the FileX= entry */
      path = NULL;
      if ((pl_format == PLAYLIST_PLS) && (strncasecmp(buf, "file", strlen("file")) == 0))
	path = strchr(buf, '=') + 1;
      else if (pl_format == PLAYLIST_M3U)
	path = buf;

      if (!path)
	continue;

      /* Check that first char is sane for a path */
      if ((!isalnum(path[0])) && (path[0] != '/') && (path[0] != '.'))
	continue;

      /* Check if line is an URL, will be added to library */
      if (strncasecmp(path, "http://", strlen("http://")) == 0)
	{
	  DPRINTF(E_DBG, L_SCAN, "Playlist contains URL entry\n");

	  filename = strdup(path);
	  if (!filename)
	    {
	      DPRINTF(E_LOG, L_SCAN, "Out of memory for playlist filename\n");

	      continue;
	    }

	  if (extinf)
	    DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", mfi.artist, mfi.title);

	  filescanner_process_media(filename, mtime, 0, F_SCAN_TYPE_URL, &mfi);
	}
      /* Regular file, should already be in library */
      else
	{
	  /* Playlist might be from Windows so we change backslash to forward slash */
	  for (i = 0; i < strlen(path); i++)
	    {
	      if (path[i] == '\\')
	        path[i] = '/';
	    }

          /* Now search for the library item where the path has closest match to playlist item */
          /* Succes is when we find an unambiguous match, or when we no longer can expand the  */
          /* the path to refine our search.                                                    */
	  entry = NULL;
	  do
	    {
	      ptr = strrchr(path, '/');
	      if (entry)
		*(entry - 1) = '/';
	      if (ptr)
		{
		  *ptr = '\0';
		  entry = ptr + 1;
		}
	      else
		entry = path;

	      DPRINTF(E_SPAM, L_SCAN, "Playlist entry is now %s\n", entry);
	      ret = db_files_get_count_bymatch(entry);

	    } while (ptr && (ret > 1));

	  if (ret > 0)
	    {
	      mfi_id = db_file_id_bymatch(entry);
	      DPRINTF(E_DBG, L_SCAN, "Found playlist entry match, id is %d, entry is %s\n", mfi_id, entry);

	      filename = db_file_path_byid(mfi_id);
	      if (!filename)
		{
		  DPRINTF(E_LOG, L_SCAN, "Playlist entry %s matches file id %d, but file path is missing.\n", entry, mfi_id);

		  continue;
		}
	    }
	  else
	    {
	      DPRINTF(E_DBG, L_SCAN, "No match for playlist entry %s\n", entry);

	      continue;
	    }
	}

      ret = db_pl_add_item_bypath(pl_id, filename);
      if (ret < 0)
	DPRINTF(E_WARN, L_SCAN, "Could not add %s to playlist\n", filename);

      /* Clean up in preparation for next item */
      extinf = 0;
      free_mfi(&mfi, 1);
      free(filename);
    }

  /* We had some extinf that we never got to use, free it now */
  if (extinf)
    free_mfi(&mfi, 1);

  if (!feof(fp))
    {
      DPRINTF(E_LOG, L_SCAN, "Error reading playlist '%s': %s\n", file, strerror(errno));

      fclose(fp);
      return;
    }

  fclose(fp);

  DPRINTF(E_INFO, L_SCAN, "Done processing playlist\n");
}
示例#4
0
static int
scrobble(int id)
{
  struct media_file_info *mfi;
  struct keyval *kv;
  char duration[4];
  char trackNumber[4];
  char timestamp[16];
  int ret;

  mfi = db_file_fetch_byid(id);
  if (!mfi)
    {
      DPRINTF(E_LOG, L_LASTFM, "Scrobble failed, track id %d is unknown\n", id);
      return -1;
    }

  // Don't scrobble songs which are shorter than 30 sec
  if (mfi->song_length < 30000)
    goto noscrobble;

  // Don't scrobble non-music and radio stations
  if ((mfi->media_kind != MEDIA_KIND_MUSIC) || (mfi->data_kind == DATA_KIND_URL))
    goto noscrobble;

  // Don't scrobble songs with unknown artist
  if (strcmp(mfi->artist, "Unknown artist") == 0)
    goto noscrobble;

  kv = keyval_alloc();
  if (!kv)
    goto noscrobble;

  snprintf(duration, sizeof(duration), "%" PRIu32, mfi->song_length);
  snprintf(trackNumber, sizeof(trackNumber), "%" PRIu32, mfi->track);
  snprintf(timestamp, sizeof(timestamp), "%" PRIi64, (int64_t)time(NULL));

  ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) &&
          (keyval_add(kv, "sk", lastfm_session_key) == 0) &&
          (keyval_add(kv, "artist", mfi->artist) == 0) &&
          (keyval_add(kv, "track", mfi->title) == 0) &&
          (keyval_add(kv, "album", mfi->album) == 0) &&
          (keyval_add(kv, "albumArtist", mfi->album_artist) == 0) &&
          (keyval_add(kv, "duration", duration) == 0) &&
          (keyval_add(kv, "trackNumber", trackNumber) == 0) &&
          (keyval_add(kv, "timestamp", timestamp) == 0)
        );

  free_mfi(mfi, 0);

  if (!ret)
    {
      keyval_clear(kv);
      free(kv);
      return -1;
    }

  DPRINTF(E_INFO, L_LASTFM, "Scrobbling '%s' by '%s'\n", keyval_get(kv, "track"), keyval_get(kv, "artist"));

  ret = request_post("track.scrobble", kv, 0);

  keyval_clear(kv);
  free(kv);

  return ret;

 noscrobble:
  free_mfi(mfi, 0);

  return -1;
}
void
scan_playlist(const char *file, time_t mtime, int dir_id)
{
  FILE *fp;
  struct media_file_info mfi;
  struct playlist_info *pli;
  struct stat sb;
  char buf[PATH_MAX];
  char *path;
  const char *filename;
  char *ptr;
  size_t len;
  int extinf;
  int pl_id;
  int pl_format;
  int ntracks;
  int nadded;
  int ret;

  ptr = strrchr(file, '.');
  if (!ptr)
    return;

  if (strcasecmp(ptr, ".m3u") == 0)
    pl_format = PLAYLIST_M3U;
  else if (strcasecmp(ptr, ".pls") == 0)
    pl_format = PLAYLIST_PLS;
  else
    return;

  filename = filename_from_path(file);

  /* Fetch or create playlist */
  pli = db_pl_fetch_bypath(file);
  if (pli)
    {
      db_pl_ping(pli->id);

      if (mtime && (pli->db_timestamp >= mtime))
	{
	  DPRINTF(E_LOG, L_SCAN, "Unchanged playlist found, not processing '%s'\n", file);

	  // Protect this playlist's radio stations from purge after scan
	  db_pl_ping_items_bymatch("http://", pli->id);
	  free_pli(pli, 0);
	  return;
	}

      DPRINTF(E_LOG, L_SCAN, "Modified playlist found, processing '%s'\n", file);

      pl_id = pli->id;
      db_pl_clear_items(pl_id);
    }
  else
    {
      DPRINTF(E_LOG, L_SCAN, "New playlist found, processing '%s'\n", file);

      CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info)));

      pli->type = PL_PLAIN;

      /* Get only the basename, to be used as the playlist title */
      pli->title = strip_extension(filename);

      pli->path = strdup(file);
      snprintf(buf, sizeof(buf), "/file:%s", file);
      pli->virtual_path = strip_extension(buf);

      pli->directory_id = dir_id;

      ret = db_pl_add(pli, &pl_id);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_SCAN, "Error adding playlist '%s'\n", file);

	  free_pli(pli, 0);
	  return;
	}

      DPRINTF(E_INFO, L_SCAN, "Added new playlist as id %d\n", pl_id);
    }

  free_pli(pli, 0);

  ret = stat(file, &sb);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno));
      return;
    }

  fp = fopen(file, "r");
  if (!fp)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not open playlist '%s': %s\n", file, strerror(errno));
      return;
    }

  db_transaction_begin();

  extinf = 0;
  memset(&mfi, 0, sizeof(struct media_file_info));
  ntracks = 0;
  nadded = 0;

  while (fgets(buf, sizeof(buf), fp) != NULL)
    {
      len = strlen(buf);

      /* rtrim and check that length is sane (ignore blank lines) */
      while ((len > 0) && isspace(buf[len - 1]))
	{
	  len--;
	  buf[len] = '\0';
	}
      if (len < 1)
	continue;

      /* Saves metadata in mfi if EXTINF metadata line */
      if ((pl_format == PLAYLIST_M3U) && extinf_get(buf, &mfi, &extinf))
	continue;

      /* For pls files we are only interested in the part after the FileX= entry */
      path = NULL;
      if ((pl_format == PLAYLIST_PLS) && (strncasecmp(buf, "file", strlen("file")) == 0))
	path = strchr(buf, '=') + 1;
      else if (pl_format == PLAYLIST_M3U)
	path = buf;

      if (!path)
	continue;

      /* Check that first char is sane for a path */
      if ((!isalnum(path[0])) && (path[0] != '/') && (path[0] != '.'))
	continue;

      /* Check if line is an URL, will be added to library, otherwise it should already be there */
      if (strncasecmp(path, "http://", 7) == 0)
	ret = process_url(pl_id, path, sb.st_mtime, extinf, &mfi);
      else
	ret = process_regular_file(pl_id, path);

      ntracks++;
      if (ntracks % 200 == 0)
	{
	  DPRINTF(E_LOG, L_SCAN, "Processed %d items...\n", ntracks);
	  db_transaction_end();
	  db_transaction_begin();
	}

      if (ret == 0)
	nadded++;

      /* Clean up in preparation for next item */
      extinf = 0;
      free_mfi(&mfi, 1);
    }

  db_transaction_end();

  /* We had some extinf that we never got to use, free it now */
  if (extinf)
    free_mfi(&mfi, 1);

  if (!feof(fp))
    DPRINTF(E_LOG, L_SCAN, "Error reading playlist '%s' (only added %d tracks): %s\n", file, nadded, strerror(errno));
  else
    DPRINTF(E_LOG, L_SCAN, "Done processing playlist, added/modified %d items\n", nadded);

  fclose(fp);
}
static int
process_track_file(plist_t trk)
{
  struct media_file_info *mfi;
  char *location;
  char *path;
  char *string;
  uint64_t integer;
  char **strval;
  uint32_t *intval;
  char *chrval;
  uint8_t boolean;
  int mfi_id;
  int i;
  int ret;

  ret = get_dictval_string_from_key(trk, "Location", &location);
  if ((ret < 0) || !location)
    {
      DPRINTF(E_LOG, L_SCAN, "Track type File with no Location\n");
      return -1;
    }

  if (strncmp(location, "file://", strlen("file://")) != 0)
    {
      DPRINTF(E_LOG, L_SCAN, "Track type File, but Location does not start with 'file://': '%s'\n", location);
      free(location);
      return -1;
    }

  path = evhttp_decode_uri(location + strlen("file://"));
  free(location);

  mfi_id = mfi_id_find(path);
  if (mfi_id <= 0)
    {
      free(path);
      return -1;
    }

  free(path);

  if (!cfg_getbool(cfg_getsec(cfg, "library"), "itunes_overrides"))
    return mfi_id;

  /* Override our metadata with what's provided by iTunes */
  mfi = db_file_fetch_byid(mfi_id);
  if (!mfi)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not retrieve file info for file id %d\n", mfi_id);

      return mfi_id;
    }

  for (i = 0; md_map[i].key != NULL; i++)
    {
      switch (md_map[i].type)
	{
	  case PLIST_UINT:
	    ret = get_dictval_int_from_key(trk, md_map[i].key, &integer);
	    if (ret < 0)
	      break;

	    intval = (uint32_t *) ((char *) mfi + md_map[i].offset);

	    *intval = (uint32_t)integer;
	    break;

	  case PLIST_STRING:
	    ret = get_dictval_string_from_key(trk, md_map[i].key, &string);
	    if (ret < 0)
	      break;

	    strval = (char **) ((char *) mfi + md_map[i].offset);

	    if (*strval)
	      free(*strval);

	    *strval = string;
	    break;

	  case PLIST_BOOLEAN:
	    ret = get_dictval_bool_from_key(trk, md_map[i].key, &boolean);
	    if (ret < 0)
	      break;

	    chrval = (char *) mfi + md_map[i].offset;

	    *chrval = boolean;
	    break;

	  case PLIST_DATE:
	    intval = (uint32_t *) ((char *) mfi + md_map[i].offset);

	    get_dictval_date_from_key(trk, md_map[i].key, intval);
	    break;

	  default:
	    DPRINTF(E_WARN, L_SCAN, "Unhandled metadata type %d\n", md_map[i].type);
	    break;
	}
    }

  /* Set media_kind to 4 (Podcast) if Podcast is true */
  ret = get_dictval_bool_from_key(trk, "Podcast", &boolean); 
  if ((ret == 0) && boolean)
    {
      mfi->media_kind = MEDIA_KIND_PODCAST;
    }

  /* Don't let album_artist set to "Unknown artist" if we've
   * filled artist from the iTunes data in the meantime
   */
  if (strcmp(mfi->album_artist, "Unknown artist") == 0)
    {
      free(mfi->album_artist);
      mfi->album_artist = strdup(mfi->artist);
    }

  db_file_update(mfi);

  free_mfi(mfi, 0);

  return mfi_id;
}
示例#7
0
void
filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi)
{
  struct media_file_info *mfi;
  char *filename;
  time_t stamp;
  int id;
  int ret;

  filename = strrchr(path, '/');
  if ((!filename) || (strlen(filename) == 1))
    filename = path;
  else
    filename++;

  db_file_stamp_bypath(path, &stamp, &id);

  if (stamp && (stamp >= mtime))
    {
      db_file_ping(id);
      return;
    }

  if (!external_mfi)
    {
      mfi = (struct media_file_info*)malloc(sizeof(struct media_file_info));
      if (!mfi)
	{
	  DPRINTF(E_LOG, L_SCAN, "Out of memory for mfi\n");
	  return;
	}

      memset(mfi, 0, sizeof(struct media_file_info));
    }
  else
    mfi = external_mfi;

  if (stamp)
    mfi->id = db_file_id_bypath(path);

  mfi->fname = strdup(filename);
  if (!mfi->fname)
    {
      DPRINTF(E_LOG, L_SCAN, "Out of memory for fname\n");
      goto out;
    }

  mfi->path = strdup(path);
  if (!mfi->path)
    {
      DPRINTF(E_LOG, L_SCAN, "Out of memory for path\n");
      goto out;
    }

  mfi->time_modified = mtime;
  mfi->file_size = size;

  if (type & F_SCAN_TYPE_FILE)
    {
      mfi->data_kind = 0; /* real file */
      ret = scan_metadata_ffmpeg(path, mfi);
    }
  else if (type & F_SCAN_TYPE_URL)
    {
      mfi->data_kind = 1; /* url/stream */
#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13)
      ret = scan_metadata_ffmpeg(path, mfi);
#else
      ret = scan_metadata_icy(path, mfi);
#endif
    }
  else if (type & F_SCAN_TYPE_SPOTIFY)
    {
      mfi->data_kind = 2; /* iTunes has no spotify data kind, but we use 2 */
      ret = mfi->artist && mfi->album && mfi->title;
    }
  else if (type & F_SCAN_TYPE_PIPE)
    {
      mfi->data_kind = 3; /* iTunes has no pipe data kind, but we use 3 */
      mfi->type = strdup("wav");
      mfi->codectype = strdup("wav");
      mfi->description = strdup("PCM16 pipe");
      ret = 1;
    }
  else
    {
      DPRINTF(E_LOG, L_SCAN, "Unknown scan type for %s, this error should not occur\n", path);
      ret = -1;
    }

  if (ret < 0)
    {
      DPRINTF(E_INFO, L_SCAN, "Could not extract metadata for %s\n", path);
      goto out;
    }

  if (type & F_SCAN_TYPE_COMPILATION)
    mfi->compilation = 1;
  if (type & F_SCAN_TYPE_PODCAST)
    mfi->media_kind = 4; /* podcast */
  if (type & F_SCAN_TYPE_AUDIOBOOK)
    mfi->media_kind = 8; /* audiobook */

  if (!mfi->item_kind)
    mfi->item_kind = 2; /* music */
  if (!mfi->media_kind)
    mfi->media_kind = 1; /* music */

  unicode_fixup_mfi(mfi);

  fixup_tags(mfi);

  if (mfi->id == 0)
    db_file_add(mfi);
  else
    db_file_update(mfi);

 out:
  if (!external_mfi)
    free_mfi(mfi, 0);
}
示例#8
0
static void
process_media_file(char *file, time_t mtime, off_t size, int compilation)
{
  struct media_file_info mfi;
  char *filename;
  char *ext;
  time_t stamp;
  int id;
  int ret;

  db_file_stamp_bypath(file, &stamp, &id);

  if (stamp >= mtime)
    {
      db_file_ping(id);
      return;
    }

  memset(&mfi, 0, sizeof(struct media_file_info));

  if (stamp)
    mfi.id = db_file_id_bypath(file);

  filename = strrchr(file, '/');
  if (!filename)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not determine filename for %s\n", file);

      return;
    }

  mfi.fname = strdup(filename + 1);
  if (!mfi.fname)
    {
      DPRINTF(E_WARN, L_SCAN, "Out of memory for fname\n");

      return;
    }

  mfi.path = strdup(file);
  if (!mfi.path)
    {
      DPRINTF(E_WARN, L_SCAN, "Out of memory for path\n");

      free(mfi.fname);
      return;
    }

  mfi.time_modified = mtime;
  mfi.file_size = size;

  ret = -1;

  /* Special cases */
  ext = strrchr(file, '.');
  if (ext)
    {
      if ((strcmp(ext, ".pls") == 0)
	  || (strcmp(ext, ".url") == 0))
	{
	  mfi.data_kind = 1; /* url/stream */

	  ret = scan_url_file(file, &mfi);
	  if (ret < 0)
	    goto out;
	}
      else if ((strcmp(ext, ".png") == 0)
	       || (strcmp(ext, ".jpg") == 0))
	{
	  /* Artwork - don't scan */
	  goto out;
	}
    }

  /* General case */
  if (ret < 0)
    {
      ret = scan_metadata_ffmpeg(file, &mfi);
      mfi.data_kind = 0; /* real file */
    }

  if (ret < 0)
    {
      DPRINTF(E_INFO, L_SCAN, "Could not extract metadata for %s\n", file);

      goto out;
    }

  mfi.compilation = compilation;

  if (!mfi.item_kind)
    mfi.item_kind = 2; /* music */
  if (!mfi.media_kind)
    mfi.media_kind = 1; /* music */

  unicode_fixup_mfi(&mfi);

  fixup_tags(&mfi);

  if (mfi.id == 0)
    db_file_add(&mfi);
  else
    db_file_update(&mfi);

 out:
  free_mfi(&mfi, 1);
}
示例#9
0
void
filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi, int dir_id)
{
  struct media_file_info *mfi;
  char *filename;
  time_t stamp;
  int id;
  char virtual_path[PATH_MAX];
  int ret;

  filename = strrchr(path, '/');
  if ((!filename) || (strlen(filename) == 1))
    filename = path;
  else
    filename++;

  db_file_stamp_bypath(path, &stamp, &id);

  if (stamp && (stamp >= mtime))
    {
      db_file_ping(id);
      return;
    }

  if (!external_mfi)
    {
      mfi = (struct media_file_info*)malloc(sizeof(struct media_file_info));
      if (!mfi)
	{
	  DPRINTF(E_LOG, L_SCAN, "Out of memory for mfi\n");
	  return;
	}

      memset(mfi, 0, sizeof(struct media_file_info));
    }
  else
    mfi = external_mfi;

  if (stamp)
    mfi->id = db_file_id_bypath(path);

  mfi->fname = strdup(filename);
  if (!mfi->fname)
    {
      DPRINTF(E_LOG, L_SCAN, "Out of memory for fname\n");
      goto out;
    }

  mfi->path = strdup(path);
  if (!mfi->path)
    {
      DPRINTF(E_LOG, L_SCAN, "Out of memory for path\n");
      goto out;
    }

  mfi->time_modified = mtime;
  mfi->file_size = size;

  if (type & F_SCAN_TYPE_COMPILATION)
    mfi->compilation = 1;
  if (type & F_SCAN_TYPE_PODCAST)
    mfi->media_kind = MEDIA_KIND_PODCAST; /* podcast */
  if (type & F_SCAN_TYPE_AUDIOBOOK)
    mfi->media_kind = MEDIA_KIND_AUDIOBOOK; /* audiobook */

  if (type & F_SCAN_TYPE_FILE)
    {
      mfi->data_kind = DATA_KIND_FILE;
      ret = scan_metadata_ffmpeg(path, mfi);
    }
  else if (type & F_SCAN_TYPE_URL)
    {
      mfi->data_kind = DATA_KIND_HTTP;
      ret = scan_metadata_ffmpeg(path, mfi);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_SCAN, "Playlist URL is unavailable for probe/metadata, assuming MP3 encoding\n");
	  mfi->type = strdup("mp3");
	  mfi->codectype = strdup("mpeg");
	  mfi->description = strdup("MPEG audio file");
	  ret = 1;
	}
    }
  else if (type & F_SCAN_TYPE_SPOTIFY)
    {
      mfi->data_kind = DATA_KIND_SPOTIFY;
      ret = mfi->artist && mfi->album && mfi->title;
    }
  else if (type & F_SCAN_TYPE_PIPE)
    {
      mfi->data_kind = DATA_KIND_PIPE;
      mfi->type = strdup("wav");
      mfi->codectype = strdup("wav");
      mfi->description = strdup("PCM16 pipe");
      ret = 1;
    }
  else
    {
      DPRINTF(E_LOG, L_SCAN, "Unknown scan type for %s, this error should not occur\n", path);
      ret = -1;
    }

  if (ret < 0)
    {
      DPRINTF(E_INFO, L_SCAN, "Could not extract metadata for %s\n", path);
      goto out;
    }

  if (!mfi->item_kind)
    mfi->item_kind = 2; /* music */
  if (!mfi->media_kind)
    mfi->media_kind = MEDIA_KIND_MUSIC; /* music */

  unicode_fixup_mfi(mfi);

  fixup_tags(mfi);

  if (type & F_SCAN_TYPE_URL)
    {
      snprintf(virtual_path, PATH_MAX, "/http:/%s", mfi->title);
      mfi->virtual_path = strdup(virtual_path);
    }
  else if (type & F_SCAN_TYPE_SPOTIFY)
    {
      snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", mfi->album_artist, mfi->album, mfi->title);
      mfi->virtual_path = strdup(virtual_path);
    }
  else
    {
      snprintf(virtual_path, PATH_MAX, "/file:%s", mfi->path);
      mfi->virtual_path = strdup(virtual_path);
    }

  mfi->directory_id = dir_id;

  if (mfi->id == 0)
    db_file_add(mfi);
  else
    db_file_update(mfi);

 out:
  if (!external_mfi)
    free_mfi(mfi, 0);
}