/* get initial station from autostart setting or user input */ static void BarMainGetInitialStation (BarApp_t *app) { /* try to get autostart station */ if (app->settings.autostartStation != NULL) { app->curStation = PianoFindStationById (app->ph.stations, app->settings.autostartStation); if (app->curStation == NULL) { BarUiMsg (&app->settings, MSG_ERR, "Error: Autostart station not found.\n"); } } /* no autostart? ask the user */ if (app->curStation == NULL) { app->curStation = BarUiSelectStation (app, "Select station: ", NULL); } if (app->curStation != NULL) { BarUiPrintStation (&app->settings, app->curStation); } }
/* Print song infos (artist, title, album, loved) * @param pianobar settings * @param the song * @param alternative station info (show real station for quickmix, e.g.) */ void BarUiPrintSong (const BarSettings_t *settings, const PianoSong_t *song, const PianoStation_t *station) { char outstr[512]; const char *vals[] = {song->title, song->artist, song->album, (song->rating == PIANO_RATE_LOVE) ? settings->loveIcon : "", station != NULL ? settings->atIcon : "", station != NULL ? station->name : "", song->detailUrl}; BarUiCustomFormat (outstr, sizeof (outstr), settings->npSongFormat, "talr@su", vals); BarUiAppendNewline (outstr, sizeof (outstr)); BarUiMsg (settings, MSG_PLAYING, "%s", outstr); BarUiCustomFormat(outstr, sizeof(outstr), settings->titleFormat, "talr@su", vals); BarConsoleSetTitle(outstr); }
/* start new player thread */ static void BarMainStartPlayback (BarApp_t *app, pthread_t *playerThread) { assert (app != NULL); assert (playerThread != NULL); const PianoSong_t * const curSong = app->playlist; assert (curSong != NULL); BarUiPrintSong (&app->settings, curSong, app->curStation->isQuickMix ? PianoFindStationById (app->ph.stations, curSong->stationId) : NULL); static const char httpPrefix[] = "http://"; /* avoid playing local files */ if (curSong->audioUrl == NULL || strncmp (curSong->audioUrl, httpPrefix, strlen (httpPrefix)) != 0) { BarUiMsg (&app->settings, MSG_ERR, "Invalid song url.\n"); } else { /* setup player */ memset (&app->player, 0, sizeof (app->player)); app->player.url = curSong->audioUrl; app->player.gain = curSong->fileGain; app->player.settings = &app->settings; app->player.songDuration = curSong->length; pthread_mutex_init (&app->player.pauseMutex, NULL); pthread_cond_init (&app->player.pauseCond, NULL); assert (interrupted == &app->doQuit); interrupted = &app->player.interrupted; /* throw event */ BarUiStartEventCmd (&app->settings, "songstart", app->curStation, curSong, &app->player, app->ph.stations, PIANO_RET_OK, CURLE_OK); /* prevent race condition, mode must _not_ be DEAD if * thread has been started */ app->player.mode = PLAYER_WAITING; /* start player */ pthread_create (playerThread, NULL, BarPlayerThread, &app->player); } }
/* start new player thread */ static void BarMainStartPlayback (BarApp_t *app, pthread_t *playerThread) { BarUiPrintSong (&app->settings, app->playlist, app->curStation->isQuickMix ? PianoFindStationById (app->ph.stations, app->playlist->stationId) : NULL); if (app->playlist->audioUrl == NULL) { BarUiMsg (&app->settings, MSG_ERR, "Invalid song url.\n"); } else { /* setup player */ memset (&app->player, 0, sizeof (app->player)); WaitressInit (&app->player.waith); WaitressSetUrl (&app->player.waith, app->playlist->audioUrl); /* set up global proxy, player is NULLed on songfinish */ if (app->settings.proxy != NULL) { WaitressSetProxy (&app->player.waith, app->settings.proxy); } app->player.gain = app->playlist->fileGain; if ( !app->mute ) { app->player.scale = BarPlayerCalcScale (app->player.gain + app->settings.volume); } app->player.audioFormat = app->playlist->audioFormat; app->player.settings = &app->settings; pthread_mutex_init (&app->player.pauseMutex, NULL); pthread_cond_init (&app->player.pauseCond, NULL); /* throw event */ BarUiStartEventCmd (&app->settings, "songstart", app->curStation, app->playlist, &app->player, app->ph.stations, PIANO_RET_OK, WAITRESS_RET_OK); BarDownloadStart(app); /* prevent race condition, mode must _not_ be FREED if * thread has been started */ app->player.mode = PLAYER_STARTING; /* start player */ pthread_create (playerThread, NULL, BarPlayerThread, &app->player); } }
/* setup libao */ static bool openDevice (player_t * const player) { AVCodecContext * const cctx = player->st->codec; ao_sample_format aoFmt; memset (&aoFmt, 0, sizeof (aoFmt)); aoFmt.bits = av_get_bytes_per_sample (avformat) * 8; assert (aoFmt.bits > 0); aoFmt.channels = cctx->channels; aoFmt.rate = cctx->sample_rate; aoFmt.byte_format = AO_FMT_NATIVE; int driver = ao_default_driver_id (); if ((player->aoDev = ao_open_live (driver, &aoFmt, NULL)) == NULL) { BarUiMsg (player->settings, MSG_ERR, "Cannot open audio device.\n"); return false; } return true; }
static int _BarFlyTagFetchCover(uint8_t** cover_art, size_t* cover_size, char const* url, BarSettings_t const* settings) { int exit_status = 0; int status; uint8_t* tmp_cover_art = NULL; size_t tmp_cover_size; assert(cover_art != NULL); assert(cover_size != NULL); assert(url != NULL); assert(settings != NULL); /* * Fetch the cover art. */ status = _BarFlyFetchURL(url, &tmp_cover_art, &tmp_cover_size, settings); if (status != 0) { BarUiMsg(settings, MSG_ERR, "Could not get the cover art.\n"); goto error; } *cover_art = tmp_cover_art; tmp_cover_art = NULL; *cover_size = tmp_cover_size; goto end; error: exit_status = -1; end: if (tmp_cover_art != NULL) { free(tmp_cover_art); } return exit_status; }
/* let user pick one song * @param pianobar settings * @param song list * @param input fds * @return pointer to selected item in song list or NULL */ PianoSong_t *BarUiSelectSong (const BarSettings_t *settings, PianoSong_t *startSong, BarReadlineFds_t *input) { PianoSong_t *tmpSong = NULL; char buf[100]; memset (buf, 0, sizeof (buf)); do { BarUiListSongs (settings, startSong, buf); BarUiMsg (settings, MSG_QUESTION, "Select song: "); if (BarReadlineStr (buf, sizeof (buf), input, BAR_RL_DEFAULT) == 0) { return NULL; } if (isnumeric (buf)) { unsigned long i = strtoul (buf, NULL, 0); tmpSong = PianoListGetP (startSong, i); } } while (tmpSong == NULL); return tmpSong; }
int main (int argc, char **argv) { static BarApp_t app; /* terminal attributes _before_ we started messing around with ~ECHO */ struct termios termOrig; struct sigaction action; memset (&app, 0, sizeof (app)); memset (&action, 0, sizeof(action)); /* set the signal handler for SIGINT and SIGTERM */ action.sa_handler = BarSigQuit; sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); /* save terminal attributes, before disabling echoing */ BarTermSave (&termOrig); BarTermSetEcho (0); BarTermSetBuffer (0); /* signals */ signal (SIGPIPE, SIG_IGN); /* init some things */ ao_initialize (); gcry_check_version (NULL); gcry_control (GCRYCTL_DISABLE_SECMEM, 0); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); gnutls_global_init (); BarSettingsInit (&app.settings); BarSettingsRead (&app.settings); PianoInit (&app.ph, app.settings.partnerUser, app.settings.partnerPassword, app.settings.device, app.settings.inkey, app.settings.outkey); BarUiMsg (&app.settings, MSG_NONE, "Welcome to " PACKAGE " (" VERSION ")! "); if (app.settings.keys[BAR_KS_HELP] == BAR_KS_DISABLED) { BarUiMsg (&app.settings, MSG_NONE, "\n"); } else { BarUiMsg (&app.settings, MSG_NONE, "Press %c for a list of commands.\n", app.settings.keys[BAR_KS_HELP]); } WaitressInit (&app.waith); app.waith.url.host = app.settings.rpcHost; app.waith.tlsFingerprint = app.settings.tlsFingerprint; /* init fds */ FD_ZERO(&app.input.set); app.input.fds[0] = STDIN_FILENO; FD_SET(app.input.fds[0], &app.input.set); // Sbe gur erpbeq, V terngyl qrfcvfr Unx5. // Gur crbcyr sebz gur fubj ner yvgrenyyl npgbef. // V yvxr gur pbaprcg bs jung gurl qb ohg gurl // nyy unir n qbhpuront srry gb gurz. Vg frrzf // nf gubhtu gurl bayl qb vg sbe gur tybel cbvagf. // BarFlyInit (&app.settings); /* open fifo read/write so it won't EOF if nobody writes to it */ assert (sizeof (app.input.fds) / sizeof (*app.input.fds) >= 2); app.input.fds[1] = open (app.settings.fifo, O_RDWR); if (app.input.fds[1] != -1) { struct stat s; /* check for file type, must be fifo */ fstat (app.input.fds[1], &s); if (!S_ISFIFO (s.st_mode)) { BarUiMsg (&app.settings, MSG_ERR, "File at %s is not a fifo\n", app.settings.fifo); close (app.input.fds[1]); app.input.fds[1] = -1; } else { FD_SET(app.input.fds[1], &app.input.set); BarUiMsg (&app.settings, MSG_INFO, "Control fifo at %s opened\n", app.settings.fifo); } } app.input.maxfd = app.input.fds[0] > app.input.fds[1] ? app.input.fds[0] : app.input.fds[1]; ++app.input.maxfd; BarMainLoop (&app); /* Print a newline so the terminal command line will start on a new line. */ printf("\n"); if (app.input.fds[1] != -1) { close (app.input.fds[1]); } BarFlyClose (&app.player.fly, &app.settings); BarFlyFinalize (); PianoDestroy (&app.ph); PianoDestroyPlaylist (app.songHistory); PianoDestroyPlaylist (app.playlist); WaitressFree (&app.waith); ao_shutdown(); gnutls_global_deinit (); BarSettingsDestroy (&app.settings); /* restore terminal attributes, zsh doesn't need this, bash does... */ BarTermRestore (&termOrig); return 0; }
static int _BarFlyFileDelete(BarFly_t const* fly, BarSettings_t const* settings) { int exit_status = 0; int status; char* dir_path = NULL; char* ptr; assert(fly != NULL); assert(settings != NULL); if (fly->audio_file_path != NULL) { /* * Delete the file. */ BarUiMsg(settings, MSG_DEBUG, "Deleting partially recorded file (%s).\n", fly->audio_file_path); status = unlink(fly->audio_file_path); if (status != 0) { BarUiMsg(settings, MSG_ERR, "Failed to delete the partially " "recorded file (%s).\n", fly->audio_file_path); goto error; } /* * Delete any empty parent directories. */ dir_path = strdup(fly->audio_file_path); if (dir_path == NULL) { BarUiMsg(settings, MSG_ERR, "Error copying the file path (%s) (%d:%s).\n", fly->audio_file_path, errno, strerror(errno)); goto error; } ptr = strrchr(dir_path, '/'); while (ptr != NULL) { *ptr = '\0'; status = rmdir(dir_path); if ((status != 0) && (errno != ENOTEMPTY) && (errno != EEXIST)) { BarUiMsg(settings, MSG_ERR, "Failed to delete the empty artist directory " "(%s) (%d:%s).\n", dir_path, errno, strerror); goto error; } ptr = strrchr(dir_path, '/'); } } goto end; error: exit_status = -1; end: if (dir_path != NULL) { free(dir_path); } return exit_status; }
/* browse genre stations and create shared station * @param app handle */ void BarStationFromGenre (BarApp_t *app) { PianoReturn_t pRet; WaitressReturn_t wRet; PianoGenreCategory_t *curCat; PianoGenre_t *curGenre; int i; /* receive genre stations list if not yet available */ if (app->ph.genreStations == NULL) { BarUiMsg (&app->settings, MSG_INFO, "Receiving genre stations... "); if (!BarUiPianoCall (app, PIANO_REQUEST_GET_GENRE_STATIONS, NULL, &pRet, &wRet)) { return; } } /* print all available categories */ curCat = app->ph.genreStations; i = 0; while (curCat != NULL) { BarUiMsg (&app->settings, MSG_LIST, "%2i) %s\n", i, curCat->name); i++; curCat = curCat->next; } do { /* select category or exit */ BarUiMsg (&app->settings, MSG_QUESTION, "Select category: "); if (BarReadlineInt (&i, &app->input) == 0) { return; } curCat = app->ph.genreStations; while (curCat != NULL && i > 0) { curCat = curCat->next; i--; } } while (curCat == NULL); /* print all available stations */ curGenre = curCat->genres; i = 0; while (curGenre != NULL) { BarUiMsg (&app->settings, MSG_LIST, "%2i) %s\n", i, curGenre->name); i++; curGenre = curGenre->next; } do { BarUiMsg (&app->settings, MSG_QUESTION, "Select genre: "); if (BarReadlineInt (&i, &app->input) == 0) { return; } curGenre = curCat->genres; while (curGenre != NULL && i > 0) { curGenre = curGenre->next; i--; } } while (curGenre == NULL); /* create station */ PianoRequestDataCreateStation_t reqData; reqData.token = curGenre->musicId; reqData.type = PIANO_MUSICTYPE_INVALID; BarUiMsg (&app->settings, MSG_INFO, "Adding shared station \"%s\"... ", curGenre->name); BarUiPianoCall (app, PIANO_REQUEST_CREATE_STATION, &reqData, &pRet, &wRet); }
/* let user pick one station * @param app handle * @param stations that should be listed * @param prompt string * @param called if input was not a number * @param auto-select if only one station remains after filtering * @return pointer to selected station or NULL */ PianoStation_t *BarUiSelectStation (BarApp_t *app, PianoStation_t *stations, const char *prompt, BarUiSelectStationCallback_t callback, bool autoselect) { PianoStation_t **sortedStations = NULL, *retStation = NULL; size_t stationCount, i, lastDisplayed, displayCount; char buf[100]; if (stations == NULL) { BarUiMsg (&app->settings, MSG_ERR, "No station available.\n"); return NULL; } memset (buf, 0, sizeof (buf)); /* sort and print stations */ sortedStations = BarSortedStations (stations, &stationCount, app->settings.sortOrder); do { displayCount = 0; for (i = 0; i < stationCount; i++) { const PianoStation_t *currStation = sortedStations[i]; /* filter stations */ if (BarStrCaseStr (currStation->name, buf) != NULL) { BarUiMsg (&app->settings, MSG_LIST, "%2i) %c%c%c %s\n", i, currStation->useQuickMix ? 'q' : ' ', currStation->isQuickMix ? 'Q' : ' ', !currStation->isCreator ? 'S' : ' ', currStation->name); ++displayCount; lastDisplayed = i; } } BarUiMsg (&app->settings, MSG_QUESTION, prompt); if (autoselect && displayCount == 1 && stationCount != 1) { /* auto-select last remaining station */ BarUiMsg (&app->settings, MSG_NONE, "%i\n", lastDisplayed); retStation = sortedStations[lastDisplayed]; } else { if (BarReadlineStr (buf, sizeof (buf), &app->input, BAR_RL_DEFAULT) == 0) { break; } if (isnumeric (buf)) { unsigned long selected = strtoul (buf, NULL, 0); if (selected < stationCount) { retStation = sortedStations[selected]; } } /* hand over buffer to external function if it was not a station number */ if (retStation == NULL && callback != NULL) { callback (app, buf); } } } while (retStation == NULL); free (sortedStations); return retStation; }
/* ask for username/password if none were provided in settings */ static bool BarMainGetLoginCredentials (BarSettings_t *settings, BarReadlineFds_t *input) { bool usernameFromConfig = true; if (settings->username == NULL) { char nameBuf[100]; BarUiMsg (settings, MSG_QUESTION, "No User name in config? Email: "); BarReadlineStr (nameBuf, sizeof (nameBuf), input, BAR_RL_DEFAULT); settings->username = strdup (nameBuf); usernameFromConfig = false; } if (settings->password == NULL) { char passBuf[100]; if (usernameFromConfig) { BarUiMsg (settings, MSG_QUESTION, "Got username Email: %s\n", settings->username); } if (settings->passwordCmd == NULL) { BarUiMsg (settings, MSG_QUESTION, "Password: "******""); settings->password = strdup (passBuf); } else { pid_t chld; int pipeFd[2]; BarUiMsg (settings, MSG_INFO, "Requesting password from external helper... "); if (pipe (pipeFd) == -1) { BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno)); return false; } chld = fork (); if (chld == 0) { /* child */ close (pipeFd[0]); dup2 (pipeFd[1], fileno (stdout)); execl ("/bin/sh", "/bin/sh", "-c", settings->passwordCmd, (char *) NULL); BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno)); close (pipeFd[1]); exit (1); } else if (chld == -1) { BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno)); return false; } else { /* parent */ int status; close (pipeFd[1]); memset (passBuf, 0, sizeof (passBuf)); read (pipeFd[0], passBuf, sizeof (passBuf)-1); close (pipeFd[0]); /* drop trailing newlines */ ssize_t len = strlen (passBuf)-1; while (len >= 0 && passBuf[len] == '\n') { passBuf[len] = '\0'; --len; } waitpid (chld, &status, 0); if (WEXITSTATUS (status) == 0) { settings->password = strdup (passBuf); BarUiMsg (settings, MSG_NONE, "Ok.\n"); } else { BarUiMsg (settings, MSG_NONE, "Error: Exit status %i.\n", WEXITSTATUS (status)); return false; } } } /* end else passwordCmd */ } return true; }
int main (int argc, char **argv) { static BarApp_t app; /* terminal attributes _before_ we started messing around with ~ECHO */ struct termios termOrig; memset (&app, 0, sizeof (app)); /* save terminal attributes, before disabling echoing */ BarTermSave (&termOrig); BarTermSetEcho (0); BarTermSetBuffer (0); /* signals */ signal (SIGPIPE, SIG_IGN); /* init some things */ ao_initialize (); gcry_check_version (NULL); gcry_control (GCRYCTL_DISABLE_SECMEM, 0); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); gnutls_global_init (); BarSettingsInit (&app.settings); BarSettingsRead (&app.settings); PianoInit (&app.ph, app.settings.partnerUser, app.settings.partnerPassword, app.settings.device, app.settings.inkey, app.settings.outkey); BarUiMsg (&app.settings, MSG_NONE, "Welcome to " PACKAGE " (" VERSION ")! "); if (app.settings.keys[BAR_KS_HELP] == BAR_KS_DISABLED) { BarUiMsg (&app.settings, MSG_NONE, "\n"); } else { BarUiMsg (&app.settings, MSG_NONE, "Press %c for a list of commands.\n", app.settings.keys[BAR_KS_HELP]); } WaitressInit (&app.waith); app.waith.url.host = app.settings.rpcHost; app.waith.tlsFingerprint = app.settings.tlsFingerprint; /* init fds */ FD_ZERO(&app.input.set); app.input.fds[0] = STDIN_FILENO; FD_SET(app.input.fds[0], &app.input.set); /* open fifo read/write so it won't EOF if nobody writes to it */ assert (sizeof (app.input.fds) / sizeof (*app.input.fds) >= 2); app.input.fds[1] = open (app.settings.fifo, O_RDWR); if (app.input.fds[1] != -1) { struct stat s; /* check for file type, must be fifo */ fstat (app.input.fds[1], &s); if (!S_ISFIFO (s.st_mode)) { BarUiMsg (&app.settings, MSG_ERR, "File at %s is not a fifo\n", app.settings.fifo); close (app.input.fds[1]); app.input.fds[1] = -1; } else { FD_SET(app.input.fds[1], &app.input.set); BarUiMsg (&app.settings, MSG_INFO, "Control fifo at %s opened\n", app.settings.fifo); } } app.input.maxfd = app.input.fds[0] > app.input.fds[1] ? app.input.fds[0] : app.input.fds[1]; ++app.input.maxfd; BarMainLoop (&app); if (app.input.fds[1] != -1) { close (app.input.fds[1]); } PianoDestroy (&app.ph); PianoDestroyPlaylist (app.songHistory); PianoDestroyPlaylist (app.playlist); WaitressFree (&app.waith); ao_shutdown(); gnutls_global_deinit (); BarSettingsDestroy (&app.settings); /* restore terminal attributes, zsh doesn't need this, bash does... */ BarTermRestore (&termOrig); return 0; }
/* mp3 playback callback */ static WaitressCbReturn_t BarPlayerMp3Cb (void *ptr, size_t size, void *stream) { const char *data = ptr; struct audioPlayer *player = stream; size_t i; QUIT_PAUSE_CHECK; if (!BarPlayerBufferFill (player, data, size)) { return WAITRESS_CB_RET_ERR; } /* some "prebuffering" */ if (player->mode < PLAYER_RECV_DATA && player->bufferFilled < BAR_PLAYER_BUFSIZE / 2) { return WAITRESS_CB_RET_OK; } mad_stream_buffer (&player->mp3Stream, player->buffer, player->bufferFilled); player->mp3Stream.error = 0; do { /* channels * max samples, found in mad.h */ signed short int madDecoded[2*1152], *madPtr = madDecoded; if (mad_frame_decode (&player->mp3Frame, &player->mp3Stream) != 0) { if (player->mp3Stream.error != MAD_ERROR_BUFLEN) { BarUiMsg (player->settings, MSG_ERR, "mp3 decoding error: %s\n", mad_stream_errorstr (&player->mp3Stream)); return WAITRESS_CB_RET_ERR; } else { /* rebuffering required => exit loop */ break; } } mad_synth_frame (&player->mp3Synth, &player->mp3Frame); for (i = 0; i < player->mp3Synth.pcm.length; i++) { /* left channel */ *(madPtr++) = applyReplayGain (BarPlayerMadToShort ( player->mp3Synth.pcm.samples[0][i]), player->scale); /* right channel */ *(madPtr++) = applyReplayGain (BarPlayerMadToShort ( player->mp3Synth.pcm.samples[1][i]), player->scale); } if (player->mode < PLAYER_AUDIO_INITIALIZED) { ao_sample_format format; int audioOutDriver; player->channels = player->mp3Synth.pcm.channels; player->samplerate = player->mp3Synth.pcm.samplerate; audioOutDriver = ao_default_driver_id(); memset (&format, 0, sizeof (format)); format.bits = 16; format.channels = player->channels; format.rate = player->samplerate; format.byte_format = AO_FMT_NATIVE; if ((player->audioOutDevice = ao_open_live (audioOutDriver, &format, NULL)) == NULL) { player->aoError = 1; BarUiMsg (player->settings, MSG_ERR, "Cannot open audio device\n"); return WAITRESS_CB_RET_ERR; } /* calc song length using the framerate of the first decoded frame */ player->songDuration = (unsigned long long int) player->waith.request.contentLength / ((unsigned long long int) player->mp3Frame.header.bitrate / (unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR / 8LL); /* must be > PLAYER_SAMPLESIZE_INITIALIZED, otherwise time won't * be visible to user (ugly, but mp3 decoding != aac decoding) */ player->mode = PLAYER_RECV_DATA; } /* samples * length * channels */ ao_play (player->audioOutDevice, (char *) madDecoded, player->mp3Synth.pcm.length * 2 * 2); /* avoid division by 0 */ if (player->mode == PLAYER_RECV_DATA) { /* same calculation as in aac player; don't need to divide by * channels, length is number of samples for _one_ channel */ player->songPlayed += (unsigned long long int) player->mp3Synth.pcm.length * (unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR / (unsigned long long int) player->samplerate; } QUIT_PAUSE_CHECK; } while (player->mp3Stream.error != MAD_ERROR_BUFLEN); player->bufferRead += player->mp3Stream.next_frame - player->buffer; BarPlayerBufferMove (player); return WAITRESS_CB_RET_OK; }
static int _BarFlyParseTrackDisc(char const* title, char const* album_xml, short unsigned* track_num, short unsigned* disc_num, BarSettings_t const* settings) { size_t const MATCH_COUNT = 3; size_t const ERROR_MSG_SIZE = 100; int exit_status = 0; int status; char* regex_string = NULL; regex_t regex_track; regmatch_t match[MATCH_COUNT]; char error_msg[ERROR_MSG_SIZE]; char regex_title[BAR_FLY_NAME_LENGTH]; int index; int index2; short unsigned track; short unsigned disc; assert(title != NULL); assert(album_xml != NULL); assert(track_num != NULL); assert(disc_num != NULL); assert(settings != NULL); memset(®ex_track, 0, sizeof(regex_track)); /* * Create the regular expression string. * * The title needs to have all potential regular expresion metacharacters * fixed. The real correct way to do this would be to escape all of them * with '\'. But the easy way to it is simply replace them with '.'. * * Additionally Pandora excludes some characters from the titles in the * xml page. These characters are ignored. */ index2 = 0; for (index = 0; title[index] != '\0'; index++) { if (title[index] == '^' || title[index] == '$' || title[index] == '(' || title[index] == ')' || title[index] == '>' || title[index] == '<' || title[index] == '[' || title[index] == '{' || title[index] == '\\' || title[index] == '|' || title[index] == '.' || title[index] == '*' || title[index] == '+' || title[index] == '&') { regex_title[index2] = '.'; index2++; } else if (title[index] == '?' ) { /* * Skip these characters. */ } else { regex_title[index2] = title[index]; index2++; } } regex_title[index2] = '\0'; status = BarFlyasprintf(®ex_string, "songTitle *= *\"%s\"[^>]+" "discNum *= *\"([0-9]+)\"[^>]+" "trackNum *= *\"([0-9]+)\"", regex_title); if (status == -1) { BarUiMsg(settings, MSG_ERR, "Failed to create the regex string to get " "the track and disc numbers (%d:%s).\n", errno, strerror(errno)); goto error; } /* * Compile and execute the regular expression to get the track and disc * numbers. */ status = regcomp(®ex_track, regex_string, REG_EXTENDED); if (status != 0) { regerror(status, ®ex_track, error_msg, ERROR_MSG_SIZE); BarUiMsg(settings, MSG_ERR, "Failed to compile the regex to get the " "track and disc numbers (%d:%s).\n", status, error_msg); goto error; } memset(match, 0, sizeof(match)); status = regexec(®ex_track, album_xml, MATCH_COUNT, match, 0); if (status != 0) { regerror(status, ®ex_track, error_msg, ERROR_MSG_SIZE); BarUiMsg(settings, MSG_DEBUG, "The track and disc numbers were not " "included in the album explorer page (%d:%s).\n", status, error_msg); goto error; } /* * Copy the track number. */ status = sscanf(album_xml + match[2].rm_so, "%hu", &track); if (status != 1) { BarUiMsg(settings, MSG_ERR, "Failed to copy the track number " "(%d:%s).\n", errno, strerror(errno)); goto error; } /* * Copy the disc number. */ status = sscanf(album_xml + match[1].rm_so, "%hu", &disc); if (status != 1) { BarUiMsg(settings, MSG_ERR, "Failed to copy the disc number (%d:%s).\n", errno, strerror(errno)); goto error; } *track_num = track; *disc_num = disc; goto end; error: exit_status = -1; end: regfree(®ex_track); if (regex_string != NULL) { free(regex_string); } return exit_status; }
static char* _BarFlyParseCoverArtURL(char const* html, BarSettings_t const* settings) { size_t const MATCH_COUNT = 2; size_t const ERROR_MSG_SIZE = 100; char* url = NULL; int status; regex_t regex_cover; regmatch_t cover_match[MATCH_COUNT]; char error_msg[ERROR_MSG_SIZE]; assert(html != NULL); /* * Search the html page to find the cover art URL. */ memset(®ex_cover, 0, sizeof(regex_cover)); status = regcomp(®ex_cover, "src *= *\"([^\"]+)\"[^>]*class *= *\"img_cvr\"", REG_EXTENDED); if (status != 0) { regerror(status, ®ex_cover, error_msg, ERROR_MSG_SIZE); BarUiMsg(settings, MSG_ERR, "Failed to compile the cover at regex " "(%d:%s).\n", status, error_msg); goto error; } memset(cover_match, 0, sizeof(cover_match)); status = regexec(®ex_cover, html, MATCH_COUNT, cover_match, 0); if (status != 0) { regerror(status, ®ex_cover, error_msg, ERROR_MSG_SIZE); BarUiMsg(settings, MSG_DEBUG, "The cover art was not included in the " "album detail page (%d:%s).\n", status, error_msg); goto error; } /* * Extract the cover art URL. */ url = strndup(html + cover_match[1].rm_so, cover_match[1].rm_eo - cover_match[1].rm_so); if (url == NULL) { BarUiMsg(settings, MSG_ERR, "Failed to copy the cover art url " "(%d:%s).\n", errno, strerror(errno)); goto error; } /* * Make sure this isn't the no_album_art.jpg image. This check must be * done to only the URL itself and not the whole HTML page since the similar * albums list could also use this image. */ if (strstr(url, "no_album_art.jpg") != NULL) { BarUiMsg(settings, MSG_DEBUG, "This album does not have cover art.\n"); goto error; } goto end; error: if (url != NULL) { free(url); url = NULL; } end: regfree(®ex_cover); return url; }
static int _BarFlyFileOpen(FILE** file, char const* path, BarSettings_t const* settings) { FILE *tmp_file = NULL; int exit_status = 0; int status; char* dir_path = NULL; char* ptr; assert(file != NULL); assert(path != NULL); assert(settings != NULL); /* * Create any parent directories. */ ptr = strchr(path, '/'); while (ptr != NULL) { status = BarFlyasprintf(&dir_path, "%.*s", (int)(ptr - path), path); if (status == -1) { BarUiMsg(settings, MSG_ERR, "Error copying the directory path of " "the audio file (%d:%s).\n", errno, strerror(errno)); exit_status = -1; goto error; } status = mkdir(dir_path, 0755); if ((status == -1) && (errno != EEXIST)) { BarUiMsg(settings, MSG_ERR, "Error creating a parent directory of " "the audio file (%s) (%d:%s).\n", errno, strerror(errno)); exit_status = -1; goto error; } free(dir_path); dir_path = NULL; ptr = strchr(ptr + 1, '/'); } /* * Open the audio file for writing. */ tmp_file = _BarFlyFileOpenStream(path, "wb"); if ((tmp_file == NULL) && (errno == EEXIST)) { BarUiMsg(settings, MSG_DEBUG, "The audio file already exists. It will " "not be recorded (%s).\n", path); exit_status = -2; goto error; } else if (tmp_file == NULL) { BarUiMsg(settings, MSG_ERR, "Error opening the audio file for reading " "(%s) (%d:%s).\n", path, errno, strerror(errno)); exit_status = -1; goto error; } *file = tmp_file; tmp_file = NULL; goto end; error: end: if (dir_path != NULL) { free(dir_path); } if (tmp_file != NULL) { fclose(tmp_file); } return exit_status; }
static char* _BarFlyFileGetPath(char const* artist, char const* album, char const* title, short unsigned year, short unsigned track, short unsigned disc, PianoAudioFormat_t audio_format, BarSettings_t const* settings) { char* path = NULL; size_t path_length; char path_artist[BAR_FLY_NAME_LENGTH]; char path_album[BAR_FLY_NAME_LENGTH]; char path_title[BAR_FLY_NAME_LENGTH]; char const* extension; char const* file_pattern_ptr; size_t count; char* path_ptr; assert(artist != NULL); assert(album != NULL); assert(title != NULL); assert(settings != NULL); /* * Get the artist, album, and title. Translate each of the characters * we don't want to _. */ _BarFlyNameTranslate(path_artist, artist, BAR_FLY_NAME_LENGTH, settings); _BarFlyNameTranslate(path_album, album, BAR_FLY_NAME_LENGTH, settings); _BarFlyNameTranslate(path_title, title, BAR_FLY_NAME_LENGTH, settings); /* * Get the extension. */ switch (audio_format) { #ifdef ENABLE_FAAD case PIANO_AF_AACPLUS: extension = ".m4a"; break; #endif #ifdef ENABLE_MAD case PIANO_AF_MP3: case PIANO_AF_MP3_HI: extension = ".mp3"; break; #endif default: BarUiMsg(settings, MSG_ERR, "Unsupported audio format!\n"); goto error; break; } /* * Calculate the length of the path. */ path_length = 0; file_pattern_ptr = settings->audioFileName; while (*file_pattern_ptr != '\0') { /* * Get the length of everything up to the next '%'. */ count = strcspn(file_pattern_ptr, "%"); path_length += count; file_pattern_ptr += count; /* * Get the length of each substitution. The track is always at least * 2 digits. */ if (*file_pattern_ptr != '\0') { if (strncmp("%artist", file_pattern_ptr, 7) == 0) { path_length += strlen(path_artist); file_pattern_ptr += 7; } else if (strncmp("%album", file_pattern_ptr, 6) == 0) { path_length += strlen(path_album); file_pattern_ptr += 6; } else if (strncmp("%title", file_pattern_ptr, 6) == 0) { path_length += strlen(path_title); file_pattern_ptr += 6; } else if (strncmp("%year", file_pattern_ptr, 5) == 0) { path_length += snprintf(NULL, 0, "%hu", year); file_pattern_ptr += 5; } else if (strncmp("%track", file_pattern_ptr, 6) == 0) { path_length += snprintf(NULL, 0, "%02hu", track); file_pattern_ptr += 6; } else if (strncmp("%disc", file_pattern_ptr, 5) == 0) { path_length += snprintf(NULL, 0, "%hu", disc); file_pattern_ptr += 5; } else { file_pattern_ptr += 1; } } } path_length += strlen(extension); /* * Allocate space for the path. */ path = malloc(path_length + 1); if (path == NULL) { BarUiMsg(settings, MSG_ERR, "Error allocating memory (%d bytes) (%d:%s).\n", path_length + 1, errno, strerror(errno)); goto error; } /* * Populate the buffer with the path. */ file_pattern_ptr = settings->audioFileName; path_ptr = path; while (*file_pattern_ptr != '\0') { /* * Copy any any characters before the next substitution. */ count = strcspn(file_pattern_ptr, "%"); strncpy(path_ptr, file_pattern_ptr, count); file_pattern_ptr += count; path_ptr += count; /* * Substitute in the value. */ if (*file_pattern_ptr != '\0') { if (strncmp("%artist", file_pattern_ptr, 7) == 0) { strcpy(path_ptr, path_artist); file_pattern_ptr += 7; path_ptr += strlen(path_artist); } else if (strncmp("%album", file_pattern_ptr, 6) == 0) { strcpy(path_ptr, path_album); file_pattern_ptr += 6; path_ptr += strlen(path_album); } else if (strncmp("%title", file_pattern_ptr, 6) == 0) { strcpy(path_ptr, path_title); file_pattern_ptr += 6; path_ptr += strlen(path_title); } else if (strncmp("%year", file_pattern_ptr, 5) == 0) { sprintf(path_ptr, "%hu", year); file_pattern_ptr += 5; path_ptr += snprintf(NULL, 0, "%hu", year); } else if (strncmp("%track", file_pattern_ptr, 6) == 0) { sprintf(path_ptr, "%02hu", track); file_pattern_ptr += 6; path_ptr += snprintf(NULL, 0, "%02hu", track); } else if (strncmp("%disc", file_pattern_ptr, 5) == 0) { sprintf(path_ptr, "%hu", disc); file_pattern_ptr += 5; path_ptr += snprintf(NULL, 0, "%hu", disc); } else { file_pattern_ptr += 1; } } } strcpy(path_ptr, extension); goto end; error: if (path != NULL) { free(path); path = NULL; } end: return path; }
/* play aac stream * @param streamed data * @param received bytes * @param extra data (player data) * @return received bytes or less on error */ static WaitressCbReturn_t BarPlayerAACCb (void *ptr, size_t size, void *stream) { const char *data = ptr; struct audioPlayer *player = stream; QUIT_PAUSE_CHECK; if (!BarPlayerBufferFill (player, data, size)) { return WAITRESS_CB_RET_ERR; } if (player->mode == PLAYER_RECV_DATA) { short int *aacDecoded; NeAACDecFrameInfo frameInfo; size_t i; while ((player->bufferFilled - player->bufferRead) > player->sampleSize[player->sampleSizeCurr]) { /* decode frame */ aacDecoded = NeAACDecDecode(player->aacHandle, &frameInfo, player->buffer + player->bufferRead, player->sampleSize[player->sampleSizeCurr]); if (frameInfo.error != 0) { BarUiMsg (player->settings, MSG_ERR, "Decoding error: %s\n", NeAACDecGetErrorMessage (frameInfo.error)); break; } for (i = 0; i < frameInfo.samples; i++) { aacDecoded[i] = applyReplayGain (aacDecoded[i], player->scale); } /* ao_play needs bytes: 1 sample = 16 bits = 2 bytes */ ao_play (player->audioOutDevice, (char *) aacDecoded, frameInfo.samples * 2); /* add played frame length to played time, explained below */ player->songPlayed += (unsigned long long int) frameInfo.samples * (unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR / (unsigned long long int) player->samplerate / (unsigned long long int) player->channels; player->bufferRead += frameInfo.bytesconsumed; player->sampleSizeCurr++; /* going through this loop can take up to a few seconds => * allow earlier thread abort */ QUIT_PAUSE_CHECK; } } else { if (player->mode == PLAYER_INITIALIZED) { while (player->bufferRead+4 < player->bufferFilled) { if (memcmp (player->buffer + player->bufferRead, "esds", 4) == 0) { player->mode = PLAYER_FOUND_ESDS; player->bufferRead += 4; break; } player->bufferRead++; } } if (player->mode == PLAYER_FOUND_ESDS) { /* FIXME: is this the correct way? */ /* we're gonna read 10 bytes */ while (player->bufferRead+1+4+5 < player->bufferFilled) { if (memcmp (player->buffer + player->bufferRead, "\x05\x80\x80\x80", 4) == 0) { ao_sample_format format; int audioOutDriver; /* +1+4 needs to be replaced by <something>! */ player->bufferRead += 1+4; char err = NeAACDecInit2 (player->aacHandle, player->buffer + player->bufferRead, 5, &player->samplerate, &player->channels); player->bufferRead += 5; if (err != 0) { BarUiMsg (player->settings, MSG_ERR, "Error while initializing audio decoder " "(%i)\n", err); return WAITRESS_CB_RET_ERR; } audioOutDriver = ao_default_driver_id(); memset (&format, 0, sizeof (format)); format.bits = 16; format.channels = player->channels; format.rate = player->samplerate; format.byte_format = AO_FMT_NATIVE; if ((player->audioOutDevice = ao_open_live (audioOutDriver, &format, NULL)) == NULL) { /* we're not interested in the errno */ player->aoError = 1; BarUiMsg (player->settings, MSG_ERR, "Cannot open audio device\n"); return WAITRESS_CB_RET_ERR; } player->mode = PLAYER_AUDIO_INITIALIZED; break; } player->bufferRead++; } } if (player->mode == PLAYER_AUDIO_INITIALIZED) { while (player->bufferRead+4+8 < player->bufferFilled) { if (memcmp (player->buffer + player->bufferRead, "stsz", 4) == 0) { player->mode = PLAYER_FOUND_STSZ; player->bufferRead += 4; /* skip version and unknown */ player->bufferRead += 8; break; } player->bufferRead++; } } /* get frame sizes */ if (player->mode == PLAYER_FOUND_STSZ) { while (player->bufferRead+4 < player->bufferFilled) { /* how many frames do we have? */ if (player->sampleSizeN == 0) { /* mp4 uses big endian, convert */ player->sampleSizeN = bigToHostEndian32 (*((uint32_t *) (player->buffer + player->bufferRead))); player->sampleSize = malloc (player->sampleSizeN * sizeof (*player->sampleSize)); player->bufferRead += 4; player->sampleSizeCurr = 0; /* set up song duration (assuming one frame always contains * the same number of samples) * calculation: channels * number of frames * samples per * frame / samplerate */ /* FIXME: Hard-coded number of samples per frame */ player->songDuration = (unsigned long long int) player->sampleSizeN * 4096LL * (unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR / (unsigned long long int) player->samplerate / (unsigned long long int) player->channels; break; } else { player->sampleSize[player->sampleSizeCurr] = bigToHostEndian32 (*((uint32_t *) (player->buffer + player->bufferRead))); player->sampleSizeCurr++; player->bufferRead += 4; } /* all sizes read, nearly ready for data mode */ if (player->sampleSizeCurr >= player->sampleSizeN) { player->mode = PLAYER_SAMPLESIZE_INITIALIZED; break; } } } /* search for data atom and let the show begin... */ if (player->mode == PLAYER_SAMPLESIZE_INITIALIZED) { while (player->bufferRead+4 < player->bufferFilled) { if (memcmp (player->buffer + player->bufferRead, "mdat", 4) == 0) { player->mode = PLAYER_RECV_DATA; player->sampleSizeCurr = 0; player->bufferRead += 4; break; } player->bufferRead++; } } } BarPlayerBufferMove (player); return WAITRESS_CB_RET_OK; }
static void printError (const BarSettings_t * const settings, const char * const msg, int ret) { char avmsg[128]; av_strerror (ret, avmsg, sizeof (avmsg)); BarUiMsg (settings, MSG_ERR, "%s (%s)\n", msg, avmsg); }
/* player thread; for every song a new thread is started * @param audioPlayer structure * @return PLAYER_RET_* */ void *BarPlayerThread (void *data) { struct audioPlayer *player = data; char extraHeaders[32]; void *ret = PLAYER_RET_OK; #ifdef ENABLE_FAAD NeAACDecConfigurationPtr conf; #endif WaitressReturn_t wRet = WAITRESS_RET_ERR; /* init handles */ player->waith.data = (void *) player; /* extraHeaders will be initialized later */ player->waith.extraHeaders = extraHeaders; player->buffer = malloc (BAR_PLAYER_BUFSIZE); switch (player->audioFormat) { #ifdef ENABLE_FAAD case PIANO_AF_AACPLUS: player->aacHandle = NeAACDecOpen(); /* set aac conf */ conf = NeAACDecGetCurrentConfiguration(player->aacHandle); conf->outputFormat = FAAD_FMT_16BIT; conf->downMatrix = 1; NeAACDecSetConfiguration(player->aacHandle, conf); player->waith.callback = BarPlayerAACCb; break; #endif /* ENABLE_FAAD */ #ifdef ENABLE_MAD case PIANO_AF_MP3: mad_stream_init (&player->mp3Stream); mad_frame_init (&player->mp3Frame); mad_synth_init (&player->mp3Synth); player->waith.callback = BarPlayerMp3Cb; break; #endif /* ENABLE_MAD */ default: /* FIXME: leaks memory */ BarUiMsg (player->settings, MSG_ERR, "Unsupported audio format!\n"); return PLAYER_RET_OK; break; } player->mode = PLAYER_INITIALIZED; /* This loop should work around song abortions by requesting the * missing part of the song */ do { snprintf (extraHeaders, sizeof (extraHeaders), "Range: bytes=%zu-\r\n", player->bytesReceived); wRet = WaitressFetchCall (&player->waith); } while (wRet == WAITRESS_RET_PARTIAL_FILE || wRet == WAITRESS_RET_TIMEOUT || wRet == WAITRESS_RET_READ_ERR); /* If the song was played all the way through tag it. */ if (wRet == WAITRESS_RET_OK) { BarFlyTag(&player->fly, player->settings); } switch (player->audioFormat) { #ifdef ENABLE_FAAD case PIANO_AF_AACPLUS: NeAACDecClose(player->aacHandle); free (player->sampleSize); break; #endif /* ENABLE_FAAD */ #ifdef ENABLE_MAD case PIANO_AF_MP3: mad_synth_finish (&player->mp3Synth); mad_frame_finish (&player->mp3Frame); mad_stream_finish (&player->mp3Stream); break; #endif /* ENABLE_MAD */ default: /* this should never happen: thread is aborted above */ break; } if (player->aoError) { ret = (void *) PLAYER_RET_ERR; } ao_close(player->audioOutDevice); WaitressFree (&player->waith); free (player->buffer); player->mode = PLAYER_FINISHED_PLAYBACK; return ret; }
int BarFlyID3AddFrame(struct id3_tag* tag, char const* type, char const* value, BarSettings_t const* settings) { int exit_status = 0; int status; struct id3_frame* frame = NULL; union id3_field* field; id3_ucs4_t* ucs4 = NULL; int index; assert(tag != NULL); assert(type != NULL); assert(value != NULL); assert(settings != NULL); /* * Create the frame. */ frame = id3_frame_new(type); if (frame == NULL) { BarUiMsg(settings, MSG_ERR, "Failed to create new frame (type = %s).\n", type); goto error; } frame->flags &= ~ID3_FRAME_FLAG_FORMATFLAGS; /* * Get the string list field of the frame. */ index = 0; do { field = id3_frame_field(frame, index); index++; } while (id3_field_type(field) != ID3_FIELD_TYPE_STRINGLIST); assert(id3_field_type(field) == ID3_FIELD_TYPE_STRINGLIST); /* * Add the value as a string to the field. */ ucs4 = id3_latin1_ucs4duplicate((id3_latin1_t*)value); if (ucs4 == NULL) { BarUiMsg(settings, MSG_ERR, "Could not allocate memory.\n"); goto error; } status = id3_field_addstring(field, ucs4); if (status != 0) { BarUiMsg(settings, MSG_ERR, "Failed to set field value (value = %s).\n", value); goto error; } /* * Attach the frame to the tag. */ status = id3_tag_attachframe(tag, frame); if (status != 0) { BarUiMsg(settings, MSG_ERR, "Failed to attach frame (type = %s).\n", type); goto error; } goto end; error: if (frame != NULL) { id3_frame_delete(frame); } exit_status = -1; end: if (ucs4 != NULL) { free(ucs4); } return exit_status; }
int main (int argc, char **argv) { static BarApp_t app; memset (&app, 0, sizeof (app)); /* save terminal attributes, before disabling echoing */ BarTermInit (); /* signals */ signal (SIGPIPE, SIG_IGN); /* init some things */ gcry_check_version (NULL); gcry_control (GCRYCTL_DISABLE_SECMEM, 0); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); BarPlayerInit (); BarSettingsInit (&app.settings); BarSettingsRead (&app.settings); PianoReturn_t pret; if ((pret = PianoInit (&app.ph, app.settings.partnerUser, app.settings.partnerPassword, app.settings.device, app.settings.inkey, app.settings.outkey)) != PIANO_RET_OK) { BarUiMsg (&app.settings, MSG_ERR, "Initialization failed:" " %s\n", PianoErrorToStr (pret)); return 0; } BarUiMsg (&app.settings, MSG_NONE, "Welcome to " PACKAGE " (" VERSION ")! "); if (app.settings.keys[BAR_KS_HELP] == BAR_KS_DISABLED) { BarUiMsg (&app.settings, MSG_NONE, "\n"); } else { BarUiMsg (&app.settings, MSG_NONE, "Press %c for a list of commands.\n", app.settings.keys[BAR_KS_HELP]); } curl_global_init (CURL_GLOBAL_DEFAULT); app.http = curl_easy_init (); assert (app.http != NULL); /* init fds */ FD_ZERO(&app.input.set); int fifo_uses_this_fd = 0; app.input.fds[0] = STDIN_FILENO; if (isatty(fileno(stdin))) { fifo_uses_this_fd = 1; FD_SET(app.input.fds[0], &app.input.set); } /* open fifo read/write so it won't EOF if nobody writes to it */ assert (sizeof (app.input.fds) / sizeof (*app.input.fds) >= 2); app.input.fds[fifo_uses_this_fd] = open (app.settings.fifo, O_RDWR); if (app.input.fds[fifo_uses_this_fd] != -1) { struct stat s; /* check for file type, must be fifo */ fstat (app.input.fds[fifo_uses_this_fd], &s); if (!S_ISFIFO (s.st_mode)) { BarUiMsg (&app.settings, MSG_ERR, "File at %s is not a fifo\n", app.settings.fifo); close (app.input.fds[fifo_uses_this_fd]); app.input.fds[fifo_uses_this_fd] = -1; } else { FD_SET(app.input.fds[fifo_uses_this_fd], &app.input.set); BarUiMsg (&app.settings, MSG_INFO, "Control fifo at %s opened\n", app.settings.fifo); } } app.input.maxfd = app.input.fds[0]; if (fifo_uses_this_fd>0) app.input.maxfd = app.input.fds[0] > app.input.fds[1] ? app.input.fds[0] : app.input.fds[1]; ++app.input.maxfd; BarMainLoop (&app); if (app.input.fds[fifo_uses_this_fd] != -1) { close (app.input.fds[fifo_uses_this_fd]); } /* write statefile */ BarSettingsWrite (app.curStation, &app.settings); PianoDestroy (&app.ph); PianoDestroyPlaylist (app.songHistory); PianoDestroyPlaylist (app.playlist); curl_easy_cleanup (app.http); curl_global_cleanup (); BarPlayerDestroy (); BarSettingsDestroy (&app.settings); /* restore terminal attributes, zsh doesn't need this, bash does... */ BarTermRestore (); return 0; }
int BarFlyID3WriteFile(char const* file_path, struct id3_tag const* tag, BarSettings_t const* settings) { /* * './' + artist + '/' + album + '/' + BAR_FLY_TMP_MP3_FILE_NAME + '\0' */ int const TMP_FILE_PATH_LENGTH = 2 + BAR_FLY_NAME_LENGTH + 1 + BAR_FLY_NAME_LENGTH + 1 + strlen(BAR_FLY_TMP_MP3_FILE_NAME) + 1; int exit_status = 0; int status_int; id3_length_t size1; id3_length_t size2; id3_byte_t* tag_buffer = NULL; FILE* audio_file = NULL; FILE* tmp_file = NULL; uint8_t audio_buffer[BAR_FLY_COPY_BLOCK_SIZE]; char tmp_file_path[TMP_FILE_PATH_LENGTH]; size_t read_count; size_t write_count; tmp_file_path[0] = '\0'; /* * For starters libid3tag kinda sucks. It will only write a tag to a file * if the new tag is the same size as the old tag. Which in this case, * since there is no tag, will never work. So writing of the tag to the * file has to be done manually. */ /* * Render the tag to a buffer that can then be written to the audio file. */ size1 = id3_tag_render(tag, NULL); tag_buffer = malloc(size1); if (tag_buffer == NULL) { BarUiMsg(settings, MSG_ERR, "Failed to allocate memory (bytes = %d).\n", size1); goto error; } size2 = id3_tag_render(tag, tag_buffer); if (size1 != size2) { BarUiMsg(settings, MSG_ERR, "Invalid tag size (expected = %d, " "recevied = %d).\n", size1, size2); goto error; } /* * Prepending data to a file is not trivial in C. Here the approach taken * is to create a temporary file, write the tag to the beginning of the * file, copy the audio file block by block to the tmp file, then overwrite * the audio file with the tmp file. This was done in order to minimize the * chance of ending up with a broken audio file in case the program stopped * durring this process. */ /* * Open the audio file. */ audio_file = fopen(file_path, "rb"); if (audio_file == NULL) { BarUiMsg(settings, MSG_ERR, "Could not read the audio file (%s) " "(%d:%s).\n", file_path, errno, strerror(errno)); goto error; } /* * Open the tmp file. */ if (strchr(file_path, '/') == NULL) { strcpy(tmp_file_path, BAR_FLY_TMP_MP3_FILE_NAME); } else { strncpy(tmp_file_path, file_path, TMP_FILE_PATH_LENGTH); tmp_file_path[TMP_FILE_PATH_LENGTH - 1] = '\0'; dirname(tmp_file_path); strcat(tmp_file_path, "/"); strcat(tmp_file_path, BAR_FLY_TMP_MP3_FILE_NAME); } tmp_file = fopen(tmp_file_path, "w+b"); if (tmp_file == NULL) { BarUiMsg(settings, MSG_ERR, "Could not open the temporary file (%s) " "(%d:%s).\n", tmp_file_path, errno, strerror(errno)); goto error; } /* * Write the tag to the tmp file. */ write_count = fwrite(tag_buffer, 1, size2, tmp_file); if (write_count != size2) { BarUiMsg(settings, MSG_ERR, "Could not write the tag to the file (%s) " "(%d:%s).\n", tmp_file_path, errno, strerror(errno)); goto error; } /* * Read the audio file block by block until the end is reached. Each block * is written to the tmp file. */ while (feof(audio_file) == 0) { read_count = fread(audio_buffer, 1, BAR_FLY_COPY_BLOCK_SIZE, audio_file); if ((read_count != BAR_FLY_COPY_BLOCK_SIZE) && (feof(audio_file) == 0)) { BarUiMsg(settings, MSG_ERR, "Failed to read the audio file (%s) " "(%d:%s).\n", file_path, errno, strerror(errno)); goto error; } write_count = fwrite(audio_buffer, 1, read_count, tmp_file); if (write_count != read_count) { BarUiMsg(settings, MSG_ERR, "Failed to write to the tmp file " "(%s).\n", tmp_file_path); goto error; } } /* * The entire contents of the audio file was copied to the tmp file. Close * the two files. */ fclose(tmp_file); tmp_file = NULL; fclose(audio_file); audio_file = NULL; /* * Overwrite the audio file with the tmp file. */ status_int = rename(tmp_file_path, file_path); if (status_int != 0) { BarUiMsg(settings, MSG_ERR, "Could not overwrite the audio file " "(%d:%s).\n", errno, strerror(errno)); goto error; } goto end; error: /* * Delete the tmp file if it exists. */ unlink(tmp_file_path); exit_status = -1; end: if (tag_buffer != NULL) { free(tag_buffer); } if (audio_file != NULL) { fclose(audio_file); } if (tmp_file != NULL) { fclose(tmp_file); } return exit_status; }
/* piano wrapper: prepare/execute http request and pass result back to * libpiano (updates data structures) * @param app handle * @param request type * @param request data * @param stores piano return code * @param stores waitress return code * @return 1 on success, 0 otherwise */ int BarUiPianoCall (BarApp_t * const app, PianoRequestType_t type, void *data, PianoReturn_t *pRet, WaitressReturn_t *wRet) { PianoRequest_t req; memset (&req, 0, sizeof (req)); /* repeat as long as there are http requests to do */ do { req.data = data; *pRet = PianoRequest (&app->ph, &req, type); if (*pRet != PIANO_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Error: %s\n", PianoErrorToStr (*pRet)); PianoDestroyRequest (&req); return 0; } *wRet = BarPianoHttpRequest (&app->waith, &req); if (*wRet != WAITRESS_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Network error: %s\n", WaitressErrorToStr (*wRet)); if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } *pRet = PianoResponse (&app->ph, &req); if (*pRet != PIANO_RET_CONTINUE_REQUEST) { /* checking for request type avoids infinite loops */ if (*pRet == PIANO_RET_P_INVALID_AUTH_TOKEN && type != PIANO_REQUEST_LOGIN) { /* reauthenticate */ PianoReturn_t authpRet; WaitressReturn_t authwRet; PianoRequestDataLogin_t reqData; reqData.user = app->settings.username; reqData.password = app->settings.password; reqData.step = 0; BarUiMsg (&app->settings, MSG_NONE, "Reauthentication required... "); if (!BarUiPianoCall (app, PIANO_REQUEST_LOGIN, &reqData, &authpRet, &authwRet)) { *pRet = authpRet; *wRet = authwRet; if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } else { /* try again */ *pRet = PIANO_RET_CONTINUE_REQUEST; BarUiMsg (&app->settings, MSG_INFO, "Trying again... "); } } else if (*pRet != PIANO_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Error: %s\n", PianoErrorToStr (*pRet)); if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } else { BarUiMsg (&app->settings, MSG_NONE, "Ok.\n"); } } /* we can destroy the request at this point, even when this call needs * more than one http request. persistent data (step counter, e.g.) is * stored in req.data */ if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); } while (*pRet == PIANO_RET_CONTINUE_REQUEST); return 1; }
int BarFlyID3AddCover(struct id3_tag* tag, uint8_t const* cover_art, size_t cover_size, BarSettings_t const* settings) { /* * http://flac.sourceforge.net/api/group__flac__format.html#ga113 */ int const PICTURE_TYPE_FRONT_COVER = 3; char const BAR_FLY_ID3_FRAME_PICTURE[] = "APIC"; int exit_status = 0; int status; struct id3_frame* frame = NULL; union id3_field* field; int index; char* mime_type; assert(tag != NULL); assert(cover_art != NULL); assert(settings != NULL); /* * Get a new picture frame. */ frame = id3_frame_new(BAR_FLY_ID3_FRAME_PICTURE); if (frame == NULL) { BarUiMsg(settings, MSG_ERR, "Failed to create new frame (type = %s).\n", BAR_FLY_ID3_FRAME_PICTURE); goto error; } /* * Go through all the frame fields setting the mime type, image type, and * the image data. */ index = 0; field = id3_frame_field(frame, index); while (field != NULL) { switch (id3_field_type(field)) { /* * Set the cover art mime type. */ case (ID3_FIELD_TYPE_LATIN1): if ((cover_art[0] == 0xFF) && (cover_art[1] == 0xD8)) { mime_type = "image/jpeg"; } else if ((cover_art[0] == 0x89) && (cover_art[1] == 0x50) && (cover_art[2] == 0x4E) && (cover_art[3] == 0x47) && (cover_art[4] == 0x0D) && (cover_art[5] == 0x0A) && (cover_art[6] == 0x1A) && (cover_art[7] == 0x0A)) { mime_type = "image/png"; } else { mime_type = NULL; } id3_field_setlatin1(field, (id3_latin1_t const*)mime_type); break; /* * Designate this as the front cover. */ case (ID3_FIELD_TYPE_INT8): id3_field_setint(field, PICTURE_TYPE_FRONT_COVER); break; /* * Set the image data. */ case (ID3_FIELD_TYPE_BINARYDATA): id3_field_setbinarydata(field, cover_art, cover_size); break; default: break; } index++; field = id3_frame_field(frame, index); } /* * Attach the frame to the tag. */ status = id3_tag_attachframe(tag, frame); if (status != 0) { BarUiMsg(settings, MSG_ERR, "Failed to attach cover art frame.\n"); goto error; } goto end; error: if (frame != NULL) { id3_frame_delete(frame); } exit_status = -1; end: return exit_status; }
/* search music: query, search request, return music id * @param app handle * @param seed suggestion station * @param seed suggestion musicid * @param prompt string * @return musicId or NULL on abort/error */ char *BarUiSelectMusicId (BarApp_t *app, PianoStation_t *station, const char *msg) { char *musicId = NULL; char lineBuf[100], selectBuf[2]; PianoSearchResult_t searchResult; PianoArtist_t *tmpArtist; PianoSong_t *tmpSong; BarUiMsg (&app->settings, MSG_QUESTION, msg); if (BarReadlineStr (lineBuf, sizeof (lineBuf), &app->input, BAR_RL_DEFAULT) > 0) { PianoReturn_t pRet; WaitressReturn_t wRet; PianoRequestDataSearch_t reqData; reqData.searchStr = lineBuf; BarUiMsg (&app->settings, MSG_INFO, "Searching... "); if (!BarUiPianoCall (app, PIANO_REQUEST_SEARCH, &reqData, &pRet, &wRet)) { return NULL; } memcpy (&searchResult, &reqData.searchResult, sizeof (searchResult)); BarUiMsg (&app->settings, MSG_NONE, "\r"); if (searchResult.songs != NULL && searchResult.artists != NULL) { /* songs and artists found */ BarUiMsg (&app->settings, MSG_QUESTION, "Is this an [a]rtist or [t]rack name? "); BarReadline (selectBuf, sizeof (selectBuf), "at", &app->input, BAR_RL_FULLRETURN, -1); if (*selectBuf == 'a') { tmpArtist = BarUiSelectArtist (app, searchResult.artists); if (tmpArtist != NULL) { musicId = strdup (tmpArtist->musicId); } } else if (*selectBuf == 't') { tmpSong = BarUiSelectSong (&app->settings, searchResult.songs, &app->input); if (tmpSong != NULL) { musicId = strdup (tmpSong->musicId); } } } else if (searchResult.songs != NULL) { /* songs found */ tmpSong = BarUiSelectSong (&app->settings, searchResult.songs, &app->input); if (tmpSong != NULL) { musicId = strdup (tmpSong->musicId); } } else if (searchResult.artists != NULL) { /* artists found */ tmpArtist = BarUiSelectArtist (app, searchResult.artists); if (tmpArtist != NULL) { musicId = strdup (tmpArtist->musicId); } } else { BarUiMsg (&app->settings, MSG_INFO, "Nothing found...\n"); } PianoDestroySearchResult (&searchResult); } return musicId; }
/* player thread; for every song a new thread is started * @param audioPlayer structure * @return PLAYER_RET_* */ void *BarPlayerThread (void *data) { struct audioPlayer *player = data; char extraHeaders[32]; void *ret = PLAYER_RET_OK; #ifdef ENABLE_FAAD NeAACDecConfigurationPtr conf; #endif WaitressReturn_t wRet = WAITRESS_RET_ERR; /* init handles */ pthread_mutex_init (&player->pauseMutex, NULL); player->waith.data = (void *) player; /* extraHeaders will be initialized later */ player->waith.extraHeaders = extraHeaders; player->buffer = malloc (BAR_PLAYER_BUFSIZE); switch (player->audioFormat) { #ifdef ENABLE_FAAD case PIANO_AF_AACPLUS: player->aacHandle = NeAACDecOpen(); /* set aac conf */ conf = NeAACDecGetCurrentConfiguration(player->aacHandle); conf->outputFormat = FAAD_FMT_16BIT; conf->downMatrix = 1; NeAACDecSetConfiguration(player->aacHandle, conf); player->waith.callback = BarPlayerAACCb; break; #endif /* ENABLE_FAAD */ #ifdef ENABLE_MAD case PIANO_AF_MP3: mad_stream_init (&player->mp3Stream); mad_frame_init (&player->mp3Frame); mad_synth_init (&player->mp3Synth); player->waith.callback = BarPlayerMp3Cb; break; #endif /* ENABLE_MAD */ default: BarUiMsg (player->settings, MSG_ERR, "Unsupported audio format!\n"); ret = (void *) PLAYER_RET_HARDFAIL; goto cleanup; break; } player->mode = PLAYER_INITIALIZED; /* This loop should work around song abortions by requesting the * missing part of the song */ do { snprintf (extraHeaders, sizeof (extraHeaders), "Range: bytes=%zu-\r\n", player->bytesReceived); wRet = WaitressFetchCall (&player->waith); } while (wRet == WAITRESS_RET_PARTIAL_FILE || wRet == WAITRESS_RET_TIMEOUT || wRet == WAITRESS_RET_READ_ERR); switch (player->audioFormat) { #ifdef ENABLE_FAAD case PIANO_AF_AACPLUS: NeAACDecClose(player->aacHandle); free (player->sampleSize); break; #endif /* ENABLE_FAAD */ #ifdef ENABLE_MAD case PIANO_AF_MP3: mad_synth_finish (&player->mp3Synth); mad_frame_finish (&player->mp3Frame); mad_stream_finish (&player->mp3Stream); break; #endif /* ENABLE_MAD */ default: /* this should never happen */ assert (0); break; } if (player->aoError) { ret = (void *) PLAYER_RET_HARDFAIL; } /* Pandora sends broken audio url’s sometimes (“bad request”). ignore them. */ if (wRet != WAITRESS_RET_OK && wRet != WAITRESS_RET_CB_ABORT) { BarUiMsg (player->settings, MSG_ERR, "Cannot access audio file: %s\n", WaitressErrorToStr (wRet)); ret = (void *) PLAYER_RET_SOFTFAIL; } cleanup: ao_close (player->audioOutDevice); WaitressFree (&player->waith); free (player->buffer); player->mode = PLAYER_FINISHED_PLAYBACK; return ret; }
/* Excute external event handler * @param settings containing the cmdline * @param event type * @param current station * @param current song * @param piano error-code (PIANO_RET_OK if not applicable) * @param waitress error-code (WAITRESS_RET_OK if not applicable) */ void BarUiStartEventCmd (const BarSettings_t *settings, const char *type, const PianoStation_t *curStation, const PianoSong_t *curSong, const struct audioPlayer *player, PianoStation_t *stations, PianoReturn_t pRet, WaitressReturn_t wRet) { pid_t chld; int pipeFd[2]; if (settings->eventCmd == NULL) { /* nothing to do... */ return; } if (pipe (pipeFd) == -1) { BarUiMsg (settings, MSG_ERR, "Cannot create eventcmd pipe. (%s)\n", strerror (errno)); return; } chld = fork (); if (chld == 0) { /* child */ close (pipeFd[1]); dup2 (pipeFd[0], fileno (stdin)); execl (settings->eventCmd, settings->eventCmd, type, (char *) NULL); BarUiMsg (settings, MSG_ERR, "Cannot start eventcmd. (%s)\n", strerror (errno)); close (pipeFd[0]); exit (1); } else if (chld == -1) { BarUiMsg (settings, MSG_ERR, "Cannot fork eventcmd. (%s)\n", strerror (errno)); } else { /* parent */ int status; PianoStation_t *songStation = NULL; FILE *pipeWriteFd; close (pipeFd[0]); pipeWriteFd = fdopen (pipeFd[1], "w"); if (curSong != NULL && stations != NULL && curStation->isQuickMix) { songStation = PianoFindStationById (stations, curSong->stationId); } fprintf (pipeWriteFd, "artist=%s\n" "title=%s\n" "album=%s\n" "coverArt=%s\n" "stationName=%s\n" "songStationName=%s\n" "pRet=%i\n" "pRetStr=%s\n" "wRet=%i\n" "wRetStr=%s\n" "songDuration=%lu\n" "songPlayed=%lu\n" "rating=%i\n" "detailUrl=%s\n", curSong == NULL ? "" : curSong->artist, curSong == NULL ? "" : curSong->title, curSong == NULL ? "" : curSong->album, curSong == NULL ? "" : curSong->coverArt, curStation == NULL ? "" : curStation->name, songStation == NULL ? "" : songStation->name, pRet, PianoErrorToStr (pRet), wRet, WaitressErrorToStr (wRet), player->songDuration, player->songPlayed, curSong == NULL ? PIANO_RATE_NONE : curSong->rating, curSong == NULL ? "" : curSong->detailUrl ); if (stations != NULL) { /* send station list */ PianoStation_t **sortedStations = NULL; size_t stationCount; sortedStations = BarSortedStations (stations, &stationCount, settings->sortOrder); assert (sortedStations != NULL); fprintf (pipeWriteFd, "stationCount=%zd\n", stationCount); for (size_t i = 0; i < stationCount; i++) { const PianoStation_t *currStation = sortedStations[i]; fprintf (pipeWriteFd, "station%zd=%s\n", i, currStation->name); } free (sortedStations); } else { const char * const msg = "stationCount=0\n"; fwrite (msg, sizeof (*msg), strlen (msg), pipeWriteFd); } /* closes pipeFd[1] as well */ fclose (pipeWriteFd); /* wait to get rid of the zombie */ waitpid (chld, &status, 0); } }
int main (int argc, char **argv) { static BarApp_t app; pthread_t playerThread; /* FIXME: max path length? */ char ctlPath[1024]; FILE *ctlFd = NULL; struct timeval selectTimeout; int maxFd, selectFds[2]; fd_set readSet, readSetCopy; char buf = '\0'; /* terminal attributes _before_ we started messing around with ~ECHO */ struct termios termOrig; memset (&app, 0, sizeof (app)); /* save terminal attributes, before disabling echoing */ BarTermSave (&termOrig); BarTermSetEcho (0); BarTermSetBuffer (0); /* init some things */ ao_initialize (); PianoInit (&app.ph); WaitressInit (&app.waith); strncpy (app.waith.host, PIANO_RPC_HOST, sizeof (app.waith.host)-1); strncpy (app.waith.port, PIANO_RPC_PORT, sizeof (app.waith.port)-1); BarSettingsInit (&app.settings); BarSettingsRead (&app.settings); BarUiMsg (MSG_NONE, "Welcome to " PACKAGE "! Press %c for a list of commands.\n", app.settings.keys[BAR_KS_HELP]); /* init fds */ FD_ZERO(&readSet); selectFds[0] = fileno (stdin); FD_SET(selectFds[0], &readSet); maxFd = selectFds[0] + 1; BarGetXdgConfigDir (PACKAGE "/ctl", ctlPath, sizeof (ctlPath)); /* FIXME: why is r_+_ required? */ ctlFd = fopen (ctlPath, "r+"); if (ctlFd != NULL) { selectFds[1] = fileno (ctlFd); FD_SET(selectFds[1], &readSet); /* assuming ctlFd is always > stdin */ maxFd = selectFds[1] + 1; BarUiMsg (MSG_INFO, "Control fifo at %s opened\n", ctlPath); } if (app.settings.username == NULL) { char nameBuf[100]; BarUiMsg (MSG_QUESTION, "Username: "******"Password: "******"Login... "); if (!BarUiPianoCall (&app.ph, PIANO_REQUEST_LOGIN, &app.waith, &reqData, &pRet, &wRet)) { BarTermRestore (&termOrig); return 0; } } { PianoReturn_t pRet; WaitressReturn_t wRet; BarUiMsg (MSG_INFO, "Get stations... "); if (!BarUiPianoCall (&app.ph, PIANO_REQUEST_GET_STATIONS, &app.waith, NULL, &pRet, &wRet)) { BarTermRestore (&termOrig); return 0; } } /* try to get autostart station */ if (app.settings.autostartStation != NULL) { app.curStation = PianoFindStationById (app.ph.stations, app.settings.autostartStation); if (app.curStation == NULL) { BarUiMsg (MSG_ERR, "Error: Autostart station not found.\n"); } } /* no autostart? ask the user */ if (app.curStation == NULL) { app.curStation = BarUiSelectStation (&app.ph, "Select station: ", app.settings.sortOrder, stdin); } if (app.curStation != NULL) { BarUiPrintStation (app.curStation); } /* little hack, needed to signal: hey! we need a playlist, but don't * free anything (there is nothing to be freed yet) */ memset (&app.player, 0, sizeof (app.player)); while (!app.doQuit) { /* song finished playing, clean up things/scrobble song */ if (app.player.mode == PLAYER_FINISHED_PLAYBACK) { BarUiStartEventCmd (&app.settings, "songfinish", app.curStation, app.playlist, &app.player, PIANO_RET_OK, WAITRESS_RET_OK); /* FIXME: pthread_join blocks everything if network connection * is hung up e.g. */ void *threadRet; pthread_join (playerThread, &threadRet); /* don't continue playback if thread reports error */ if (threadRet != (void *) PLAYER_RET_OK) { app.curStation = NULL; } memset (&app.player, 0, sizeof (app.player)); } /* check whether player finished playing and start playing new * song */ if (app.player.mode >= PLAYER_FINISHED_PLAYBACK || app.player.mode == PLAYER_FREED) { if (app.curStation != NULL) { /* what's next? */ if (app.playlist != NULL) { if (app.settings.history != 0) { /* prepend song to history list */ PianoSong_t *tmpSong = app.songHistory; app.songHistory = app.playlist; /* select next song */ app.playlist = app.playlist->next; app.songHistory->next = tmpSong; /* limit history's length */ /* start with 1, so we're stopping at n-1 and have the * chance to set ->next = NULL */ unsigned int i = 1; tmpSong = app.songHistory; while (i < app.settings.history && tmpSong != NULL) { tmpSong = tmpSong->next; ++i; } /* if too many songs in history... */ if (tmpSong != NULL) { PianoSong_t *delSong = tmpSong->next; tmpSong->next = NULL; if (delSong != NULL) { PianoDestroyPlaylist (delSong); } } } else { /* don't keep history */ app.playlist = app.playlist->next; } } if (app.playlist == NULL) { PianoReturn_t pRet; WaitressReturn_t wRet; PianoRequestDataGetPlaylist_t reqData; reqData.station = app.curStation; reqData.format = app.settings.audioFormat; BarUiMsg (MSG_INFO, "Receiving new playlist... "); if (!BarUiPianoCall (&app.ph, PIANO_REQUEST_GET_PLAYLIST, &app.waith, &reqData, &pRet, &wRet)) { app.curStation = NULL; } else { app.playlist = reqData.retPlaylist; if (app.playlist == NULL) { BarUiMsg (MSG_INFO, "No tracks left.\n"); app.curStation = NULL; } } BarUiStartEventCmd (&app.settings, "stationfetchplaylist", app.curStation, app.playlist, &app.player, pRet, wRet); } /* song ready to play */ if (app.playlist != NULL) { BarUiPrintSong (app.playlist, app.curStation->isQuickMix ? PianoFindStationById (app.ph.stations, app.playlist->stationId) : NULL); if (app.playlist->audioUrl == NULL) { BarUiMsg (MSG_ERR, "Invalid song url.\n"); } else { /* setup player */ memset (&app.player, 0, sizeof (app.player)); WaitressInit (&app.player.waith); WaitressSetUrl (&app.player.waith, app.playlist->audioUrl); /* set up global proxy, player is NULLed on songfinish */ if (app.settings.proxy != NULL) { char tmpPath[2]; WaitressSplitUrl (app.settings.proxy, app.player.waith.proxyHost, sizeof (app.player.waith.proxyHost), app.player.waith.proxyPort, sizeof (app.player.waith.proxyPort), tmpPath, sizeof (tmpPath)); } app.player.gain = app.playlist->fileGain; app.player.audioFormat = app.playlist->audioFormat; /* throw event */ BarUiStartEventCmd (&app.settings, "songstart", app.curStation, app.playlist, &app.player, PIANO_RET_OK, WAITRESS_RET_OK); /* prevent race condition, mode must _not_ be FREED if * thread has been started */ app.player.mode = PLAYER_STARTING; /* start player */ pthread_create (&playerThread, NULL, BarPlayerThread, &app.player); } /* end if audioUrl == NULL */ } /* end if playlist != NULL */ } /* end if curStation != NULL */ } /* select modifies its arguments => copy the set */ memcpy (&readSetCopy, &readSet, sizeof (readSet)); selectTimeout.tv_sec = 1; selectTimeout.tv_usec = 0; /* in the meantime: wait for user actions */ if (select (maxFd, &readSetCopy, NULL, NULL, &selectTimeout) > 0) { FILE *curFd = NULL; if (FD_ISSET(selectFds[0], &readSetCopy)) { curFd = stdin; } else if (FD_ISSET(selectFds[1], &readSetCopy)) { curFd = ctlFd; } buf = fgetc (curFd); size_t i; for (i = 0; i < BAR_KS_COUNT; i++) { if (app.settings.keys[i] == buf) { static const BarKeyShortcutFunc_t idToF[] = {BarUiActHelp, BarUiActLoveSong, BarUiActBanSong, BarUiActAddMusic, BarUiActCreateStation, BarUiActDeleteStation, BarUiActExplain, BarUiActStationFromGenre, BarUiActHistory, BarUiActSongInfo, BarUiActAddSharedStation, BarUiActMoveSong, BarUiActSkipSong, BarUiActPause, BarUiActQuit, BarUiActRenameStation, BarUiActSelectStation, BarUiActTempBanSong, BarUiActPrintUpcoming, BarUiActSelectQuickMix, BarUiActDebug, BarUiActBookmark}; idToF[i] (&app, curFd); break; } } } /* show time */ if (app.player.mode >= PLAYER_SAMPLESIZE_INITIALIZED && app.player.mode < PLAYER_FINISHED_PLAYBACK) { /* Ugly: songDuration is unsigned _long_ int! Lets hope this won't * overflow */ int songRemaining = (signed long int) (app.player.songDuration - app.player.songPlayed) / BAR_PLAYER_MS_TO_S_FACTOR; char pos = 0; if (songRemaining < 0) { /* Use plus sign if song is longer than expected */ pos = 1; songRemaining = -songRemaining; } BarUiMsg (MSG_TIME, "%c%02i:%02i/%02i:%02i\r", (pos ? '+' : '-'), songRemaining / 60, songRemaining % 60, app.player.songDuration / BAR_PLAYER_MS_TO_S_FACTOR / 60, app.player.songDuration / BAR_PLAYER_MS_TO_S_FACTOR % 60); } } /* destroy everything (including the world...) */ if (app.player.mode != PLAYER_FREED) { pthread_join (playerThread, NULL); } if (ctlFd != NULL) { fclose (ctlFd); } PianoDestroy (&app.ph); PianoDestroyPlaylist (app.songHistory); PianoDestroyPlaylist (app.playlist); ao_shutdown(); BarSettingsDestroy (&app.settings); /* restore terminal attributes, zsh doesn't need this, bash does... */ BarTermRestore (&termOrig); return 0; }