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