diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ca268a2d..6efc0f56c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ rearrangements of Notcurses. `notcurses/ncerrs.h`. * CMake no longer uses the `USE_FFMPEG` option. Instead, the `USE_MULTIMEDIA` option can be defined as `ffmpeg`, `oiio`, or `none`. In `cmake-gui`, this - item will now appear as an option selector. + item will now appear as an option selector. `oiio` selects OpenImageIO. * 1.3.2 (2020-04-19) * `ncdirect_cursor_push()`, `notcurses_cursor_pop()`, and diff --git a/CMakeLists.txt b/CMakeLists.txt index f29869484..6da3997a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,8 @@ if(${USE_MULTIMEDIA} STREQUAL "ffmpeg") set(USE_FFMPEG ON) elseif(${USE_MULTIMEDIA} STREQUAL "oiio") set(USE_OIIO ON) +elseif(NOT ${USE_MULTIMEDIA} STREQUAL "none") + message(FATAL_ERROR "USE_MULTIMEDIA must be one of 'oiio', 'ffmpeg', 'none' (was '${USE_MULTIMEDIA}')") endif() find_package(PkgConfig REQUIRED) @@ -504,8 +506,8 @@ target_compile_definitions(notcurses-tetris ) # notcurses-view +if(${USE_FFMPEG} OR ${USE_OIIO}) file(GLOB VIEWSRCS CONFIGURE_DEPENDS src/view/*.cpp) -if(${USE_FFMPEG}) add_executable(notcurses-view ${VIEWSRCS}) target_include_directories(notcurses-view PRIVATE @@ -696,7 +698,7 @@ install(FILES DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) -if(${USE_FFMPEG}) +if(${USE_FFMPEG} OR ${USE_OIIO}) file(GLOB TESTDATA CONFIGURE_DEPENDS data/*) # Don't install source materia for self-originated multimedia list(FILTER TESTDATA EXCLUDE REGEX ".*xcf$") @@ -717,7 +719,7 @@ if(${USE_TESTS}) install(TARGETS notcurses-tester DESTINATION bin) endif() install(TARGETS notcurses-tetris DESTINATION bin) -if(${USE_FFMPEG}) +if(${USE_FFMPEG} OR ${USE_OIIO}) install(TARGETS notcurses-view DESTINATION bin) endif() install(TARGETS notcurses notcurses-static diff --git a/doc/man/man3/notcurses_visual.3.md b/doc/man/man3/notcurses_visual.3.md index 06c8de7dc..4ab78baea 100644 --- a/doc/man/man3/notcurses_visual.3.md +++ b/doc/man/man3/notcurses_visual.3.md @@ -57,7 +57,7 @@ result in a zero-area rendering. # RETURN VALUES **notcurses_canopen** returns true if this functionality is enabled, or false -if Notcurses was not built with FFmpeg support. **ncplane_visual_open** and +if Notcurses was not built with multimedia support. **ncplane_visual_open** and **ncvisual_open_plane** return an **ncvisual** object on success, or **NULL** on failure. Success from these functions indicates that the specified **file** was opened, and enough data was read to make a firm codec identification. It diff --git a/src/lib/blit.c b/src/lib/blit.c index 6c4ebbd4a..5e527be15 100644 --- a/src/lib/blit.c +++ b/src/lib/blit.c @@ -5,6 +5,7 @@ static inline bool ffmpeg_trans_p(bool bgr, unsigned char alpha){ if(!bgr && alpha < 192){ +//fprintf(stderr, "TRANSPARENT!\n"); return true; } return false; diff --git a/src/lib/libav.c b/src/lib/libav.c index 337f9fc23..a4c2c4f59 100644 --- a/src/lib/libav.c +++ b/src/lib/libav.c @@ -37,11 +37,11 @@ typedef struct ncvisual { #endif } ncvisual; +#ifdef USE_FFMPEG ncplane* ncvisual_plane(ncvisual* ncv){ return ncv->ncp; } -#ifdef USE_FFMPEG void ncvisual_destroy(ncvisual* ncv){ if(ncv){ avcodec_close(ncv->codecctx); diff --git a/src/lib/oiio.cpp b/src/lib/oiio.cpp index ea60f51a7..e7c15c8a0 100644 --- a/src/lib/oiio.cpp +++ b/src/lib/oiio.cpp @@ -4,13 +4,12 @@ #include #include "internal.h" -extern "C" { - typedef struct ncvisual { int packet_outstanding; int dstwidth, dstheight; float timescale; // scale frame duration by this value std::unique_ptr image; // must be close()d + std::unique_ptr frame; ncplane* ncp; // if we're creating the plane based off the first frame's dimensions, these // describe where the plane ought be placed, and how it ought be sized. this @@ -20,6 +19,12 @@ typedef struct ncvisual { struct notcurses* ncobj; // set iff this ncvisual "owns" its ncplane } ncvisual; +extern "C" { + +ncplane* ncvisual_plane(ncvisual* ncv){ + return ncv->ncp; +} + bool notcurses_canopen(const notcurses* nc __attribute__ ((unused))){ return true; } @@ -38,6 +43,7 @@ ncvisual_create(float timescale){ ret->placex = ret->placey = 0; ret->style = NCSCALE_NONE; ret->ncobj = nullptr; + ret->frame = nullptr; return ret; } @@ -54,6 +60,9 @@ ncvisual_open(const char* filename, nc_err_e* err){ *err = NCERR_DECODE; return nullptr; } +/*const auto &spec = ncv->image->spec(); +std::cout << "Opened " << filename << ": " << spec.height << "x" << +spec.width << "@" << spec.nchannels << " (" << spec.format << ")" << std::endl;*/ return ncv; } @@ -67,6 +76,7 @@ ncvisual* ncplane_visual_open(ncplane* nc, const char* filename, nc_err_e* ncerr ncv->dstheight *= 2; ncv->ncp = nc; ncv->style = NCSCALE_STRETCH; + ncv->ncobj = nullptr; return ncv; } @@ -81,31 +91,158 @@ ncvisual* ncvisual_open_plane(notcurses* nc, const char* filename, ncv->style = style; ncv->ncobj = nc; ncv->ncp = nullptr; + ncv->ncobj = nc; return ncv; } nc_err_e ncvisual_decode(ncvisual* nc){ - (void)nc; // FIXME - return NCERR_DECODE; + if(nc->frame){ + return NCERR_EOF; // FIXME + } + const auto &spec = nc->image->spec(); + auto pixels = spec.width * spec.height;// * spec.nchannels; + if(spec.nchannels < 3 || spec.nchannels > 4){ + return NCERR_DECODE; // FIXME get some to test with + } + nc->frame = std::make_unique(pixels); + if(spec.nchannels == 3){ + std::fill(nc->frame.get(), nc->frame.get() + pixels, 0xfffffffful); + } + if(!nc->image->read_image(0, 0, 0, spec.nchannels, OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4), nc->frame.get(), 4)){ + return NCERR_DECODE; + } +/*for(int i = 0 ; i < pixels ; ++i){ + //fprintf(stderr, "%06d %02x %02x %02x %02x\n", i, + fprintf(stderr, "%06d %d %d %d %d\n", i, + (nc->frame[i]) & 0xff, + (nc->frame[i] >> 8) & 0xff, + (nc->frame[i] >> 16) & 0xff, + nc->frame[i] >> 24 + ); +}*/ + int rows, cols; + if(nc->ncp == nullptr){ // create plane + if(nc->style == NCSCALE_NONE){ + rows = spec.height / 2; + cols = spec.width; + }else{ // FIXME differentiate between scale/stretch + notcurses_term_dim_yx(nc->ncobj, &rows, &cols); + if(nc->placey >= rows || nc->placex >= cols){ + return NCERR_DECODE; + } + rows -= nc->placey; + cols -= nc->placex; + } + nc->dstwidth = cols; + nc->dstheight = rows * 2; + nc->ncp = ncplane_new(nc->ncobj, rows, cols, nc->placey, nc->placex, nullptr); + nc->placey = 0; + nc->placex = 0; + if(nc->ncp == nullptr){ + return NCERR_NOMEM; + } + }else{ // check for resize + ncplane_dim_yx(nc->ncp, &rows, &cols); + if(rows != nc->dstheight / 2 || cols != nc->dstwidth){ + nc->dstheight = rows * 2; + nc->dstwidth = cols; + } + } + return NCERR_SUCCESS; } int ncvisual_render(const ncvisual* ncv, int begy, int begx, int leny, int lenx){ - (void)ncv; - (void)begy; - (void)begx; - (void)leny; - (void)lenx; - return -1; +//fprintf(stderr, "render %dx%d+%dx%d\n", begy, begx, leny, lenx); + if(begy < 0 || begx < 0 || lenx < -1 || leny < -1){ + return -1; + } + if(ncv->frame == nullptr){ + return -1; + } + const auto &spec = ncv->image->spec(); +//fprintf(stderr, "render %d/%d to %dx%d+%dx%d\n", f->height, f->width, begy, begx, leny, lenx); + if(begx >= spec.width || begy >= spec.height){ + return -1; + } + if(lenx == -1){ // -1 means "to the end"; use all space available + lenx = spec.width - begx; + } + if(leny == -1){ + leny = spec.height - begy; + } + if(lenx == 0 || leny == 0){ // no need to draw zero-size object, exit + return 0; + } + if(begx + lenx > spec.width || begy + leny > spec.height){ + return -1; + } + int dimy, dimx; + ncplane_dim_yx(ncv->ncp, &dimy, &dimx); + ncplane_cursor_move_yx(ncv->ncp, 0, 0); + // y and x are actual plane coordinates. each row corresponds to two rows of + // the input (scaled) frame (columns are 1:1). we track the row of the + // visual via visy. +//fprintf(stderr, "render: %dx%d:%d+%d of %d/%d -> %dx%d\n", begy, begx, leny, lenx, f->height, f->width, dimy, dimx); + const int linesize = spec.width * 4; + int ret = ncblit_rgba(ncv->ncp, ncv->placey, ncv->placex, linesize, + ncv->frame.get(), begy, begx, leny, lenx); + //av_frame_unref(ncv->oframe); + return ret; } int ncvisual_stream(struct notcurses* nc, struct ncvisual* ncv, nc_err_e* ncerr, - float timespec, streamcb streamer, void* curry){ - (void)nc; - (void)ncv; - (void)ncerr; - (void)timespec; - (void)streamer; - (void)curry; + float timescale, streamcb streamer, void* curry){ + int frame = 1; + ncv->timescale = timescale; + struct timespec begin; // time we started + clock_gettime(CLOCK_MONOTONIC, &begin); + uint64_t nsbegin = timespec_to_ns(&begin); + bool usets = false; + // each frame has a pkt_duration in milliseconds. keep the aggregate, in case + // we don't have PTS available. + uint64_t sum_duration = 0; + while((*ncerr = ncvisual_decode(ncv)) == NCERR_SUCCESS){ + /* codecctx seems to be off by a factor of 2 regularly. instead, go with + // the time_base from the avformatctx. + double tbase = av_q2d(ncv->fmtctx->streams[ncv->stream_index]->time_base); + int64_t ts = ncv->oframe->best_effort_timestamp; + if(frame == 1 && ts){ + usets = true; + }*/ + if(ncvisual_render(ncv, 0, 0, -1, -1) < 0){ + return -1; + } + if(streamer){ + int r = streamer(nc, ncv, curry); + if(r){ + return r; + } + } + ++frame; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + uint64_t nsnow = timespec_to_ns(&now); + struct timespec interval; + /*uint64_t duration = ncv->oframe->pkt_duration * tbase * NANOSECS_IN_SEC; + sum_duration += (duration * ncv->timescale); +//fprintf(stderr, "use: %u dur: %ju ts: %ju cctx: %f fctx: %f\n", usets, duration, ts, av_q2d(ncv->codecctx->time_base), av_q2d(ncv->fmtctx->streams[ncv->stream_index]->time_base)); + double schedns = nsbegin; + if(usets){ + if(tbase == 0){ + tbase = duration; + } + schedns += ts * (tbase * ncv->timescale) * NANOSECS_IN_SEC; + }else{ + schedns += sum_duration; + } + if(nsnow < schedns){ + ns_to_timespec(schedns - nsnow, &interval); + nanosleep(&interval, nullptr); + }*/ + } + if(*ncerr == NCERR_EOF){ + return 0; + } return -1; } @@ -122,7 +259,10 @@ int ncvisual_init(int loglevel){ void ncvisual_destroy(ncvisual* ncv){ if(ncv){ ncv->image->close(); - free(ncv); + if(ncv->ncobj){ + ncplane_destroy(ncv->ncp); + } + delete ncv; } } diff --git a/src/poc/visual.cpp b/src/poc/visual.cpp index ddbd9e02f..303a2b6f5 100644 --- a/src/poc/visual.cpp +++ b/src/poc/visual.cpp @@ -6,7 +6,14 @@ #include int main(int argc, char** argv){ + const char* file = "../data/changes.jpg"; setlocale(LC_ALL, ""); + if(argc > 2){ + fprintf(stderr, "usage: visual [ file ]\n"); + return EXIT_FAILURE; + }else if(argc == 2){ + file = argv[1]; + } notcurses_options opts{}; opts.inhibit_alternate_screen = true; struct notcurses* nc; @@ -18,7 +25,7 @@ int main(int argc, char** argv){ ncplane_dim_yx(n, &dimy, &dimx); nc_err_e ncerr; - auto ncv = ncplane_visual_open(n, "../data/changes.jpg", &ncerr); + auto ncv = ncplane_visual_open(n, file, &ncerr); if(!ncv){ goto err; } diff --git a/src/view/view.cpp b/src/view/view.cpp index 0fdd2f9ee..2a1522068 100644 --- a/src/view/view.cpp +++ b/src/view/view.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -7,15 +8,8 @@ #include #include #include -#include -#include #include - -extern "C" { -#include -#include -#include // ffmpeg doesn't reliably "C"-guard itself -} +#include using namespace ncpp; @@ -170,7 +164,6 @@ int main(int argc, char** argv){ int dimy, dimx; nc.get_term_dim(&dimy, &dimx); for(auto i = nonopt ; i < argc ; ++i){ - std::array errbuf; int frames = 0; nc_err_e err; std::unique_ptr ncv; @@ -183,9 +176,8 @@ int main(int argc, char** argv){ } int r = ncv->stream(&err, timescale, perframe, &frames); if(r < 0){ // positive is intentional abort - av_make_error_string(errbuf.data(), errbuf.size(), err); nc.stop(); - std::cerr << "Error decoding " << argv[i] << ": " << errbuf.data() << std::endl; + std::cerr << "Error decoding " << argv[i] << ": " << nc_strerror(err) << std::endl; return EXIT_FAILURE; }else if(r == 0){ std::unique_ptr stdn(nc.get_stdplane()); diff --git a/tests/visual.cpp b/tests/visual.cpp index e936a8351..193a4270c 100644 --- a/tests/visual.cpp +++ b/tests/visual.cpp @@ -1,9 +1,4 @@ #include "main.h" -#ifdef USE_MULTIMEDIA -#include -#include -#include -#endif TEST_CASE("Multimedia") { if(getenv("TERM") == nullptr){ diff --git a/tools/notcurses.spec b/tools/notcurses.spec index 849d27232..f07c856ae 100644 --- a/tools/notcurses.spec +++ b/tools/notcurses.spec @@ -11,6 +11,9 @@ Source2: https://dank.qemfd.net/dankamongmen.gpg BuildRequires: gnupg2 BuildRequires: cmake BuildRequires: gcc-c++ +BuildRequires: libqrcodegen-devel +BuildRequires: OpenEXR-devel +BuildRequires: OpenImageIO-devel BuildRequires: pandoc BuildRequires: python3-devel BuildRequires: python3-cffi @@ -53,7 +56,7 @@ Python wrappers and a demonstration script for the notcurses library. %build mkdir build cd build -%cmake -DUSE_FFMPEG=off -DUSE_TESTS=off .. +%cmake -DUSE_MULTIMEDIA=oiio -DUSE_TESTS=off .. %make_build cd python %py3_build