diff options
-rw-r--r-- | .github/workflows/build.yml | 2 | ||||
-rw-r--r-- | ChangeLog | 6 | ||||
-rw-r--r-- | INSTALL | 34 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | README.rst | 36 | ||||
-rw-r--r-- | contrib/config-example | 1 | ||||
-rw-r--r-- | contrib/pianobar.1 | 14 | ||||
-rw-r--r-- | src/config.h | 2 | ||||
-rw-r--r-- | src/libpiano/response.c | 27 | ||||
-rw-r--r-- | src/player.c | 27 | ||||
-rw-r--r-- | src/ui.c | 84 | ||||
-rw-r--r-- | src/ui_act.c | 8 |
12 files changed, 151 insertions, 94 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69aeefd..536a7d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -1,3 +1,9 @@ +Release 2024.12.21 + +- Improved network error retrying +- Improved compatibility with newer versions of ffmpeg and libcurl +- Minor documentation fixes + Release 2022.04.01 - Not a joke diff --git a/INSTALL b/INSTALL deleted file mode 100644 index 2d36705..0000000 --- a/INSTALL +++ /dev/null @@ -1,34 +0,0 @@ -Install -======= - -Dependencies ------------- - -- gmake -- pthreads -- libao -- libcurl -- gcrypt[1] -- json-c -- ffmpeg>=3.3 [2] -- UTF-8 console/locale - -[1] with blowfish cipher enabled -[2] required: demuxer mov, decoder aac, protocol http and filters volume, - aformat, aresample - -Building --------- - -Edit the Makefile and then type - - gmake clean && gmake - -You can run the client directly from the source directory now - - ./pianobar - -Or install it by issuing - - gmake install - @@ -52,8 +52,8 @@ LIBAV_LDFLAGS:=$(shell $(PKG_CONFIG) --libs libavcodec libavformat libavutil lib LIBCURL_CFLAGS:=$(shell $(PKG_CONFIG) --cflags libcurl) LIBCURL_LDFLAGS:=$(shell $(PKG_CONFIG) --libs libcurl) -LIBGCRYPT_CFLAGS:= -LIBGCRYPT_LDFLAGS:=-lgcrypt +LIBGCRYPT_CFLAGS:=$(shell $(PKG_CONFIG) --cflags libgcrypt) +LIBGCRYPT_LDFLAGS:=$(shell $(PKG_CONFIG) --libs libgcrypt) LIBJSONC_CFLAGS:=$(shell $(PKG_CONFIG) --cflags json-c 2>/dev/null || $(PKG_CONFIG) --cflags json) LIBJSONC_LDFLAGS:=$(shell $(PKG_CONFIG) --libs json-c 2>/dev/null || $(PKG_CONFIG) --libs json) @@ -33,10 +33,11 @@ and \*BSD as well as a `native Windows port`_. .. _homebrew: http://brew.sh/ .. _native Windows Port: https://github.com/thedmd/pianobar-windows -The current pianobar release is 2022.04.01_ (sha256__, sign__). More recent and +The current pianobar release is 2024.12.21_ (sha256__, sign__). More recent and experimental code is available at GitHub_ and the local gitweb_. Older releases are available here: +- 2022.04.01_ (sha256__, sign__) - 2020.11.28_ (sha256__, sign__) - 2020.04.05_ (sha256__, sign__) - 2019.02.14_ (sha256__, sign__) @@ -67,6 +68,9 @@ are available here: - 2010.10.07_ (sha1__) - 2010.08.21_ (sha1__) +.. _2024.12.21: https://6xq.net/pianobar/pianobar-2024.12.21.tar.bz2 +__ https://6xq.net/pianobar/pianobar-2024.12.21.tar.bz2.sha256 +__ https://6xq.net/pianobar/pianobar-2024.12.21.tar.bz2.asc .. _2022.04.01: https://6xq.net/pianobar/pianobar-2022.04.01.tar.bz2 __ https://6xq.net/pianobar/pianobar-2022.04.01.tar.bz2.sha256 __ https://6xq.net/pianobar/pianobar-2022.04.01.tar.bz2.asc @@ -157,6 +161,36 @@ __ https://6xq.net/pianobar/pianobar-2010.10.07.tar.bz2.sha1 .. _2010.08.21: https://6xq.net/pianobar/pianobar-2010.08.21.tar.bz2 __ https://6xq.net/pianobar/pianobar-2010.08.21.tar.bz2.sha1 +Install +------- + +You need the following software to build pianobar: + +- GNU make +- pthreads +- libao +- libcurl ≥ 7.32.0 +- gcrypt [1]_ +- json-c +- ffmpeg ≤ 5.1 [2]_ +- UTF-8 console/locale + +.. [1] with blowfish cipher enabled +.. [2] required: demuxer mov, decoder aac, protocol http and filters volume, + aformat, aresample + +Then type:: + + gmake clean && gmake + +You can run the client directly from the source directory now:: + + ./pianobar + +Or install it to ``/usr/local`` by issuing:: + + gmake install + FAQ --- diff --git a/contrib/config-example b/contrib/config-example index 5f5dc2d..060fbcb 100644 --- a/contrib/config-example +++ b/contrib/config-example @@ -23,7 +23,6 @@ #act_stationaddbygenre = g #act_songinfo = i #act_addshared = j -#act_songmove = m #act_songnext = n #act_songpause = S #act_songpausetoggle = p diff --git a/contrib/pianobar.1 b/contrib/pianobar.1 index c5b82aa..887ae4a 100644 --- a/contrib/pianobar.1 +++ b/contrib/pianobar.1 @@ -103,10 +103,6 @@ beginning. Delete artist/song seeds or feedback. .TP -.B act_songmove = m -Move current song to another station - -.TP .B act_songnext = n Skip current song. @@ -449,13 +445,13 @@ can report certain "events" to an external application (see information like error code and description, was well as song information related to the current event, is supplied through stdin. -Currently supported events are: artistbookmark, songban, songbookmark, -songexplain, songfinish, songlove, songmove, songshelf, songstart, +Currently supported events are: artistbookmark, settingschange, settingsget, +songban, songbookmark, songexplain, songfinish, songlove, songshelf, songstart, stationaddgenre, stationaddmusic, stationaddshared, stationcreate, stationdelete, stationdeleteartistseed, stationdeletefeedback, -stationdeletesongseed, stationfetchinfo, stationfetchplaylist, -stationfetchgenre stationquickmixtoggle, stationrename, userlogin, -usergetstations +stationdeletesongseed, stationdeletestationseed, stationfetchgenre, +stationfetchinfo, stationfetchplaylist, stationgetmodes, stationquickmixtoggle, +stationrename, stationsetmode, usergetstations, userlogin An example script can be found in the contrib/ directory of .B pianobar's diff --git a/src/config.h b/src/config.h index f3d3d3e..850c4aa 100644 --- a/src/config.h +++ b/src/config.h @@ -3,7 +3,7 @@ /* package name */ #define PACKAGE "pianobar" -#define VERSION "2022.04.01-dev" +#define VERSION "2024.12.21-dev" /* glibc feature test macros, define _before_ including other files */ #define _POSIX_C_SOURCE 200809L diff --git a/src/libpiano/response.c b/src/libpiano/response.c index 1e79261..0be8872 100644 --- a/src/libpiano/response.c +++ b/src/libpiano/response.c @@ -206,7 +206,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { break; } - for (int i = 0; i < json_object_array_length (stations); i++) { + for (unsigned int i = 0; i < json_object_array_length (stations); i++) { PianoStation_t *tmpStation; json_object *s = json_object_array_get_idx (stations, i); @@ -229,7 +229,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { if (mix != NULL) { PianoStation_t *curStation = ph->stations; PianoListForeachP (curStation) { - for (int i = 0; i < json_object_array_length (mix); i++) { + for (unsigned int i = 0; i < json_object_array_length (mix); i++) { json_object *id = json_object_array_get_idx (mix, i); if (strcmp (json_object_get_string (id), curStation->id) == 0) { @@ -256,7 +256,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { } assert (items != NULL); - for (int i = 0; i < json_object_array_length (items); i++) { + for (unsigned int i = 0; i < json_object_array_length (items); i++) { json_object *s = json_object_array_get_idx (items, i); PianoSong_t *song; @@ -377,7 +377,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { /* get artists */ json_object *artists; if (json_object_object_get_ex (result, "artists", &artists)) { - for (int i = 0; i < json_object_array_length (artists); i++) { + for (unsigned int i = 0; i < json_object_array_length (artists); i++) { json_object *a = json_object_array_get_idx (artists, i); PianoArtist_t *artist; @@ -396,7 +396,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { /* get songs */ json_object *songs; if (json_object_object_get_ex (result, "songs", &songs)) { - for (int i = 0; i < json_object_array_length (songs); i++) { + for (unsigned int i = 0; i < json_object_array_length (songs); i++) { json_object *s = json_object_array_get_idx (songs, i); PianoSong_t *song; @@ -456,7 +456,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { /* get genre stations */ json_object *categories; if (json_object_object_get_ex (result, "categories", &categories)) { - for (int i = 0; i < json_object_array_length (categories); i++) { + for (unsigned int i = 0; i < json_object_array_length (categories); i++) { json_object *c = json_object_array_get_idx (categories, i); PianoGenreCategory_t *tmpGenreCategory; @@ -471,7 +471,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { /* get genre subnodes */ json_object *stations; if (json_object_object_get_ex (c, "stations", &stations)) { - for (int k = 0; + for (unsigned int k = 0; k < json_object_array_length (stations); k++) { json_object *s = json_object_array_get_idx (stations, k); @@ -520,12 +520,13 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { assert (reqData != NULL); json_object *explanations; - if (json_object_object_get_ex (result, "explanations", &explanations)) { + if (json_object_object_get_ex (result, "explanations", &explanations) && + json_object_array_length (explanations) > 0) { reqData->retExplain = malloc (strSize * sizeof (*reqData->retExplain)); strncpy (reqData->retExplain, "We're playing this track " "because it features ", strSize); - for (int i = 0; i < json_object_array_length (explanations); i++) { + for (unsigned int i = 0; i < json_object_array_length (explanations); i++) { json_object *e = json_object_array_get_idx (explanations, i); json_object *f; @@ -573,7 +574,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { /* songs */ json_object *songs; if (json_object_object_get_ex (music, "songs", &songs)) { - for (int i = 0; i < json_object_array_length (songs); i++) { + for (unsigned int i = 0; i < json_object_array_length (songs); i++) { json_object *s = json_object_array_get_idx (songs, i); PianoSong_t *seedSong; @@ -594,7 +595,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { /* artists */ json_object *artists; if (json_object_object_get_ex (music, "artists", &artists)) { - for (int i = 0; i < json_object_array_length (artists); i++) { + for (unsigned int i = 0; i < json_object_array_length (artists); i++) { json_object *a = json_object_array_get_idx (artists, i); PianoArtist_t *seedArtist; @@ -622,7 +623,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { continue; } assert (json_object_is_type (val, json_type_array)); - for (int i = 0; i < json_object_array_length (val); i++) { + for (unsigned int i = 0; i < json_object_array_length (val); i++) { json_object *s = json_object_array_get_idx (val, i); PianoSong_t *feedbackSong; @@ -665,7 +666,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { json_object *availableModes; if (json_object_object_get_ex (result, "availableModes", &availableModes)) { - for (int i = 0; i < json_object_array_length (availableModes); i++) { + for (unsigned int i = 0; i < json_object_array_length (availableModes); i++) { json_object *val = json_object_array_get_idx (availableModes, i); assert (json_object_is_type (val, json_type_object)); diff --git a/src/player.c b/src/player.c index 875f473..bded009 100644 --- a/src/player.c +++ b/src/player.c @@ -235,7 +235,7 @@ static bool openStream (player_t * const player) { softfail ("avcodec_parameters_to_context"); } - AVCodec * const decoder = avcodec_find_decoder (cp->codec_id); + const AVCodec * const decoder = avcodec_find_decoder (cp->codec_id); if (decoder == NULL) { softfail ("find_decoder"); } @@ -282,11 +282,13 @@ static bool openFilter (player_t * const player) { /* abuffer */ AVRational time_base = player->st->time_base; + char channelLayout[128]; + av_channel_layout_describe(&player->cctx->ch_layout, channelLayout, sizeof(channelLayout)); snprintf (strbuf, sizeof (strbuf), - "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=%s", time_base.num, time_base.den, cp->sample_rate, av_get_sample_fmt_name (player->cctx->sample_fmt), - cp->channel_layout); + channelLayout); if ((ret = avfilter_graph_create_filter (&player->fabuf, avfilter_get_by_name ("abuffer"), "source", strbuf, NULL, player->fgraph)) < 0) { @@ -340,7 +342,7 @@ static bool openDevice (player_t * const player) { memset (&aoFmt, 0, sizeof (aoFmt)); aoFmt.bits = av_get_bytes_per_sample (avformat) * 8; assert (aoFmt.bits > 0); - aoFmt.channels = cp->channels; + aoFmt.channels = cp->ch_layout.nb_channels; aoFmt.rate = getSampleRate (player); aoFmt.byte_format = AO_FMT_NATIVE; @@ -431,8 +433,12 @@ static int play (player_t * const player) { } else if (ret < 0) { /* error, abort */ /* mark the EOF, so that BarAoPlayThread can quit*/ - debugPrint (DEBUG_AUDIO, "av_read_frame failed with code %i, sending " - "NULL frame\n", ret); + char error[AV_ERROR_MAX_STRING_SIZE]; + if (av_strerror(ret, error, sizeof(error)) < 0) { + strncpy (error, "(unknown)", sizeof(error)-1); + } + debugPrint (DEBUG_AUDIO, "av_read_frame failed with code %i (%s), " + "sending NULL frame\n", ret, error); pthread_mutex_lock (&player->aoplayLock); const int rt = av_buffersrc_add_frame (player->fabuf, NULL); assert (rt == 0); @@ -508,7 +514,7 @@ static void finish (player_t * const player) { player->fgraph = NULL; } if (player->cctx != NULL) { - avcodec_close (player->cctx); + avcodec_free_context (&player->cctx); player->cctx = NULL; } if (player->fctx != NULL) { @@ -533,7 +539,9 @@ void *BarPlayerThread (void *data) { if (openFilter (player) && openDevice (player)) { changeMode (player, PLAYER_PLAYING); BarPlayerSetVolume (player); - retry = play (player) == AVERROR_INVALIDDATA && + const int ret = play (player); + retry = (ret == AVERROR_INVALIDDATA || + ret == -ECONNRESET) && !player->interrupted; } else { /* filter missing or audio device busy */ @@ -583,8 +591,7 @@ void *BarAoPlayThread (void *data) { } pthread_mutex_unlock (&player->aoplayLock); - const int numChannels = av_get_channel_layout_nb_channels ( - filteredFrame->channel_layout); + const int numChannels = filteredFrame->ch_layout.nb_channels; const int bps = av_get_bytes_per_sample (filteredFrame->format); ao_play (player->aoDev, (char *) filteredFrame->data[0], filteredFrame->nb_samples * numChannels * bps); @@ -161,8 +161,8 @@ static size_t httpFetchCb (char *ptr, size_t size, size_t nmemb, /* libcurl progress callback. aborts the current request if user pressed ^C */ -int progressCb (void * const data, double dltotal, double dlnow, - double ultotal, double ulnow) { +int progressCb (void * const data, curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow) { const sig_atomic_t lint = *((sig_atomic_t *) data); if (lint) { return 1; @@ -224,8 +224,8 @@ static CURLcode BarPianoHttpRequest (CURL * const http, setAndCheck (CURLOPT_POSTFIELDS, req->postData); setAndCheck (CURLOPT_WRITEFUNCTION, httpFetchCb); setAndCheck (CURLOPT_WRITEDATA, &buffer); - setAndCheck (CURLOPT_PROGRESSFUNCTION, progressCb); - setAndCheck (CURLOPT_PROGRESSDATA, &lint); + setAndCheck (CURLOPT_XFERINFOFUNCTION, progressCb); + setAndCheck (CURLOPT_XFERINFODATA, &lint); setAndCheck (CURLOPT_NOPROGRESS, 0); setAndCheck (CURLOPT_POST, 1); setAndCheck (CURLOPT_TIMEOUT, settings->timeout); @@ -844,6 +844,49 @@ size_t BarUiListSongs (const BarApp_t * const app, return i; } +enum { + NO_DURATION = 0, +}; +#define NO_POSTFIX "" + +/* Print song information to the eventcmd stream + * @param Event command stream. + * @param Song information. + * @param Printed key name postfix, use NO_POSTFIX to print bare keys. + * @param Override song length from song parameter, use NO_DURATION if unavailable. + */ +static void BarUiEventcmdPrintSong (FILE * restrict stream, + const PianoSong_t * const song, const char * const postfix, + const unsigned int songDuration) { + assert (song != NULL); + assert (stream != NULL); + assert (postfix != NULL); + + fprintf (stream, + "artist%s=%s\n" + "title%s=%s\n" + "album%s=%s\n" + "coverArt%s=%s\n" + "rating%s=%i\n" + "detailUrl%s=%s\n" + "songDuration%s=%u\n", + postfix, + song->artist, + postfix, + song->title, + postfix, + song->album, + postfix, + song->coverArt, + postfix, + song->rating, + postfix, + song->detailUrl, + postfix, + songDuration == NO_DURATION ? song->length : songDuration + ); +} + /* Excute external event handler * @param settings containing the cmdline * @param event type @@ -900,36 +943,37 @@ void BarUiStartEventCmd (const BarSettings_t *settings, const char *type, pthread_mutex_unlock (&player->lock); 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=%u\n" - "songPlayed=%u\n" - "rating=%i\n" - "detailUrl=%s\n", - curSong == NULL ? "" : curSong->artist, - curSong == NULL ? "" : curSong->title, - curSong == NULL ? "" : curSong->album, - curSong == NULL ? "" : curSong->coverArt, + "songPlayed=%u\n", curStation == NULL ? "" : curStation->name, songStation == NULL ? "" : songStation->name, pRet, PianoErrorToStr (pRet), wRet, curl_easy_strerror (wRet), - songDuration, - songPlayed, - curSong == NULL ? PIANO_RATE_NONE : curSong->rating, - curSong == NULL ? "" : curSong->detailUrl + songPlayed ); + if (curSong != NULL) { + BarUiEventcmdPrintSong (pipeWriteFd, curSong, NO_POSTFIX, songDuration); + } + + const PianoSong_t *nextSong = PianoListNextP (curSong); + if (nextSong != NULL) { + unsigned int i = 0; + PianoListForeachP (nextSong) { + char postfix[16]; + snprintf (postfix, sizeof(postfix)-1, "Next%i", i); + BarUiEventcmdPrintSong (pipeWriteFd, nextSong, postfix, NO_DURATION); + i++; + } + } + if (stations != NULL) { /* send station list */ PianoStation_t **sortedStations = NULL; diff --git a/src/ui_act.c b/src/ui_act.c index 30bd4e5..fa5c43b 100644 --- a/src/ui_act.c +++ b/src/ui_act.c @@ -271,8 +271,12 @@ BarUiActCallback(BarUiActExplain) { BarUiMsg (&app->settings, MSG_INFO, "Receiving explanation... "); if (BarUiActDefaultPianoCall (PIANO_REQUEST_EXPLAIN, &reqData)) { - BarUiMsg (&app->settings, MSG_INFO, "%s\n", reqData.retExplain); - free (reqData.retExplain); + if (reqData.retExplain == NULL) { + BarUiMsg (&app->settings, MSG_ERR, "No explanation provided.\n"); + } else { + BarUiMsg (&app->settings, MSG_INFO, "%s\n", reqData.retExplain); + free (reqData.retExplain); + } } BarUiActDefaultEventcmd ("songexplain"); } |