/* 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; }
/* 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"); }
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; }
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); }
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); }
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); }