/* * A very simple despotify client, to show how the library is used. * * $Id: simple.c 514 2010-12-13 22:15:51Z dstien $ * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aes.c" #include "audio.h" #include "despotify.h" #include "channel.h" #include "util.h" static int listen_fd = -1; static int client_fd = -1; static void* audio_device; char *wrapper_read_command(void); int wrapper_listen(int port); void wrapper_wprintf(wchar_t *fmt, ...); static int simple_aes_callback(CHANNEL* ch, unsigned char* buf, unsigned short len); /****** PCM thread stuff: ***********/ static pthread_t thread; static pthread_mutex_t thread_mutex; static pthread_cond_t thread_cond; static enum { PAUSE, PLAY, EXIT } play_state = PAUSE; static void thread_play(void) { pthread_mutex_lock(&thread_mutex); play_state = PLAY; pthread_cond_signal(&thread_cond); pthread_mutex_unlock(&thread_mutex); } static void thread_pause(void) { pthread_mutex_lock(&thread_mutex); play_state = PAUSE; pthread_mutex_unlock(&thread_mutex); } static void thread_exit(void) { pthread_mutex_lock(&thread_mutex); play_state = EXIT; pthread_cond_signal(&thread_cond); pthread_mutex_unlock(&thread_mutex); pthread_join(thread, NULL); } static void* thread_loop(void* arg) { struct despotify_session* ds = arg; struct pcm_data pcm; pthread_mutex_init(&thread_mutex, NULL); pthread_cond_init(&thread_cond, NULL); bool loop = true; while (loop) { switch (play_state) { case PAUSE: pthread_mutex_lock(&thread_mutex); pthread_cond_wait(&thread_cond, &thread_mutex); pthread_mutex_unlock(&thread_mutex); break; case PLAY: { int rc = despotify_get_pcm(ds, &pcm); if (rc == 0) audio_play_pcm(audio_device, &pcm); else { wrapper_wprintf(L"despotify_get_pcm() returned error %d\n", rc); exit(-1); } break; } case EXIT: loop = false; break; } } pthread_cond_destroy(&thread_cond); pthread_mutex_destroy(&thread_mutex); return NULL; } /**************** UI (main) thread stuff: ***************/ struct playlist* get_playlist(struct playlist* rootlist, int num) { struct playlist* p = rootlist; if (!p) { wrapper_wprintf(L"Stored lists not loaded. Run 'list' without parameter to load.\n"); } else { /* skip to playlist number */ for (int i = 1; i < num && p; i++) p = p->next; if (!p) wrapper_wprintf(L"Invalid playlist number %d\n", num); } return p; } void print_list_of_lists(struct playlist* rootlist) { if (!rootlist) { wrapper_wprintf(L" \n"); } else { int count=1; for (struct playlist* p = rootlist; p; p = p->next) wrapper_wprintf(L"%2d: %-40s %3d %c %s\n", count++, p->name, p->num_tracks, p->is_collaborative ? '*' : ' ', p->author); } } void print_tracks(struct track* head) { if (!head) { wrapper_wprintf(L" \n"); return; } int count = 1; for (struct track* t = head; t; t = t->next) { if (t->has_meta_data) { wrapper_wprintf(L"%3d: %-40s %2d:%02d -|- tid: %s fid: %s br: %d key: %s", count++, t->title, t->length / 60000, t->length % 60000 / 1000, t->track_id, t->file_id, t->file_bitrate, t->key); for (struct artist* a = t->artist; a; a = a->next) wrapper_wprintf(L"%s%s", a->name, a->next ? ", " : ""); wrapper_wprintf(L" %s\n", t->playable ? "" : "(Unplayable)"); } else wrapper_wprintf(L"%3d: N/A\n", count++); } } void print_track_full(struct track* t) { if(t->has_meta_data) { wrapper_wprintf(L"\nTitle: %s\nAlbum: %s\nArtist(s): ", t->title, t->album); for (struct artist* a = t->artist; a; a = a->next) wrapper_wprintf(L"%s%s", a->name, a->next ? ", " : ""); wrapper_wprintf(L"\nYear: %d\nLength: %02d:%02d\n\n", t->year, t->length / 60000, t->length % 60000 / 1000); } else { wrapper_wprintf(L" \n"); } } void print_album(struct album_browse* a) { wrapper_wprintf(L"\nName: %s\nYear: %d\n", a->name, a->year); print_tracks(a->tracks); } void print_artist(struct artist_browse* a) { wrapper_wprintf(L"\nName: %s\n" "Genres: %s\n" "Years active: %s\n" "%d albums:\n", a->name, a->genres, a->years_active, a->num_albums); for (struct album_browse* al = a->albums; al; al = al->next) wrapper_wprintf(L" %s (%d)\n", al->name, al->year); } void print_playlist(struct playlist* pls) { wrapper_wprintf(L"\nName: %s\nAuthor: %s\n", pls->name, pls->author); print_tracks(pls->tracks); } void print_search(struct search_result *search) { if (search->suggestion[0]) wrapper_wprintf(L"\nDid you mean \"%s\"?\n", search->suggestion); if (search->total_artists > 0) { wrapper_wprintf(L"\nArtists found (%d):\n", search->total_artists); for (struct artist* artist = search->artists; artist; artist = artist->next) wrapper_wprintf(L" %s -|- %s\n", artist->name, artist->id); } if (search->total_albums > 0) { wrapper_wprintf(L"\nAlbums found (%d):\n", search->total_albums); for (struct album* album = search->albums; album; album = album->next) wrapper_wprintf(L" %s -|- %s\n", album->name, album->id); } if (search->total_tracks > 0) { wrapper_wprintf(L"\nTracks found (%d/%d):\n", search->playlist->num_tracks, search->total_tracks); print_tracks(search->tracks); } } void print_info(struct despotify_session* ds) { struct user_info* user = ds->user_info; wrapper_wprintf(L"Username : %s\n", user->username); wrapper_wprintf(L"Country : %s\n", user->country); wrapper_wprintf(L"Account type : %s\n", user->type); wrapper_wprintf(L"Account expiry : %s", ctime(&user->expiry)); wrapper_wprintf(L"Host : %s:%d\n", user->server_host, user->server_port); wrapper_wprintf(L"Last ping : %s", ctime(&user->last_ping)); if (strncmp(user->type, "premium", 7)) { wrapper_wprintf(L"\n=================================================\n" " N O T I C E\n" " You do not have a premium account.\n" " Spotify services will not be available.\n" "=================================================\n"); } } void print_help(void) { wrapper_wprintf(L"\nAvailable commands:\n\n" "list [num] List stored playlists\n" "rename [num] [string] Rename playlist\n" "collab [num] Toggle playlist collaboration\n" "\n" "search [string] Search for [string] or get next 100 results\n" "artist [num] Show information about artist for track [num]\n" "album [num] List album for track [num]\n" "uri [string] Display info about Spotify URI\n" "portrait [num] Save artist portrait to portrait.jpg\n" "cover [num] Save album cover art to cover.jpg\n" "\n" "play [num] Play track [num] in the last viewed list\n" "playalbum [num] Play album for track [num]\n" "next Play next track in the current list\n" "stop, pause, resume Control playback\n" "\n" "info Details about your account and current connection\n" "help This text\n" "quit Quit\n"); } /* called by channel */ static int simple_aes_callback(CHANNEL* ch, unsigned char* buf, unsigned short len) { wrapper_wprintf (L"got callback"); if (ch->state == CHANNEL_DATA) { wrapper_wprintf (L"w1\n"); struct despotify_session* ds = ch->private; struct track* t = ds->track; wrapper_wprintf (L"w2\n"); /*if (t->key) DSFYfree(t->key);*/ wrapper_wprintf (L"w3\n"); t->key = malloc(len); memcpy(t->key, buf, len); /* Expand file key */ rijndaelKeySetupEnc (ds->aes.state, t->key, 128); wrapper_wprintf (L"w4\n"); /* Set initial IV */ memcpy(ds->aes.IV, "\x72\xe0\x67\xfb\xdd\xcb\xcf\x77" "\xeb\xe8\xbc\x64\x3f\x63\x0d\x93", 16); char foo[1024]; hex_bytes_to_ascii(t->key,foo,len); wrapper_wprintf (L"Got AES key %s - %d\n",foo,len); wrapper_wprintf (L"w5\n"); /* Add SND_CMD_START to buffer chain, with a copy of the track struct */ struct track* copy = malloc(sizeof(struct track)); memcpy(copy, ds->track, sizeof(struct track)); // snd_ioctl(ds, SND_CMD_START, copy, 0); } return 0; } void command_loop(struct despotify_session* ds) { bool loop = true; char *buf; struct playlist* rootlist = NULL; struct playlist* searchlist = NULL; struct playlist* lastlist = NULL; struct search_result *search = NULL; struct album_browse* playalbum = NULL; print_help(); do { wprintf(L"\n> "); fflush(stdout); if((buf = wrapper_read_command()) == NULL) break; /* list */ if (!strncmp(buf, "list", 4)) { int num = 0; if(strlen(buf) > 5) num = atoi(buf + 5); if (num) { struct playlist* p = get_playlist(rootlist, num); if (p) { print_tracks(p->tracks); lastlist = p; } } else { if (!rootlist) rootlist = despotify_get_stored_playlists(ds); print_list_of_lists(rootlist); } } /* rename */ else if (!strncmp(buf, "rename", 6)) { int num = 0; char *name = 0; if (strlen(buf) > 9) { num = atoi(buf + 7); name = strchr(buf + 7, ' ') + 1; } if (num && name && name[0]) { struct playlist* p = get_playlist(rootlist, num); if (p) { if (despotify_rename_playlist(ds, p, name)) wrapper_wprintf(L"Renamed playlist %d to \"%s\".\n", num, name); else wrapper_wprintf(L"Rename failed: %s\n", despotify_get_error(ds)); } } else wrapper_wprintf(L"Usage: rename [num] [string]\n"); } /* collab */ else if (!strncmp(buf, "collab", 6)) { int num = 0; if (strlen(buf) > 7) num = atoi(buf + 7); if (num) { struct playlist* p = get_playlist(rootlist, num); if (p) { if (despotify_set_playlist_collaboration(ds, p, !p->is_collaborative)) wrapper_wprintf(L"Changed playlist %d collaboration to %s.\n", num, p->is_collaborative ? "ON" : "OFF"); else wrapper_wprintf(L"Setting playlist collaboration state failed: %s\n", despotify_get_error(ds)); } } else wrapper_wprintf(L"Usage: collab [num]\n"); } /* search */ else if (!strncmp(buf, "search", 6)) { if (buf[7]) { thread_pause(); despotify_stop(ds); /* since we replace the list */ if (search) despotify_free_search(search); search = despotify_search(ds, buf + 7, MAX_SEARCH_RESULTS); if (!search) { wrapper_wprintf(L"Search failed: %s\n", despotify_get_error(ds)); continue; } searchlist = search->playlist; } else if (searchlist && (searchlist->num_tracks < search->total_tracks)) if (!despotify_search_more(ds, search, searchlist->num_tracks, MAX_SEARCH_RESULTS)) { wrapper_wprintf(L"Search failed: %s\n", despotify_get_error(ds)); continue; } if (searchlist) { print_search(search); lastlist = searchlist; } else wrapper_wprintf(L"No previous search\n"); } /* artist */ else if (!strncmp(buf, "artist", 6)) { int num = 0; if(strlen(buf) > 7) num = atoi(buf + 7); if (!num) { wrapper_wprintf(L"usage: artist [num]\n"); continue; } if (!lastlist) { wrapper_wprintf(L"No playlist\n"); continue; } /* find the requested track */ struct track* t = lastlist->tracks; for (int i=1; inext; for (struct artist* aptr = t->artist; aptr; aptr = aptr->next) { struct artist_browse* a = despotify_get_artist(ds, aptr->id); print_artist(a); despotify_free_artist_browse(a); } } else if (!strncmp(buf, "albumid", 7)) { wrapper_wprintf(L"searching for id %s\n", buf+8); struct album_browse* a = despotify_get_album(ds, buf+8); if (a) { print_album(a); despotify_free_album_browse(a); } else wrapper_wprintf(L"Got no album for id %s\n", buf+8); } else if (!strncmp(buf, "key", 3)) { char k_track[1024]; char k_file[1024]; if (sscanf (buf, "key %32s %40s", k_track, k_file) == 2) { k_file[40] = k_track[32] = 0; if (strlen (k_file) != 40 || strlen (k_track) != 32) { wrapper_wprintf (L"501 0 WARN File ID must be provided in hex as 40 character and track ID must be provided in hex as 32 characters\n"); } wrapper_wprintf(L"requesting key for %s|%s\n", k_track,k_file); struct track* t = despotify_get_track(ds, k_track); char fid[20], tid[16]; hex_ascii_to_bytes(k_file, fid, sizeof fid); hex_ascii_to_bytes(k_track, tid, sizeof tid); ds->track = t; int error = cmd_aeskey(ds->session, fid, tid, simple_aes_callback, ds); if (error) { wrapper_wprintf(L"cmd_aeskey() failed for %s\n", t->title); return; } wrapper_wprintf (L"key is: %s",t->key); }else { wrapper_wprintf(L"foo"); } } /* album */ else if (!strncmp(buf, "album", 5)) { int num = 0; if(strlen(buf) > 6) num = atoi(buf + 6); if (!num) { wrapper_wprintf(L"usage: album [num]\n"); continue; } if (!lastlist) { wrapper_wprintf(L"No playlist\n"); continue; } /* find the requested track */ struct track* t = lastlist->tracks; for (int i=1; inext; if (t) { struct album_browse* a = despotify_get_album(ds, t->album_id); if (a) { print_album(a); despotify_free_album_browse(a); } else wrapper_wprintf(L"Got no album for id %s\n", t->album_id); } } /* playalbum */ else if (!strncmp(buf, "playalbum", 9)) { int num = 0; if(strlen(buf) > 10) num = atoi(buf + 10); if (!num) { wrapper_wprintf(L"usage: playalbum [num]\n"); continue; } if (!lastlist) { wrapper_wprintf(L"No playlist\n"); continue; } /* find the requested track */ struct track* t = lastlist->tracks; for (int i=1; inext; if (t) { thread_pause(); despotify_stop(ds); if (playalbum) despotify_free_album_browse(playalbum); playalbum = despotify_get_album(ds, t->album_id); if (playalbum) { despotify_play(ds, playalbum->tracks, true); thread_play(); } else wrapper_wprintf(L"Got no album for id %s\n", t->album_id); } } /* uri */ else if (!strncmp(buf, "uri", 3)) { char *uri = buf + 4; if(strlen(uri) == 0) { wrapper_wprintf(L"usage: info \n"); continue; } struct link* link = despotify_link_from_uri(uri); struct album_browse* al; struct artist_browse* ar; struct playlist* pls; struct search_result* s; struct track* t; switch(link->type) { case LINK_TYPE_ALBUM: al = despotify_link_get_album(ds, link); if(al) { print_album(al); despotify_free_album_browse(al); } break; case LINK_TYPE_ARTIST: ar = despotify_link_get_artist(ds, link); if(ar) { print_artist(ar); despotify_free_artist_browse(ar); } break; case LINK_TYPE_PLAYLIST: pls = despotify_link_get_playlist(ds, link); if(pls) { print_playlist(pls); despotify_free_playlist(pls); } break; case LINK_TYPE_SEARCH: s = despotify_link_get_search(ds, link); if(s) { print_search(s); despotify_free_search(s); } break; case LINK_TYPE_TRACK: t = despotify_link_get_track(ds, link); if(t) { print_track_full(t); despotify_free_track(t); } break; default: wrapper_wprintf(L"%s is a invalid Spotify URI\n", uri); } despotify_free_link(link); } /* portrait */ else if (!strncmp(buf, "portrait", 8)) { int num = 0; if(strlen(buf) > 9) num = atoi(buf + 9); if (!num) { wrapper_wprintf(L"usage: portrait [num]\n"); continue; } if (!lastlist) { wrapper_wprintf(L"No playlist\n"); continue; } /* find the requested artist */ struct track* t = lastlist->tracks; for (int i=1; inext; struct artist_browse* a = despotify_get_artist(ds, t->artist->id); if (a && a->portrait_id[0]) { int len; void* portrait = despotify_get_image(ds, a->portrait_id, &len); if (portrait && len) { wrapper_wprintf(L"Writing %d bytes into portrait.jpg\n", len); FILE* f = fopen("portrait.jpg", "w"); if (f) { fwrite(portrait, len, 1, f); fclose(f); } free(portrait); } } else wrapper_wprintf(L"Artist %s has no portrait.\n", a->name); despotify_free_artist_browse(a); } /* cover */ else if (!strncmp(buf, "cover", 5)) { int num = 0; if(strlen(buf) > 6) num = atoi(buf + 6); if (!num) { wrapper_wprintf(L"usage: cover [num]\n"); continue; } if (!lastlist) { wrapper_wprintf(L"No playlist\n"); continue; } /* find the requested album */ struct track* t = lastlist->tracks; for (int i=1; inext; if (t) { struct album_browse* a = despotify_get_album(ds, t->album_id); if (a && a->cover_id[0]) { int len; void* cover = despotify_get_image(ds, a->cover_id, &len); if (cover && len) { wrapper_wprintf(L"Writing %d bytes into cover.jpg\n", len); FILE* f = fopen("cover.jpg", "w"); if (f) { fwrite(cover, len, 1, f); fclose(f); } free(cover); } } else wrapper_wprintf(L"Album %s has no cover.\n", t->album); despotify_free_album_browse(a); } } /* play */ else if (!strncmp(buf, "play", 4) || !strncmp(buf, "next", 4)) { if (!lastlist) { wrapper_wprintf(L"No list to play from. Use 'list' or 'search' to select a list.\n"); continue; } /* skip to track , else play next */ struct track* t; if (buf[4]) { int listoffset = atoi(buf + 5); t = lastlist->tracks; for (int i=1; inext; if (t) { despotify_play(ds, t, true); thread_play(); } else wrapper_wprintf(L"Invalid track number %d\n", listoffset); } else { despotify_next(ds); } } /* stop */ else if (!strncmp(buf, "stop", 4)) { thread_pause(); despotify_stop(ds); } /* pause */ else if (!strncmp(buf, "pause", 5)) { thread_pause(); } /* resume */ else if (!strncmp(buf, "resume", 5)) { thread_play(); } /* info */ else if (!strncmp(buf, "info", 4)) { print_info(ds); } /* help */ else if (!strncmp(buf, "help", 4)) { print_help(); } /* quit */ else if (!strncmp(buf, "quit", 4)) { loop = false; } } while(loop); if (rootlist) despotify_free_playlist(rootlist); if (search) despotify_free_search(search); if (playalbum) despotify_free_album_browse(playalbum); } void callback(struct despotify_session* ds, int signal, void* data, void* callback_data) { static int seconds = -1; (void)ds; (void)callback_data; /* don't warn about unused parameters */ switch (signal) { case DESPOTIFY_NEW_TRACK: { struct track* t = data; wrapper_wprintf(L"New track: %s / %s (%d:%02d) %d kbit/s\n", t->title, t->artist->name, t->length / 60000, t->length % 60000 / 1000, t->file_bitrate / 1000); break; } case DESPOTIFY_TIME_TELL: if ((int)(*((double*)data)) != seconds) { seconds = *((double*)data); wrapper_wprintf(L"Time: %d:%02d\r", seconds / 60, seconds % 60); } break; case DESPOTIFY_END_OF_PLAYLIST: wrapper_wprintf(L"End of playlist\n"); thread_pause(); break; } } int main(int argc, char** argv) { setlocale(LC_ALL, ""); if (argc < 3) { wrapper_wprintf(L"Usage: %s [remote control port]\n", argv[0]); return 1; } DSFYDEBUG("$Id: simple.c 514 2010-12-13 22:15:51Z dstien $\n"); if (!despotify_init()) { wrapper_wprintf(L"despotify_init() failed\n"); return 1; } struct despotify_session* ds = despotify_init_client(callback, NULL, true, true); if (!ds) { wrapper_wprintf(L"despotify_init_client() failed\n"); return 1; } pthread_create(&thread, NULL, &thread_loop, ds); if(argc == 4 && wrapper_listen(atoi(argv[3]))) { wrapper_wprintf(L"wrapper_listen() failed to listen on local port %s\n", argv[3]); return 1; } if (!despotify_authenticate(ds, argv[1], argv[2])) { printf( "Authentication failed: %s\n", despotify_get_error(ds)); despotify_exit(ds); return 1; } audio_device = audio_init(); #if 0 { struct track* t = despotify_get_track(ds, "d1b264bb6bcd46be852ceba8ac5e6582"); despotify_play(ds, t, false); thread_play(); while(1) { sleep(1); } } #else print_info(ds); command_loop(ds); #endif thread_exit(); audio_exit(audio_device); despotify_exit(ds); if (!despotify_cleanup()) { wrapper_wprintf(L"despotify_cleanup() failed\n"); return 1; } return 0; } int wrapper_listen(int port) { struct sockaddr_in sin; int value; memset(&sin, 0, sizeof(sin)); sin.sin_family = PF_INET; sin.sin_port = htons(port); sin.sin_addr.s_addr = inet_addr("127.0.0.1"); if((listen_fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) return -1; value = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int)); if(bind(listen_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0 || listen(listen_fd, 1) < 0) { close(listen_fd); listen_fd = -1; return -1; } return 0; } char *wrapper_read_command(void) { static char sock_buf[256] = { 0 }; static char stdin_buf[256] = { 0 }; static int sock_buf_len; static int stdin_buf_len; static char *command = NULL; fd_set rfds; int max_fd = 0; char *ptr; int ret; if(command) { free(command); command = NULL; } for(;;) { FD_ZERO(&rfds); if(isatty(0)) FD_SET(0, &rfds); if(listen_fd != -1 && client_fd == -1) { FD_SET(listen_fd, &rfds); max_fd = listen_fd; } else if(client_fd != -1) { FD_SET(client_fd, &rfds); max_fd = client_fd; } if(select(max_fd + 1, &rfds, NULL, NULL, NULL) < 0) break; if(FD_ISSET(0, &rfds)) { ret = read(0, stdin_buf + stdin_buf_len, sizeof(stdin_buf) - stdin_buf_len - 1); if(ret > 0) { stdin_buf_len += ret; stdin_buf[stdin_buf_len] = 0; } } if(listen_fd >= 0 && FD_ISSET(listen_fd, &rfds)) { client_fd = accept(listen_fd, NULL, NULL); continue; } if(client_fd >= 0 && FD_ISSET(client_fd, &rfds)) { ret = read(client_fd, sock_buf + sock_buf_len, sizeof(sock_buf) - sock_buf_len - 1); if(ret > 0) { sock_buf_len += ret; sock_buf[sock_buf_len] = 0; } else { close(client_fd); client_fd = -1; sock_buf_len = 0; *sock_buf = 0; } } if((ptr = strchr(stdin_buf, '\n')) != NULL) { *ptr++ = 0; if(strlen(stdin_buf)) command = strdup(stdin_buf); stdin_buf_len -= ptr - stdin_buf; memmove(stdin_buf, ptr, stdin_buf_len + 1); if(command) break; } else if((ptr = strchr(sock_buf, '\n')) != NULL) { *ptr++ = 0; if(strlen(sock_buf)) command = strdup(sock_buf); sock_buf_len -= ptr - sock_buf; memmove(sock_buf, ptr, sock_buf_len + 1); if(command) break; } } return command; } void wrapper_wprintf(wchar_t *fmt, ...) { wchar_t wstr[4096]; char mbstr[4096]; char *ptr; va_list ap; int ret, written; va_start(ap, fmt); ret = vswprintf(wstr, sizeof(wstr) - 1, fmt, ap); va_end(ap); if(ret >= (int)sizeof(wstr)) return; wcstombs(mbstr, wstr, sizeof(mbstr) - 1); mbstr[sizeof(mbstr) - 1] = 0; if(isatty(0)) { wprintf(L"%S", wstr); fflush(stdout); } if(client_fd == -1) return; ptr = mbstr; ret = strlen(mbstr); while(ret > 0) { if((written = write(client_fd, ptr, ret)) <= 0) { close(client_fd); client_fd = -1; break; } ptr += written; ret -= written; } }