From 91121856579e22aee7859593d6dd895ec60c34a3 Mon Sep 17 00:00:00 2001 From: Nick Black Date: Mon, 18 Jan 2021 14:10:04 -0500 Subject: [PATCH] Split up notcurses/notcurses-core (#1297) Extract `libnotcurses-core` from `libnotcurses`. The former contains everything except multimedia code. The latter contains multimedia stuff (a wrapper around FFmpeg or OIIO). If built with `-DUSE_MULTIMEDIA=none`, there will not be any `libnotcurses.so` generated. `libnotcurses.so` uses library constructors/destructors to insert its implementation into the `ncvisual` stack at runtime. Users linking `-lnotcurses` will get the full implementation; users linking `-lnotcurses-core` only will get the stack less multimedia code. The upshot of this is that someone can compile/install only `libnotcurses-core`, and a program linked against it will work just fine. This eliminates the need to install the full (large) dependency stack of the multimedia code unless necessary. This will hopefully be useful for e.g. installers etc. Closes #339. --- CMakeLists.txt | 243 +++++++++++++++++++------------- INSTALL.md | 16 ++- NEWS.md | 6 + README.md | 2 + USAGE.md | 15 +- data/tetris-background.jpg | Bin 67164 -> 23514 bytes doc/man/man3/notcurses.3.md | 8 +- src/demo/dragon.c | 1 - src/lib/blit.c | 7 - src/lib/ffmpeg.h | 72 ---------- src/lib/internal.h | 12 +- src/lib/notcurses.c | 14 +- src/lib/oiio.h | 38 ----- src/lib/visual-details.h | 58 ++++---- src/lib/visual.cpp | 162 ++++++++++++++++------ src/{lib => media}/ffmpeg.cpp | 253 +++++++++++++++++++++++----------- src/{lib => media}/oiio.cpp | 146 +++++++++++++------- tools/notcurses++.pc.in | 2 +- tools/notcurses-core.pc.in | 12 ++ tools/notcurses.pc.in | 2 +- 20 files changed, 613 insertions(+), 456 deletions(-) delete mode 100644 src/lib/ffmpeg.h delete mode 100644 src/lib/oiio.h rename src/{lib => media}/ffmpeg.cpp (63%) rename src/{lib => media}/oiio.cpp (55%) create mode 100644 tools/notcurses-core.pc.in diff --git a/CMakeLists.txt b/CMakeLists.txt index fa5c34b0b..232312259 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,12 @@ if("${USE_COVERAGE}") string(APPEND CMAKE_CXX_FLAGS_DEBUG " --coverage -fprofile-instr-generate -fcoverage-mapping") endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +set(PKGCONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +else() +set(PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/libdata/pkgconfig") +endif() + # global compiler flags add_compile_definitions(FORTIFY_SOURCE=2) add_compile_options(-Wall -Wextra -W -Wshadow -Wformat -fexceptions) @@ -114,32 +120,31 @@ endif() set_property(GLOBAL APPEND PROPERTY PACKAGES_FOUND qrcodegen) endif() find_library(LIBRT rt) +feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) file(GLOB COMPATSRC CONFIGURE_DEPENDS src/compat/compat.c) -# libnotcurses (core shared library, core static library) -file(GLOB NCSRCS CONFIGURE_DEPENDS src/lib/*.c src/lib/*.cpp) -add_library(notcurses SHARED ${NCSRCS} ${COMPATSRC}) +############################################################################ +# libnotcurses-core (core shared library, core static library) +file(GLOB NCCORESRCS CONFIGURE_DEPENDS src/lib/*.c src/lib/*.cpp) +add_library(notcurses-core SHARED ${NCCORESRCS} ${COMPATSRC}) if(${USE_STATIC}) -add_library(notcurses-static STATIC ${NCSRCS}) +add_library(notcurses-core-static STATIC ${NCCORESRCS}) else() -add_library(notcurses-static STATIC EXCLUDE_FROM_ALL ${NCSRCS}) +add_library(notcurses-core-static STATIC EXCLUDE_FROM_ALL ${NCCORESRCS}) endif() set_target_properties( - notcurses-static PROPERTIES - OUTPUT_NAME notcurses + notcurses-core-static PROPERTIES + OUTPUT_NAME notcurses-core ) - -feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) - -set_target_properties(notcurses PROPERTIES +set_target_properties(notcurses-core PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) -set_target_properties(notcurses-static PROPERTIES +set_target_properties(notcurses-core-static PROPERTIES VERSION ${PROJECT_VERSION} ) -target_include_directories(notcurses +target_include_directories(notcurses-core PRIVATE include src @@ -147,7 +152,7 @@ target_include_directories(notcurses "${TERMINFO_INCLUDE_DIRS}" "${READLINE_INCLUDE_DIRS}" ) -target_include_directories(notcurses-static +target_include_directories(notcurses-core-static PRIVATE include src @@ -155,7 +160,7 @@ target_include_directories(notcurses-static "${TERMINFO_STATIC_INCLUDE_DIRS}" "${READLINE_STATIC_INCLUDE_DIRS}" ) -target_link_libraries(notcurses +target_link_libraries(notcurses-core PRIVATE "${TERMINFO_LIBRARIES}" "${READLINE_LIBRARIES}" @@ -164,7 +169,7 @@ target_link_libraries(notcurses PUBLIC Threads::Threads ) -target_link_libraries(notcurses-static +target_link_libraries(notcurses-core-static PRIVATE "${TERMINFO_STATIC_LIBRARIES}" "${READLINE_STATIC_LIBRARIES}" @@ -173,32 +178,81 @@ target_link_libraries(notcurses-static PUBLIC Threads::Threads ) -target_link_directories(notcurses +target_link_directories(notcurses-core PRIVATE "${TERMINFO_LIBRARY_DIRS}" "${READLINE_LIBRARY_DIRS}" ) -target_link_directories(notcurses-static +target_link_directories(notcurses-core-static PRIVATE "${TERMINFO_STATIC_LIBRARY_DIRS}" "${READLINE_STATIC_LIBRARY_DIRS}" ) - +# don't want these on freebsd/dragonfly/osx +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +target_compile_definitions(notcurses-core + PUBLIC + _XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers + PRIVATE + _GNU_SOURCE _DEFAULT_SOURCE +) +target_compile_definitions(notcurses-core-static + PUBLIC + _XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers + PRIVATE + _GNU_SOURCE _DEFAULT_SOURCE +) +endif() if(${USE_QRCODEGEN}) -target_link_libraries(notcurses PRIVATE qrcodegen) -target_link_libraries(notcurses-static PRIVATE qrcodegen) +target_link_libraries(notcurses-core PRIVATE qrcodegen) +target_link_libraries(notcurses-core-static PRIVATE qrcodegen) endif() -if(${USE_FFMPEG}) +############################################################################ +# libnotcurses (multimedia shared library+static library) +file(GLOB NCSRCS CONFIGURE_DEPENDS src/media/*.c src/media/*.cpp) +add_library(notcurses SHARED ${NCSRCS} ${COMPATSRC}) +if(${USE_STATIC}) +add_library(notcurses-static STATIC ${NCSRCS}) +else() +add_library(notcurses-static STATIC EXCLUDE_FROM_ALL ${NCSRCS}) +endif() +set_target_properties( + notcurses-static PROPERTIES + OUTPUT_NAME notcurses +) target_include_directories(notcurses + PRIVATE + include + src + src/lib + "${PROJECT_BINARY_DIR}/include" +) +target_include_directories(notcurses-static + PRIVATE + include + src + src/lib + "${PROJECT_BINARY_DIR}/include" +) +target_link_libraries(notcurses + PUBLIC + notcurses-core +) +target_link_libraries(notcurses-static PUBLIC + notcurses-core-static +) +if(${USE_FFMPEG}) +target_include_directories(notcurses + PRIVATE "${AVCODEC_INCLUDE_DIRS}" "${AVFORMAT_INCLUDE_DIRS}" "${AVUTIL_INCLUDE_DIRS}" "${SWSCALE_INCLUDE_DIRS}" ) target_include_directories(notcurses-static - PUBLIC + PRIVATE "${AVCODEC_STATIC_INCLUDE_DIRS}" "${AVFORMAT_STATIC_INCLUDE_DIRS}" "${AVUTIL_STATIC_INCLUDE_DIRS}" @@ -209,7 +263,6 @@ target_link_libraries(notcurses "${AVCODEC_LIBRARIES}" "${AVFORMAT_LIBRARIES}" "${SWSCALE_LIBRARIES}" - PUBLIC "${AVUTIL_LIBRARIES}" ) target_link_libraries(notcurses-static @@ -217,7 +270,6 @@ target_link_libraries(notcurses-static "${AVCODEC_STATIC_LIBRARIES}" "${AVFORMAT_STATIC_LIBRARIES}" "${SWSCALE_STATIC_LIBRARIES}" - PUBLIC "${AVUTIL_STATIC_LIBRARIES}" ) target_link_directories(notcurses @@ -225,7 +277,6 @@ target_link_directories(notcurses "${AVCODEC_LIBRARY_DIRS}" "${AVFORMAT_LIBRARY_DIRS}" "${SWSCALE_LIBRARY_DIRS}" - PUBLIC "${AVUTIL_LIBRARY_DIRS}" ) target_link_directories(notcurses-static @@ -233,7 +284,6 @@ target_link_directories(notcurses-static "${AVCODEC_STATIC_LIBRARY_DIRS}" "${AVFORMAT_STATIC_LIBRARY_DIRS}" "${SWSCALE_STATIC_LIBRARY_DIRS}" - PUBLIC "${AVUTIL_STATIC_LIBRARY_DIRS}" ) elseif(${USE_OIIO}) @@ -245,28 +295,8 @@ target_link_directories(notcurses PRIVATE ${OIIO_LIBRARY_DIRS}) target_link_directories(notcurses-static PRIVATE ${OIIO_STATIC_LIBRARY_DIRS}) endif() -# don't want these on freebsd -if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") -target_compile_definitions(notcurses - PUBLIC - _XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers - PRIVATE - _GNU_SOURCE _DEFAULT_SOURCE -) -target_compile_definitions(notcurses-static - PUBLIC - _XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers - PRIVATE - _GNU_SOURCE _DEFAULT_SOURCE -) -set(PKGCONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig") -elseif(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") -set(PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/libdata/pkgconfig") -elseif(${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly") -set(PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/libdata/pkgconfig") -endif() - -# libnotcurses++ +############################################################################ +# libnotcurses++ (C++ wrappers) set(NCPP_SOURCES src/libcpp/FDPlane.cc src/libcpp/Menu.cc @@ -375,31 +405,6 @@ install(FILES ${NOTCURSES_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/notcu install(FILES ${NCPP_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ncpp) install(FILES ${NCPP_INTERNAL_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ncpp/internal) -# notcurses-demo -file(GLOB DEMOSRCS CONFIGURE_DEPENDS src/demo/*.c) -add_executable(notcurses-demo ${DEMOSRCS} ${COMPATSRC}) -target_compile_definitions(notcurses-demo - PRIVATE - _GNU_SOURCE -) -target_include_directories(notcurses-demo - PRIVATE - include - src - "${PROJECT_BINARY_DIR}/include" - PUBLIC - "${AVCODEC_INCLUDE_DIRS}" - "${AVFORMAT_INCLUDE_DIRS}" - "${AVUTIL_INCLUDE_DIRS}" - "${SWSCALE_INCLUDE_DIRS}" -) -target_link_libraries(notcurses-demo - PRIVATE - notcurses - unistring - ${MATH_LIBRARIES} -) - # tiny proofs of concept, one binary per source file if(USE_POC) file(GLOB POCSRCS CONFIGURE_DEPENDS src/poc/*.c src/poc/*.cpp) @@ -496,19 +501,35 @@ if(USE_DOXYGEN) endif() endif() -# ncneofetch -file(GLOB FETCHSRCS CONFIGURE_DEPENDS src/fetch/*.c) -add_executable(ncneofetch ${FETCHSRCS}) -target_include_directories(ncneofetch +############################################################################ +# notcurses-demo +file(GLOB DEMOSRCS CONFIGURE_DEPENDS src/demo/*.c) +add_executable(notcurses-demo ${DEMOSRCS} ${COMPATSRC}) +target_compile_definitions(notcurses-demo + PRIVATE + _GNU_SOURCE +) +target_include_directories(notcurses-demo PRIVATE include + src "${PROJECT_BINARY_DIR}/include" + PUBLIC + "${AVCODEC_INCLUDE_DIRS}" + "${AVFORMAT_INCLUDE_DIRS}" + "${AVUTIL_INCLUDE_DIRS}" + "${SWSCALE_INCLUDE_DIRS}" ) -target_link_libraries(ncneofetch +target_link_libraries(notcurses-demo PRIVATE notcurses + unistring + ${MATH_LIBRARIES} + PUBLIC + Threads::Threads ) +############################################################################ # notcurses-input file(GLOB INPUTSRCS CONFIGURE_DEPENDS src/input/input.cpp) add_executable(notcurses-input ${INPUTSRCS}) @@ -522,34 +543,53 @@ target_link_libraries(notcurses-input notcurses++ ) -# ncls -file(GLOB LSSRC CONFIGURE_DEPENDS src/ls/*.cpp) -add_executable(ncls ${LSSRC}) -target_include_directories(ncls +############################################################################ +# notcurses-tetris +file(GLOB TETRISSRC CONFIGURE_DEPENDS src/tetris/*.cpp) +add_executable(notcurses-tetris ${TETRISSRC}) +target_include_directories(notcurses-tetris PRIVATE include "${PROJECT_BINARY_DIR}/include" ) -target_link_libraries(ncls +target_link_libraries(notcurses-tetris PRIVATE notcurses++ ) -# notcurses-tetris -file(GLOB TETRISSRC CONFIGURE_DEPENDS src/tetris/*.cpp) -add_executable(notcurses-tetris ${TETRISSRC}) -target_include_directories(notcurses-tetris +# all further binaries require multimedia support +if(NOT ${USE_MULTIMEDIA} STREQUAL "none") +############################################################################ +# ncneofetch +file(GLOB FETCHSRCS CONFIGURE_DEPENDS src/fetch/*.c) +add_executable(ncneofetch ${FETCHSRCS}) +target_include_directories(ncneofetch PRIVATE include "${PROJECT_BINARY_DIR}/include" ) -target_link_libraries(notcurses-tetris +target_link_libraries(ncneofetch + PRIVATE + notcurses +) + +############################################################################ +# ncls +file(GLOB LSSRC CONFIGURE_DEPENDS src/ls/*.cpp) +add_executable(ncls ${LSSRC}) +target_include_directories(ncls + PRIVATE + include + "${PROJECT_BINARY_DIR}/include" +) +target_link_libraries(ncls PRIVATE notcurses++ ) +############################################################################ # notcurses-view -if(${USE_FFMPEG} OR ${USE_OIIO}) +if(NOT ${USE_MULTIMEDIA} STREQUAL "none") file(GLOB VIEWSRCS CONFIGURE_DEPENDS src/view/*.cpp) add_executable(notcurses-view ${VIEWSRCS} ${COMPATSRC}) target_include_directories(notcurses-view @@ -563,8 +603,10 @@ target_link_libraries(notcurses-view notcurses++ ) endif() +endif() -# Testing +############################################################################ +# testing if(${BUILD_TESTING}) #set(CMAKE_CTEST_ARGUMENTS "-V") if(${USE_DOCTEST}) @@ -579,8 +621,8 @@ target_include_directories(notcurses-tester ) target_link_libraries(notcurses-tester PRIVATE - unistring notcurses++ + unistring "${TERMINFO_LIBRARIES}" ) add_test( @@ -629,11 +671,14 @@ add_custom_target(demo ) # pkg-config support +configure_file(tools/notcurses-core.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/notcurses-core.pc + @ONLY +) configure_file(tools/notcurses.pc.in ${CMAKE_CURRENT_BINARY_DIR}/notcurses.pc @ONLY ) - configure_file(tools/notcurses++.pc.in ${CMAKE_CURRENT_BINARY_DIR}/notcurses++.pc @ONLY @@ -686,7 +731,7 @@ install(FILES DESTINATION ${PKGCONFIG_DIR} ) -if(${USE_FFMPEG} OR ${USE_OIIO}) +if(NOT ${USE_MULTIMEDIA} STREQUAL "none") file(GLOB TESTDATA CONFIGURE_DEPENDS data/*) # Don't install source materia for self-originated multimedia list(FILTER TESTDATA EXCLUDE REGEX ".*xcf$") @@ -701,14 +746,14 @@ install(FILES ${MARKDOWN} DESTINATION ${CMAKE_INSTALL_DOCDIR}) install(TARGETS notcurses-demo DESTINATION bin) install(TARGETS notcurses-input DESTINATION bin) -install(TARGETS ncls DESTINATION bin) -install(TARGETS ncneofetch DESTINATION bin) install(TARGETS notcurses-tetris DESTINATION bin) -if(${USE_FFMPEG} OR ${USE_OIIO}) +if(NOT ${USE_MULTIMEDIA} STREQUAL "none") +install(TARGETS ncneofetch DESTINATION bin) +install(TARGETS ncls DESTINATION bin) install(TARGETS notcurses-view DESTINATION bin) endif() -install(TARGETS notcurses notcurses++ +install(TARGETS notcurses-core notcurses notcurses++ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries @@ -716,7 +761,7 @@ install(TARGETS notcurses notcurses++ ) if(${USE_STATIC}) install( - TARGETS notcurses-static notcurses++-static + TARGETS notcurses-core-static notcurses-static notcurses++-static LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries diff --git a/INSTALL.md b/INSTALL.md index 64e1cfe64..7aa1b2da9 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -14,6 +14,14 @@ On an APT-based distribution, run: `apt-get install build-essential cmake doctest-dev libavformat-dev libavutil-dev libncurses-dev libreadline-dev libqrcodegen-dev libswscale-dev libunistring-dev pandoc pkg-config` +If you only intend to build core Notcurses (without multimedia support), run: + +`apt-get install build-essential cmake libncurses-dev libreadline-dev libqrcodegen-dev pandoc pkg-config` + +If you only intend to build core Notcurses (without multimedia support), run: + +`apt-get install build-essential cmake libncurses-dev libreadline-dev libqrcodegen-dev pandoc pkg-config` + If you want to build the Python wrappers, you'll also need: `apt-get install python3-cffi python3-dev python3-pypandoc python3-setuptools` @@ -28,8 +36,11 @@ If you want to build the Rust wrappers, you'll also need: ## Building -* Create a subdirectory, traditionally `build`. Enter the directory. -* `cmake ..`. You might want to set e.g. `CMAKE_BUILD_TYPE`. +* Create a subdirectory, traditionally `build` (this is not strictly necessary, + but it keeps your source tree clean). Enter the directory. +* `cmake ..` +** You might want to set e.g. `CMAKE_BUILD_TYPE`. Use `-DVAR=val`. +** To build without multimedia support, use `-DUSE_MULTIMEDIA=none`. * `make` * `make test` * `make install` @@ -89,4 +100,3 @@ but must be `Debug` for use of `USE_COVERAGE`. * `USE_POC`: build small, uninstalled proof-of-concept binaries * `USE_QRCODEGEN`: build qrcode support via libqrcodegen * `USE_STATIC`: build static libraries (in addition to shared ones) - diff --git a/NEWS.md b/NEWS.md index cf0cf5868..6bacf6c40 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,12 @@ This document attempts to list user-visible changes and any major internal rearrangements of Notcurses. +* 2.1.6 (not yet released): + * Notcurses has been split into two libraries, `notcurses-core` and + `notcurses`. The latter contains the heavyweight multimedia code, + so that applications which don't need this functionality can link against + only the former. `pkg-config` support is present for both. + * 2.1.5 (2021-01-15): * Notcurses **now depends on GNU Readline at build and runtime**, entirely for the benefit of direct mode, which now prepares GNU Readline for safe diff --git a/README.md b/README.md index 9288f3c0d..c0a2522c4 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ Why use this non-standard library? * The library object exports a minimal set of symbols. Where reasonable, `static inline` header-only code is used. This facilitates compiler optimizations, and reduces loader time. + * Notcurses can be built without its multimedia functionality, requiring a + significantly lesser set of dependencies. * All APIs natively support the Universal Character Set (Unicode). The `cell` API is based around Unicode's [Extended Grapheme Cluster](https://unicode.org/reports/tr29/) concept. diff --git a/USAGE.md b/USAGE.md index 75699adf0..91c0ee4be 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1,7 +1,7 @@ # Usage -As of version 2.0.0, Notcurses honors Semantic Versioning, the API is stable, -and the project is committed to backwards compatibility. +As of version 2.0.0, the Notcurses API is stable, and the project is committed +to backwards compatibility. * [Direct Mode](#direct-mode) * [Alignment](#alignment) @@ -15,16 +15,17 @@ and the project is committed to backwards compatibility. * [Stats](#stats) * [C++](#c++) -A full API reference [is available](https://nick-black.com/notcurses/). Manual -pages ought have been installed along with Notcurses. This document is a +A full API reference [is available](https://nick-black.com/notcurses/) in the +form of manual pages; these ought have been installed along with Notcurses. This document is a secondary reference, and should not be considered authoritative. For a more unified commentary, consider the [paperback](https://www.amazon.com/dp/B086PNVNC9) -(also available as a free PDF from https://nick-black.com). +(also available as a [free PDF](https://nick-black.com/dankwiki/index.php?title=Hacking_The_Planet!_with_Notcurses). A program wishing to use Notcurses will need to link it, ideally using the output of `pkg-config --libs notcurses`. It is advised to compile with the -output of `pkg-config --cflags notcurses`. If using CMake, a support file is -provided, and can be accessed as `Notcurses`. +output of `pkg-config --cflags notcurses`. To use the minimal core Notcurses, +without multimedia support, use `pkg-config --libs notcurses-core` etc. If +using CMake, a support file is provided, and can be accessed as `Notcurses`. Before calling into Notcurses—and usually as one of the first calls of the program—be sure to call `setlocale(3)` with an appropriate UTF-8 locale. It is diff --git a/data/tetris-background.jpg b/data/tetris-background.jpg index 82d3382cb5080eff641bfc7772fb55d658a84e30..91ceeb8a326e4327ec99cb6e3a2ea80c201aeaea 100644 GIT binary patch literal 23514 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%0A)vS+@9g;3SD}Y;gxr^ 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 z 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

!`k# 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 +**#include ** or +**#include ** -**-lnotcurses** +**-lnotcurses** or **-lnotcurses-core** # DESCRIPTION @@ -23,7 +24,8 @@ A program wishing to use Notcurses will need to link it, ideally using the output of **pkg-config --libs notcurses** (see **pkg-config(1)**). It is advised to compile with the output of **pkg-config --cflags notcurses**. If using CMake, a support file is provided, and can be accessed as **Notcurses** -(see **cmake(1)**). +(see **cmake(1)**). If multimedia capabilities are not needed, it is possible +to link against a minimal Notcurses using **pkg-config --libs notcurses-core**. **notcurses_init(3)** can then be used to initialize a Notcurses instance for a given **FILE** (usually **stdout**, usually attached to a terminal). diff --git a/src/demo/dragon.c b/src/demo/dragon.c index 42b9ee965..ca3071bd8 100644 --- a/src/demo/dragon.c +++ b/src/demo/dragon.c @@ -105,7 +105,6 @@ int dragon_demo(struct notcurses* nc){ } DEMO_RENDER(nc); demo_nanosleep(nc, &scaled); - ncplane_erase(n); }while(lasttotal != r); ncvisual_destroy(ncv); return 0; diff --git a/src/lib/blit.c b/src/lib/blit.c index b206b3f66..c3629428c 100644 --- a/src/lib/blit.c +++ b/src/lib/blit.c @@ -878,13 +878,6 @@ int ncblit_rgba(const void* data, int linesize, const struct ncvisual_options* v leny, lenx, blend); } -int rgba_blit_dispatch(ncplane* nc, const struct blitset* bset, int placey, - int placex, int linesize, const void* data, int begy, - int begx, int leny, int lenx, bool blendcolors){ - return bset->blit(nc, placey, placex, linesize, data, begy, begx, - leny, lenx, blendcolors); -} - ncblitter_e ncvisual_media_defblitter(const notcurses* nc, ncscale_e scale){ return rgba_blitter_default(nc->utf8, scale, nc->tcache.sextants); } diff --git a/src/lib/ffmpeg.h b/src/lib/ffmpeg.h deleted file mode 100644 index f02b87782..000000000 --- a/src/lib/ffmpeg.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef NOTCURSES_FFMPEG -#define NOTCURSES_FFMPEG - -#include "version.h" -#ifdef USE_FFMPEG - -extern "C" { - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -} // extern "C" - -struct AVFormatContext; -struct AVCodecContext; -struct AVFrame; -struct AVCodec; -struct AVCodecParameters; -struct AVPacket; - -typedef struct ncvisual_details { - int packet_outstanding; - struct AVFormatContext* fmtctx; - struct AVCodecContext* codecctx; // video codec context - struct AVCodecContext* subtcodecctx; // subtitle codec context - struct AVFrame* frame; // frame as read - struct AVFrame* oframe; // RGBA frame - struct AVCodec* codec; - struct AVCodecParameters* cparams; - struct AVCodec* subtcodec; - struct AVPacket* packet; - struct SwsContext* swsctx; - AVSubtitle subtitle; - int stream_index; // match against this following av_read_frame() - int sub_stream_index; // subtitle stream index, can be < 0 if no subtitles -} ncvisual_details; - -static inline auto -ncvisual_details_init(ncvisual_details* deets) -> int { - memset(deets, 0, sizeof(*deets)); - deets->stream_index = -1; - deets->sub_stream_index = -1; - if((deets->frame = av_frame_alloc()) == nullptr){ - return -1; - } - return 0; -} - -static inline auto -ncvisual_details_destroy(ncvisual_details* deets) -> void { - avcodec_close(deets->codecctx); - avcodec_free_context(&deets->codecctx); - av_frame_free(&deets->frame); - av_freep(&deets->oframe); - //avcodec_parameters_free(&ncv->cparams); - sws_freeContext(deets->swsctx); - av_packet_free(&deets->packet); - avformat_close_input(&deets->fmtctx); - avsubtitle_free(&deets->subtitle); -} - -#endif - -#endif diff --git a/src/lib/internal.h b/src/lib/internal.h index 351c3cbfc..48035ea81 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -681,9 +681,13 @@ memdup(const void* src, size_t len){ void* bgra_to_rgba(const void* data, int rows, int rowstride, int cols); -int rgba_blit_dispatch(ncplane* nc, const struct blitset* bset, int placey, - int placex, int linesize, const void* data, int begy, - int begx, int leny, int lenx, bool blendcolors); +static inline int +rgba_blit_dispatch(ncplane* nc, const struct blitset* bset, int placey, + int placex, int linesize, const void* data, int begy, + int begx, int leny, int lenx, bool blendcolors){ + return bset->blit(nc, placey, placex, linesize, data, begy, begx, + leny, lenx, blendcolors); +} // find the "center" cell of two lengths. in the case of even rows/columns, we // place the center on the top/left. in such a case there will be one more @@ -1068,6 +1072,8 @@ int setup_signals(void* nc, bool no_quit_sigs, bool no_winch_sig, int(*handler)(void*)); int drop_signals(void* nc); +void ncvisual_printbanner(const notcurses* nc); + #ifdef __cplusplus } #endif diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index b6483cd53..2a769596c 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -802,19 +802,7 @@ init_banner(const notcurses* nc){ #error "No __BYTE_ORDER__ definition" #endif , curses_version()); -#ifdef USE_FFMPEG - printf(" avformat %u.%u.%u avutil %u.%u.%u swscale %u.%u.%u\n", - LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO, - LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO, - LIBSWSCALE_VERSION_MAJOR, LIBSWSCALE_VERSION_MINOR, LIBSWSCALE_VERSION_MICRO); -#else -#ifdef USE_OIIO - printf(" openimageio %s\n", oiio_version()); -#else - term_fg_palindex(nc, stderr, nc->tcache.colors <= 88 ? 1 % nc->tcache.colors : 0xcb); - fprintf(stderr, "\n Warning! Notcurses was built without multimedia support.\n"); -#endif -#endif + ncvisual_printbanner(nc); fflush(stdout); term_fg_palindex(nc, stderr, nc->tcache.colors <= 88 ? 1 % nc->tcache.colors : 0xcb); if(!nc->tcache.RGBflag){ // FIXME diff --git a/src/lib/oiio.h b/src/lib/oiio.h deleted file mode 100644 index a7e983ca0..000000000 --- a/src/lib/oiio.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef NOTCURSES_OIIO -#define NOTCURSES_OIIO - -// OpenImageIO implementation of ncvisual_details -#include "version.h" -#ifdef USE_OIIO -#include -#include -#include -#include -#include - -typedef struct ncvisual_details { - std::unique_ptr image; // must be close()d - std::unique_ptr frame; - std::unique_ptr ibuf; - uint64_t framenum; -} ncvisual_details; - -static inline auto -ncvisual_details_init(ncvisual_details *deets) -> int { - deets->image = nullptr; - deets->frame = nullptr; - deets->ibuf = nullptr; - deets->framenum = 0; - return 0; -} - -static inline auto -ncvisual_details_destroy(ncvisual_details* deets) -> void { - if(deets->image){ - deets->image->close(); - } -} - -#endif - -#endif diff --git a/src/lib/visual-details.h b/src/lib/visual-details.h index a22493fa3..f2d9a2916 100644 --- a/src/lib/visual-details.h +++ b/src/lib/visual-details.h @@ -4,48 +4,40 @@ #include "builddef.h" #include "notcurses/notcurses.h" -#ifdef USE_FFMPEG -#include "ffmpeg.h" -#else -#ifdef USE_OIIO -#include "oiio.h" -#else - -typedef struct ncvisual_details { -} ncvisual_details; - -static inline auto ncvisual_details_init(ncvisual_details* deets) -> int { - (void)deets; - return 0; -} - -static inline auto -ncvisual_details_destroy(ncvisual_details* deets) -> void { - (void)deets; -} -#endif -#endif - struct ncplane; +struct ncvisual_details; typedef struct ncvisual { + struct ncvisual_details* details;// implementation-specific details + uint32_t* data; // (scaled) RGBA image data, rowstride bytes per row int cols, rows; // lines are sometimes padded. this many true bytes per row in data. int rowstride; - ncvisual_details details;// implementation-specific details - uint32_t* data; // (scaled) RGBA image data, rowstride bytes per row bool owndata; // we own data iff owndata == true } ncvisual; -static inline auto -ncvisual_create(void) -> ncvisual* { - auto ret = new ncvisual{}; - if(ret == nullptr){ - return nullptr; - } - ncvisual_details_init(&ret->details); - return ret; -} +typedef struct ncvisual_implementation { + int (*ncvisual_init)(int loglevel); + int (*ncvisual_decode)(ncvisual*); + int (*ncvisual_blit)(ncvisual* ncv, int rows, int cols, ncplane* n, + const struct blitset* bset, int placey, int placex, + int begy, int begx, int leny, int lenx, + bool blendcolors); + ncvisual* (*ncvisual_create)(void); + ncvisual* (*ncvisual_from_file)(const char* s); + void (*ncvisual_printbanner)(const struct notcurses* nc); + // ncv constructors other than ncvisual_from_file() need to set up the + // AVFrame* 'frame' according to their own data, which is assumed to + // have been prepared already in 'ncv'. + void (*ncvisual_details_seed)(ncvisual* ncv); + void (*ncvisual_details_destroy)(struct ncvisual_details* deets); + bool canopen_images; + bool canopen_videos; +} ncvisual_implementation; + +// ugh! need export this for pluggable multimedia modules without dlopen() +__attribute__((visibility("default"))) +int notcurses_set_ncvisual_implementation(const ncvisual_implementation* imp); static inline auto ncvisual_set_data(ncvisual* ncv, uint32_t* data, bool owned) -> void { diff --git a/src/lib/visual.cpp b/src/lib/visual.cpp index 6738ed879..070880131 100644 --- a/src/lib/visual.cpp +++ b/src/lib/visual.cpp @@ -4,10 +4,111 @@ #include "visual-details.h" #include "internal.h" -// ncv constructors other than ncvisual_from_file() need to set up the -// AVFrame* 'frame' according to their own data, which is assumed to -// have been prepared already in 'ncv'. -auto ncvisual_details_seed(struct ncvisual* ncv) -> void; +// FIXME make this a weak symbol instead so we work with static linking +static const ncvisual_implementation* impl; +static pthread_rwlock_t impllock = PTHREAD_RWLOCK_INITIALIZER; + +int notcurses_set_ncvisual_implementation(const ncvisual_implementation* imp){ + int ret = -1; + if(pthread_rwlock_wrlock(&impllock)){ + return -1; + } + if(impl == nullptr){ + impl = imp; + } + ret |= pthread_rwlock_unlock(&impllock); + return ret; +} + +auto ncvisual_decode(ncvisual* nc) -> int { + int ret = -1; + if(pthread_rwlock_rdlock(&impllock)){ + return -1; + } + if(impl){ + ret = impl->ncvisual_decode(nc); + } + ret |= pthread_rwlock_unlock(&impllock); + return ret; +} + +auto ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, + const struct blitset* bset, int placey, int placex, + int begy, int begx, int leny, int lenx, + bool blendcolors) -> int { + int ret = -1; + if(pthread_rwlock_rdlock(&impllock)){ + return -1; + } + if(impl){ + if(impl->ncvisual_blit(ncv, rows, cols, n, bset, placey, placex, + begy, begx, leny, lenx, blendcolors) >= 0){ + ret = 0; + } + }else{ + if(rgba_blit_dispatch(n, bset, placey, placex, ncv->rowstride, ncv->data, + begy, begx, leny, lenx, blendcolors) >= 0){ + ret = 0; + } + } + ret |= pthread_rwlock_unlock(&impllock); + return ret; +} + +auto ncvisual_details_seed(struct ncvisual* ncv) -> void { + pthread_rwlock_rdlock(&impllock); + if(impl){ + impl->ncvisual_details_seed(ncv); + } + pthread_rwlock_unlock(&impllock); +} + +auto ncvisual_init(int loglevel) -> int { + int ret = 0; // default to success here + if(pthread_rwlock_rdlock(&impllock)){ + return -1; + } + if(impl){ + ret = impl->ncvisual_init(loglevel); + } + ret |= pthread_rwlock_unlock(&impllock); + return ret; +} + +auto ncvisual_from_file(const char* filename) -> ncvisual* { + ncvisual* ret = nullptr; + if(pthread_rwlock_rdlock(&impllock) == 0){ + if(impl){ + ret = impl->ncvisual_from_file(filename); + } + pthread_rwlock_unlock(&impllock); + } + return ret; +} + +auto ncvisual_create(void) -> ncvisual* { + ncvisual* ret = nullptr; + if(pthread_rwlock_rdlock(&impllock) == 0){ + if(impl){ + ret = impl->ncvisual_create(); + }else{ + ret = new ncvisual{}; + } + pthread_rwlock_unlock(&impllock); + } + return ret; +} + +auto ncvisual_printbanner(const notcurses* nc) -> void { + pthread_rwlock_rdlock(&impllock); + if(impl){ + impl->ncvisual_printbanner(nc); + }else{ + term_fg_palindex(nc, stderr, nc->tcache.colors <= 88 ? 1 % nc->tcache.colors : 0xcb); + fprintf(stderr, "\n Warning! Notcurses was built without multimedia support.\n"); + } + pthread_rwlock_unlock(&impllock); +} auto ncvisual_geom(const notcurses* nc, const ncvisual* n, const struct ncvisual_options* vopts, @@ -484,11 +585,15 @@ auto ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int begx, auto ncvisual_destroy(ncvisual* ncv) -> void { if(ncv){ - ncvisual_details_destroy(&ncv->details); + pthread_rwlock_rdlock(&impllock); + if(impl){ + impl->ncvisual_details_destroy(ncv->details); + } if(ncv->owndata){ free(ncv->data); } delete ncv; + pthread_rwlock_unlock(&impllock); } } @@ -567,24 +672,18 @@ auto ncvisual_polyfill_yx(ncvisual* n, int y, int x, uint32_t rgba) -> int { return ncvisual_polyfill_recurse(n, y, x, rgba, *pixel); } -#ifndef USE_OIIO // built without ffmpeg or oiio -#ifndef USE_FFMPEG -auto ncvisual_from_file(const char* filename) -> ncvisual* { - (void)filename; - return nullptr; -} - auto notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))) -> bool { - return false; + if(!impl){ + return false; + } + return impl->canopen_images; } auto notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))) -> bool { - return false; -} - -auto ncvisual_decode(ncvisual* nc) -> int { - (void)nc; - return -1; + if(!impl){ + return false; + } + return impl->canopen_videos; } auto ncvisual_decode_loop(ncvisual* nc) -> int { @@ -609,28 +708,6 @@ auto ncvisual_subtitle(const ncvisual* ncv) -> char* { return nullptr; } -auto ncvisual_init(int loglevel) -> int { - (void)loglevel; - return 0; // allow success here -} - -auto ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, - const struct blitset* bset, int placey, int placex, - int begy, int begx, int leny, int lenx, - bool blendcolors) -> int { - (void)rows; - (void)cols; - if(rgba_blit_dispatch(n, bset, placey, placex, ncv->rowstride, ncv->data, - begy, begx, leny, lenx, blendcolors) < 0){ - return -1; - } - return 0; -} - -auto ncvisual_details_seed(struct ncvisual* ncv) -> void { - (void)ncv; -} - auto ncvisual_resize(ncvisual* nc, int rows, int cols) -> int { // we'd need to verify that it's RGBA as well, except that if we've got no // multimedia engine, we've only got memory-assembled ncvisuals, which are @@ -640,6 +717,3 @@ auto ncvisual_resize(ncvisual* nc, int rows, int cols) -> int { } return -1; } - -#endif -#endif diff --git a/src/lib/ffmpeg.cpp b/src/media/ffmpeg.cpp similarity index 63% rename from src/lib/ffmpeg.cpp rename to src/media/ffmpeg.cpp index df6412afc..1bba247d2 100644 --- a/src/lib/ffmpeg.cpp +++ b/src/media/ffmpeg.cpp @@ -1,18 +1,47 @@ #include "builddef.h" #ifdef USE_FFMPEG -#include "ffmpeg.h" +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} #include "internal.h" #include "visual-details.h" -#define IMGALLOCALIGN 32 +struct AVFormatContext; +struct AVCodecContext; +struct AVFrame; +struct AVCodec; +struct AVCodecParameters; +struct AVPacket; -bool notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))) { - return true; -} +typedef struct ncvisual_details { + int packet_outstanding; + struct AVFormatContext* fmtctx; + struct AVCodecContext* codecctx; // video codec context + struct AVCodecContext* subtcodecctx; // subtitle codec context + struct AVFrame* frame; // frame as read + struct AVFrame* oframe; // RGBA frame + struct AVCodec* codec; + struct AVCodecParameters* cparams; + struct AVCodec* subtcodec; + struct AVPacket* packet; + struct SwsContext* swsctx; + AVSubtitle subtitle; + int stream_index; // match against this following av_read_frame() + int sub_stream_index; // subtitle stream index, can be < 0 if no subtitles +} ncvisual_details; -bool notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))) { - return true; -} +#define IMGALLOCALIGN 32 + +static void inject_implementation(void) __attribute__ ((constructor)); /*static void print_frame_summary(const AVCodecContext* cctx, const AVFrame* f) { @@ -86,8 +115,8 @@ deass(const char* ass) { } auto ncvisual_subtitle(const ncvisual* ncv) -> char* { - for(unsigned i = 0 ; i < ncv->details.subtitle.num_rects ; ++i){ - const AVSubtitleRect* rect = ncv->details.subtitle.rects[i]; + for(unsigned i = 0 ; i < ncv->details->subtitle.num_rects ; ++i){ + const AVSubtitleRect* rect = ncv->details->subtitle.rects[i]; if(rect->type == SUBTITLE_ASS){ return deass(rect->ass); }else if(rect->type == SUBTITLE_TEXT) {; @@ -107,49 +136,50 @@ averr2ncerr(int averr){ return -1; } -int ncvisual_decode(ncvisual* nc){ - if(nc->details.fmtctx == nullptr){ // not a file-backed ncvisual +static int +ffmpeg_decode(ncvisual* nc){ + if(nc->details->fmtctx == nullptr){ // not a file-backed ncvisual return -1; } bool have_frame = false; bool unref = false; // FIXME what if this was set up with e.g. ncvisual_from_rgba()? - if(nc->details.oframe){ - av_freep(&nc->details.oframe->data[0]); + if(nc->details->oframe){ + av_freep(&nc->details->oframe->data[0]); } do{ do{ - if(nc->details.packet_outstanding){ + if(nc->details->packet_outstanding){ break; } if(unref){ - av_packet_unref(nc->details.packet); + av_packet_unref(nc->details->packet); } int averr; - if((averr = av_read_frame(nc->details.fmtctx, nc->details.packet)) < 0){ + if((averr = av_read_frame(nc->details->fmtctx, nc->details->packet)) < 0){ /*if(averr != AVERROR_EOF){ fprintf(stderr, "Error reading frame info (%s)\n", av_err2str(averr)); }*/ return averr2ncerr(averr); } unref = true; - if(nc->details.packet->stream_index == nc->details.sub_stream_index){ + if(nc->details->packet->stream_index == nc->details->sub_stream_index){ int result = 0, ret; - ret = avcodec_decode_subtitle2(nc->details.subtcodecctx, &nc->details.subtitle, &result, nc->details.packet); + ret = avcodec_decode_subtitle2(nc->details->subtcodecctx, &nc->details->subtitle, &result, nc->details->packet); if(ret >= 0 && result){ // FIXME? } } - }while(nc->details.packet->stream_index != nc->details.stream_index); - ++nc->details.packet_outstanding; - int averr = avcodec_send_packet(nc->details.codecctx, nc->details.packet); + }while(nc->details->packet->stream_index != nc->details->stream_index); + ++nc->details->packet_outstanding; + int averr = avcodec_send_packet(nc->details->codecctx, nc->details->packet); if(averr < 0){ //fprintf(stderr, "Error processing AVPacket\n"); return averr2ncerr(averr); } - --nc->details.packet_outstanding; - av_packet_unref(nc->details.packet); - averr = avcodec_receive_frame(nc->details.codecctx, nc->details.frame); + --nc->details->packet_outstanding; + av_packet_unref(nc->details->packet); + averr = avcodec_receive_frame(nc->details->codecctx, nc->details->frame); if(averr >= 0){ have_frame = true; }else if(averr == AVERROR(EAGAIN) || averr == AVERROR_EOF){ @@ -159,12 +189,12 @@ int ncvisual_decode(ncvisual* nc){ return averr2ncerr(averr); } }while(!have_frame); -//print_frame_summary(nc->details.codecctx, nc->details.frame); - const AVFrame* f = nc->details.frame; +//print_frame_summary(nc->details->codecctx, nc->details->frame); + const AVFrame* f = nc->details->frame; nc->rowstride = f->linesize[0]; - nc->cols = nc->details.frame->width; - nc->rows = nc->details.frame->height; -//fprintf(stderr, "good decode! %d/%d %d %p\n", nc->details.frame->height, nc->details.frame->width, nc->rowstride, f->data); + nc->cols = nc->details->frame->width; + nc->rows = nc->details->frame->height; +//fprintf(stderr, "good decode! %d/%d %d %p\n", nc->details->frame->height, nc->details->frame->width, nc->rowstride, f->data); ncvisual_set_data(nc, reinterpret_cast(f->data[0]), false); return 0; } @@ -172,7 +202,7 @@ int ncvisual_decode(ncvisual* nc){ // resize frame to oframe, converting to RGBA (if necessary) along the way int ncvisual_resize(ncvisual* nc, int rows, int cols) { const int targformat = AV_PIX_FMT_RGBA; - AVFrame* inf = nc->details.oframe ? nc->details.oframe : nc->details.frame; + AVFrame* inf = nc->details->oframe ? nc->details->oframe : nc->details->frame; //fprintf(stderr, "got format: %d (%d/%d) want format: %d (%d/%d)\n", inf->format, nc->rows, nc->cols, targformat, rows, cols); if(inf->format == targformat && nc->rows == rows && nc->cols == cols){ return 0; @@ -231,72 +261,92 @@ int ncvisual_resize(ncvisual* nc, int rows, int cols) { nc->rows = rows; nc->cols = cols; ncvisual_set_data(nc, reinterpret_cast(sframe->data[0]), true); - if(nc->details.oframe){ - //av_freep(nc->details.oframe->data); - av_freep(&nc->details.oframe); + if(nc->details->oframe){ + //av_freep(nc->details->oframe->data); + av_freep(&nc->details->oframe); } - nc->details.oframe = sframe; + nc->details->oframe = sframe; -//fprintf(stderr, "SIZE SCALED: %d %d (%u)\n", nc->details.oframe->height, nc->details.oframe->width, nc->details.oframe->linesize[0]); +//fprintf(stderr, "SIZE SCALED: %d %d (%u)\n", nc->details->oframe->height, nc->details->oframe->width, nc->details->oframe->linesize[0]); return 0; } -ncvisual* ncvisual_from_file(const char* filename) { +auto ffmpeg_details_init(void) -> ncvisual_details* { + auto deets = new ncvisual_details{}; + deets->stream_index = -1; + deets->sub_stream_index = -1; + if((deets->frame = av_frame_alloc()) == nullptr){ + delete deets; + return nullptr; + } + return deets; +} + +auto ffmpeg_create() -> ncvisual* { + auto nc = new ncvisual{}; + if((nc->details = ffmpeg_details_init()) == nullptr){ + delete nc; + return nullptr; + } + return nc; +} + +ncvisual* ffmpeg_from_file(const char* filename) { AVStream* st; - ncvisual* ncv = ncvisual_create(); + ncvisual* ncv = ffmpeg_create(); if(ncv == nullptr){ // fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno)); return nullptr; } -//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details.frame); - int averr = avformat_open_input(&ncv->details.fmtctx, filename, nullptr, nullptr); +//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame); + int averr = avformat_open_input(&ncv->details->fmtctx, filename, nullptr, nullptr); if(averr < 0){ //fprintf(stderr, "Couldn't open %s (%d)\n", filename, averr); goto err; } - averr = avformat_find_stream_info(ncv->details.fmtctx, nullptr); + averr = avformat_find_stream_info(ncv->details->fmtctx, nullptr); if(averr < 0){ //fprintf(stderr, "Error extracting stream info from %s (%d)\n", filename, averr); goto err; } -//av_dump_format(ncv->details.fmtctx, 0, filename, false); - if((averr = av_find_best_stream(ncv->details.fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->details.subtcodec, 0)) >= 0){ - ncv->details.sub_stream_index = averr; - if((ncv->details.subtcodecctx = avcodec_alloc_context3(ncv->details.subtcodec)) == nullptr){ +//av_dump_format(ncv->details->fmtctx, 0, filename, false); + if((averr = av_find_best_stream(ncv->details->fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->details->subtcodec, 0)) >= 0){ + ncv->details->sub_stream_index = averr; + if((ncv->details->subtcodecctx = avcodec_alloc_context3(ncv->details->subtcodec)) == nullptr){ //fprintf(stderr, "Couldn't allocate decoder for %s\n", filename); goto err; } // FIXME do we need avcodec_parameters_to_context() here? - if(avcodec_open2(ncv->details.subtcodecctx, ncv->details.subtcodec, nullptr) < 0){ + if(avcodec_open2(ncv->details->subtcodecctx, ncv->details->subtcodec, nullptr) < 0){ //fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr)); goto err; } }else{ - ncv->details.sub_stream_index = -1; + ncv->details->sub_stream_index = -1; } -//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details.frame); - if((ncv->details.packet = av_packet_alloc()) == nullptr){ +//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame); + if((ncv->details->packet = av_packet_alloc()) == nullptr){ // fprintf(stderr, "Couldn't allocate packet for %s\n", filename); goto err; } - if((averr = av_find_best_stream(ncv->details.fmtctx, AVMEDIA_TYPE_VIDEO, -1, -1, &ncv->details.codec, 0)) < 0){ + if((averr = av_find_best_stream(ncv->details->fmtctx, AVMEDIA_TYPE_VIDEO, -1, -1, &ncv->details->codec, 0)) < 0){ // fprintf(stderr, "Couldn't find visuals in %s (%s)\n", filename, av_err2str(*averr)); goto err; } - ncv->details.stream_index = averr; - if(ncv->details.codec == nullptr){ + ncv->details->stream_index = averr; + if(ncv->details->codec == nullptr){ //fprintf(stderr, "Couldn't find decoder for %s\n", filename); goto err; } - st = ncv->details.fmtctx->streams[ncv->details.stream_index]; - if((ncv->details.codecctx = avcodec_alloc_context3(ncv->details.codec)) == nullptr){ + st = ncv->details->fmtctx->streams[ncv->details->stream_index]; + if((ncv->details->codecctx = avcodec_alloc_context3(ncv->details->codec)) == nullptr){ //fprintf(stderr, "Couldn't allocate decoder for %s\n", filename); goto err; } - if(avcodec_parameters_to_context(ncv->details.codecctx, st->codecpar) < 0){ + if(avcodec_parameters_to_context(ncv->details->codecctx, st->codecpar) < 0){ goto err; } - if(avcodec_open2(ncv->details.codecctx, ncv->details.codec, nullptr) < 0){ + if(avcodec_open2(ncv->details->codecctx, ncv->details->codec, nullptr) < 0){ //fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr)); goto err; } @@ -304,11 +354,11 @@ ncvisual* ncvisual_from_file(const char* filename) { //fprintf(stderr, "Couldn't allocate codec params for %s\n", filename); goto err; } - if((*averr = avcodec_parameters_from_context(ncv->cparams, ncv->details.codecctx)) < 0){ + if((*averr = avcodec_parameters_from_context(ncv->cparams, ncv->details->codecctx)) < 0){ //fprintf(stderr, "Couldn't get codec params for %s (%s)\n", filename, av_err2str(*averr)); goto err; }*/ -//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details.frame); +//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame); // frame is set up in prep_details(), so that format can be set there, as // is necessary when it is prepared from inputs other than files. oframe // is set up whenever we convert to RGBA. @@ -344,8 +394,8 @@ int ncvisual_stream(notcurses* nc, ncvisual* ncv, float timescale, do{ // 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->details.fmtctx->streams[ncv->details.stream_index]->time_base); - int64_t ts = ncv->details.frame->best_effort_timestamp; + double tbase = av_q2d(ncv->details->fmtctx->streams[ncv->details->stream_index]->time_base); + int64_t ts = ncv->details->frame->best_effort_timestamp; if(frame == 1 && ts){ usets = true; } @@ -362,8 +412,8 @@ int ncvisual_stream(notcurses* nc, ncvisual* ncv, float timescale, activevopts.n = newn; } ++frame; - uint64_t duration = ncv->details.frame->pkt_duration * tbase * NANOSECS_IN_SEC; -//fprintf(stderr, "use: %u dur: %ju ts: %ju cctx: %f fctx: %f\n", usets, duration, ts, av_q2d(ncv->details.codecctx->time_base), av_q2d(ncv->details.fmtctx->streams[ncv->stream_index]->time_base)); + uint64_t duration = ncv->details->frame->pkt_duration * tbase * NANOSECS_IN_SEC; +//fprintf(stderr, "use: %u dur: %ju ts: %ju cctx: %f fctx: %f\n", usets, duration, ts, av_q2d(ncv->details->codecctx->time_base), av_q2d(ncv->details->fmtctx->streams[ncv->stream_index]->time_base)); double schedns = nsbegin; if(usets){ if(tbase == 0){ @@ -401,7 +451,7 @@ int ncvisual_stream(notcurses* nc, ncvisual* ncv, float timescale, int ncvisual_decode_loop(ncvisual* ncv){ int r = ncvisual_decode(ncv); if(r == 1){ - if(av_seek_frame(ncv->details.fmtctx, ncv->details.stream_index, 0, AVSEEK_FLAG_FRAME) < 0){ + if(av_seek_frame(ncv->details->fmtctx, ncv->details->stream_index, 0, AVSEEK_FLAG_FRAME) < 0){ // FIXME log error return -1; } @@ -412,12 +462,11 @@ int ncvisual_decode_loop(ncvisual* ncv){ return r; } -int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, - const struct blitset* bset, int placey, int placex, - int begy, int begx, int leny, int lenx, - bool blendcolors) { - const AVFrame* inframe = ncv->details.oframe ? ncv->details.oframe : ncv->details.frame; -//fprintf(stderr, "inframe: %p oframe: %p frame: %p\n", inframe, ncv->details.oframe, ncv->details.frame); +int ffmpeg_blit(ncvisual* ncv, int rows, int cols, ncplane* n, + const struct blitset* bset, int placey, int placex, + int begy, int begx, int leny, int lenx, bool blendcolors) { + const AVFrame* inframe = ncv->details->oframe ? ncv->details->oframe : ncv->details->frame; +//fprintf(stderr, "inframe: %p oframe: %p frame: %p\n", inframe, ncv->details->oframe, ncv->details->frame); void* data = nullptr; int stride = 0; AVFrame* sframe = nullptr; @@ -431,14 +480,14 @@ int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, return -1; } //fprintf(stderr, "WHN NCV: %d/%d\n", inframe->width, inframe->height); - ncv->details.swsctx = sws_getCachedContext(ncv->details.swsctx, + ncv->details->swsctx = sws_getCachedContext(ncv->details->swsctx, ncv->cols, ncv->rows, static_cast(inframe->format), cols, rows, static_cast(targformat), SWS_LANCZOS, nullptr, nullptr, nullptr); - if(ncv->details.swsctx == nullptr){ -//fprintf(stderr, "Error retrieving details.swsctx\n"); + if(ncv->details->swsctx == nullptr){ +//fprintf(stderr, "Error retrieving details->swsctx\n"); return -1; } memcpy(sframe, inframe, sizeof(*inframe)); @@ -453,7 +502,7 @@ int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, //fprintf(stderr, "Error allocating visual data (%d X %d)\n", sframe->height, sframe->width); return -1; } - int height = sws_scale(ncv->details.swsctx, (const uint8_t* const*)inframe->data, + int height = sws_scale(ncv->details->swsctx, (const uint8_t* const*)inframe->data, inframe->linesize, 0, inframe->height, sframe->data, sframe->linesize); if(height < 0){ @@ -484,20 +533,58 @@ int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, return 0; } -auto ncvisual_details_seed(ncvisual* ncv) -> void { - assert(nullptr == ncv->details.oframe); - ncv->details.frame->data[0] = reinterpret_cast(ncv->data); - ncv->details.frame->data[1] = nullptr; - ncv->details.frame->linesize[0] = ncv->rowstride; - ncv->details.frame->linesize[1] = 0; - ncv->details.frame->width = ncv->cols; - ncv->details.frame->height = ncv->rows; - ncv->details.frame->format = AV_PIX_FMT_RGBA; +auto ffmpeg_details_seed(ncvisual* ncv) -> void { + assert(nullptr == ncv->details->oframe); + ncv->details->frame->data[0] = reinterpret_cast(ncv->data); + ncv->details->frame->data[1] = nullptr; + ncv->details->frame->linesize[0] = ncv->rowstride; + ncv->details->frame->linesize[1] = 0; + ncv->details->frame->width = ncv->cols; + ncv->details->frame->height = ncv->rows; + ncv->details->frame->format = AV_PIX_FMT_RGBA; } -int ncvisual_init(int loglevel) { +auto ffmpeg_init(int loglevel) -> int { av_log_set_level(loglevel); // FIXME could also use av_log_set_callback() and capture the message... return 0; } + +void ffmpeg_printbanner(const notcurses* nc __attribute__ ((unused))){ + printf(" avformat %u.%u.%u avutil %u.%u.%u swscale %u.%u.%u\n", + LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO, + LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO, + LIBSWSCALE_VERSION_MAJOR, LIBSWSCALE_VERSION_MINOR, LIBSWSCALE_VERSION_MICRO); +} + +auto ffmpeg_details_destroy(ncvisual_details* deets) -> void { + avcodec_close(deets->codecctx); + avcodec_free_context(&deets->codecctx); + av_frame_free(&deets->frame); + av_freep(&deets->oframe); + //avcodec_parameters_free(&ncv->cparams); + sws_freeContext(deets->swsctx); + av_packet_free(&deets->packet); + avformat_close_input(&deets->fmtctx); + avsubtitle_free(&deets->subtitle); + delete deets; +} + +const static ncvisual_implementation ffmpeg_impl = { + .ncvisual_init = ffmpeg_init, + .ncvisual_decode = ffmpeg_decode, + .ncvisual_blit = ffmpeg_blit, + .ncvisual_create = ffmpeg_create, + .ncvisual_from_file = ffmpeg_from_file, + .ncvisual_printbanner = ffmpeg_printbanner, + .ncvisual_details_seed = ffmpeg_details_seed, + .ncvisual_details_destroy = ffmpeg_details_destroy, + .canopen_images = true, + .canopen_videos = true, +}; + +static void inject_implementation(void){ + notcurses_set_ncvisual_implementation(&ffmpeg_impl); +} + #endif diff --git a/src/lib/oiio.cpp b/src/media/oiio.cpp similarity index 55% rename from src/lib/oiio.cpp rename to src/media/oiio.cpp index 938662e25..b2f98d7b2 100644 --- a/src/lib/oiio.cpp +++ b/src/media/oiio.cpp @@ -1,29 +1,63 @@ #include "builddef.h" #ifdef USE_OIIO -#include "oiio.h" +#include +#include +#include +#include +#include #include "internal.h" #include "visual-details.h" -bool notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))) { - return true; +static void inject_implementation(void) __attribute__ ((constructor)); + +typedef struct ncvisual_details { + std::unique_ptr image; // must be close()d + std::unique_ptr frame; + std::unique_ptr ibuf; + uint64_t framenum; +} ncvisual_details; + +static inline auto +oiio_details_init(void) -> ncvisual_details* { + auto deets = new ncvisual_details{}; + if(deets){ + deets->image = nullptr; + deets->frame = nullptr; + deets->ibuf = nullptr; + deets->framenum = 0; + } + return deets; } -bool notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))) { - return false; // too slow for reliable use at the moment +static inline auto +oiio_details_destroy(ncvisual_details* deets) -> void { + if(deets->image){ + deets->image->close(); + } + delete deets; } -ncvisual* ncvisual_from_file(const char* filename) { - ncvisual* ncv = ncvisual_create(); +auto oiio_create() -> ncvisual* { + auto nc = new ncvisual{}; + if((nc->details = oiio_details_init()) == nullptr){ + delete nc; + return nullptr; + } + return nc; +} + +ncvisual* oiio_from_file(const char* filename) { + ncvisual* ncv = oiio_create(); if(ncv == nullptr){ return nullptr; } - ncv->details.image = OIIO::ImageInput::open(filename); - if(!ncv->details.image){ + ncv->details->image = OIIO::ImageInput::open(filename); + if(!ncv->details->image){ // fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno)); ncvisual_destroy(ncv); return nullptr; } -/*const auto &spec = ncv->details.image->spec_dimensions(0); +/*const auto &spec = ncv->details->image->spec_dimensions(0); std::cout << "Opened " << filename << ": " << spec.height << "x" << spec.width << "@" << spec.nchannels << " (" << spec.format << ")" << std::endl;*/ if(ncvisual_decode(ncv)){ @@ -33,38 +67,38 @@ spec.width << "@" << spec.nchannels << " (" << spec.format << ")" << std::endl;* return ncv; } -int ncvisual_decode(ncvisual* nc) { -//fprintf(stderr, "current subimage: %d frame: %p\n", nc->details.image->current_subimage(), nc->details.frame.get()); - const auto &spec = nc->details.image->spec_dimensions(nc->details.framenum); - if(nc->details.frame){ -//fprintf(stderr, "seeking subimage: %d\n", nc->details.image->current_subimage() + 1); +int oiio_decode(ncvisual* nc) { +//fprintf(stderr, "current subimage: %d frame: %p\n", nc->details->image->current_subimage(), nc->details->frame.get()); + const auto &spec = nc->details->image->spec_dimensions(nc->details->framenum); + if(nc->details->frame){ +//fprintf(stderr, "seeking subimage: %d\n", nc->details->image->current_subimage() + 1); OIIO::ImageSpec newspec; - if(!nc->details.image->seek_subimage(nc->details.image->current_subimage() + 1, 0, newspec)){ + if(!nc->details->image->seek_subimage(nc->details->image->current_subimage() + 1, 0, newspec)){ return 1; } // FIXME check newspec vis-a-vis image->spec()? } -//fprintf(stderr, "SUBIMAGE: %d\n", nc->details.image->current_subimage()); +//fprintf(stderr, "SUBIMAGE: %d\n", nc->details->image->current_subimage()); auto pixels = spec.width * spec.height;// * spec.nchannels; if(spec.nchannels < 3 || spec.nchannels > 4){ return -1; // FIXME get some to test with } - nc->details.frame = std::make_unique(pixels); + nc->details->frame = std::make_unique(pixels); if(spec.nchannels == 3){ // FIXME replace with channel shuffle - std::fill(nc->details.frame.get(), nc->details.frame.get() + pixels, 0xfffffffful); + std::fill(nc->details->frame.get(), nc->details->frame.get() + pixels, 0xfffffffful); } -//fprintf(stderr, "READING: %d %ju\n", nc->details.image->current_subimage(), nc->details.framenum); - if(!nc->details.image->read_image(nc->details.framenum++, 0, 0, spec.nchannels, OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4), nc->details.frame.get(), 4)){ +//fprintf(stderr, "READING: %d %ju\n", nc->details->image->current_subimage(), nc->details->framenum); + if(!nc->details->image->read_image(nc->details->framenum++, 0, 0, spec.nchannels, OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4), nc->details->frame.get(), 4)){ return -1; } -//fprintf(stderr, "READ: %d %ju\n", nc->details.image->current_subimage(), nc->details.framenum); +//fprintf(stderr, "READ: %d %ju\n", nc->details->image->current_subimage(), nc->details->framenum); /*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->details.frame[i]) & 0xff, - (nc->details.frame[i] >> 8) & 0xff, - (nc->details.frame[i] >> 16) & 0xff, - nc->details.frame[i] >> 24 + (nc->details->frame[i]) & 0xff, + (nc->details->frame[i] >> 8) & 0xff, + (nc->details->frame[i] >> 16) & 0xff, + nc->details->frame[i] >> 24 ); }*/ nc->cols = spec.width; @@ -72,10 +106,10 @@ int ncvisual_decode(ncvisual* nc) { nc->rowstride = nc->cols * 4; OIIO::ImageSpec rgbaspec = spec; rgbaspec.nchannels = 4; - nc->details.ibuf = std::make_unique(rgbaspec, nc->details.frame.get()); -//fprintf(stderr, "SUBS: %d\n", nc->details.ibuf->nsubimages()); - ncvisual_set_data(nc, static_cast(nc->details.ibuf->localpixels()), false); -//fprintf(stderr, "POST-DECODE DATA: %d %d %p %p\n", nc->rows, nc->cols, nc->data, nc->details.ibuf->localpixels()); + nc->details->ibuf = std::make_unique(rgbaspec, nc->details->frame.get()); +//fprintf(stderr, "SUBS: %d\n", nc->details->ibuf->nsubimages()); + ncvisual_set_data(nc, static_cast(nc->details->ibuf->localpixels()), false); +//fprintf(stderr, "POST-DECODE DATA: %d %d %p %p\n", nc->rows, nc->cols, nc->data, nc->details->ibuf->localpixels()); return 0; } @@ -83,10 +117,10 @@ int ncvisual_decode_loop(ncvisual* ncv){ int r = ncvisual_decode(ncv); if(r == 1){ OIIO::ImageSpec newspec; - if(ncv->details.image->seek_subimage(0, 0, newspec)){ + if(ncv->details->image->seek_subimage(0, 0, newspec)){ return -1; } - ncv->details.framenum = 0; + ncv->details->framenum = 0; if(ncvisual_decode(ncv) < 0){ return -1; } @@ -98,13 +132,13 @@ int ncvisual_decode_loop(ncvisual* ncv){ int ncvisual_resize(ncvisual* nc, int rows, int cols) { //fprintf(stderr, "%d/%d -> %d/%d on the resize\n", ncv->rows, ncv->cols, rows, cols); auto ibuf = std::make_unique(); - if(nc->details.ibuf && (nc->cols != cols || nc->rows != rows)){ // scale it + if(nc->details->ibuf && (nc->cols != cols || nc->rows != rows)){ // scale it OIIO::ImageSpec sp{}; sp.width = cols; sp.height = rows; ibuf->reset(sp, OIIO::InitializePixels::Yes); OIIO::ROI roi(0, cols, 0, rows, 0, 1, 0, 4); - if(!OIIO::ImageBufAlgo::resize(*ibuf, *nc->details.ibuf, "", 0, roi)){ + if(!OIIO::ImageBufAlgo::resize(*ibuf, *nc->details->ibuf, "", 0, roi)){ return -1; } nc->cols = cols; @@ -112,12 +146,12 @@ int ncvisual_resize(ncvisual* nc, int rows, int cols) { nc->rowstride = cols * 4; ncvisual_set_data(nc, static_cast(ibuf->localpixels()), false); //fprintf(stderr, "HAVE SOME NEW DATA: %p\n", ibuf->localpixels()); - nc->details.ibuf = std::move(ibuf); + nc->details->ibuf = std::move(ibuf); } return 0; } -int ncvisual_blit(struct ncvisual* ncv, int rows, int cols, +int oiio_blit(struct ncvisual* ncv, int rows, int cols, ncplane* n, const struct blitset* bset, int placey, int placex, int begy, int begx, int leny, int lenx, bool blendcolors) { @@ -125,13 +159,13 @@ int ncvisual_blit(struct ncvisual* ncv, int rows, int cols, void* data = nullptr; int stride = 0; auto ibuf = std::make_unique(); - if(ncv->details.ibuf && (ncv->cols != cols || ncv->rows != rows)){ // scale it + if(ncv->details->ibuf && (ncv->cols != cols || ncv->rows != rows)){ // scale it OIIO::ImageSpec sp{}; sp.width = cols; sp.height = rows; ibuf->reset(sp, OIIO::InitializePixels::Yes); OIIO::ROI roi(0, cols, 0, rows, 0, 1, 0, 4); - if(!OIIO::ImageBufAlgo::resize(*ibuf, *ncv->details.ibuf, "", 0, roi)){ + if(!OIIO::ImageBufAlgo::resize(*ibuf, *ncv->details->ibuf, "", 0, roi)){ return -1; } stride = cols * 4; @@ -205,37 +239,53 @@ char* ncvisual_subtitle(const ncvisual* ncv) { // no support in OIIO /* auto ncvisual_rotate(ncvisual* ncv, double rads) -> int { OIIO::ROI roi(0, ncv->cols, 0, ncv->rows, 0, 1, 0, 4); - auto tmpibuf = std::move(*ncv->details.ibuf); - ncv->details.ibuf = std::make_unique(); + auto tmpibuf = std::move(*ncv->details->ibuf); + ncv->details->ibuf = std::make_unique(); OIIO::ImageSpec sp{}; sp.set_format(OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4)); sp.nchannels = 4; - ncv->details.ibuf->reset(); - if(!OIIO::ImageBufAlgo::rotate(*ncv->details.ibuf, tmpibuf, rads, "", 0, true, roi)){ + ncv->details->ibuf->reset(); + if(!OIIO::ImageBufAlgo::rotate(*ncv->details->ibuf, tmpibuf, rads, "", 0, true, roi)){ return NCERR_DECODE; // FIXME need we do anything further? } ncv->rowstride = ncv->cols * 4; - ncvisual_set_data(ncv, static_cast(ncv->details.ibuf->localpixels()), false); + ncvisual_set_data(ncv, static_cast(ncv->details->ibuf->localpixels()), false); return NCERR_SUCCESS; } */ -auto ncvisual_details_seed(ncvisual* ncv) -> void { +auto oiio_details_seed(ncvisual* ncv) -> void { (void)ncv; // FIXME? } -int ncvisual_init(int loglevel) { +int oiio_init(int loglevel) { // FIXME set OIIO global attribute "debug" based on loglevel (void)loglevel; // FIXME check OIIO_VERSION_STRING components against linked openimageio_version() return 0; // allow success here } -extern "C" { // FIXME would be nice to have OIIO::attributes("libraries") in here -const char* oiio_version(void){ - return OIIO_VERSION_STRING; +void oiio_printbanner(const notcurses* nc __attribute__ ((unused))){ + printf(" openimageio %s\n", OIIO_VERSION_STRING); } + +const static ncvisual_implementation oiio_impl = { + .ncvisual_init = oiio_init, + .ncvisual_decode = oiio_decode, + .ncvisual_blit = oiio_blit, + .ncvisual_create = oiio_create, + .ncvisual_from_file = oiio_from_file, + .ncvisual_printbanner = oiio_printbanner, + .ncvisual_details_seed = oiio_details_seed, + .ncvisual_details_destroy = oiio_details_destroy, + .canopen_images = true, + .canopen_videos = false, +}; + +static void inject_implementation(void){ + notcurses_set_ncvisual_implementation(&oiio_impl); } + #endif diff --git a/tools/notcurses++.pc.in b/tools/notcurses++.pc.in index 42fa811ea..8b88c33cf 100644 --- a/tools/notcurses++.pc.in +++ b/tools/notcurses++.pc.in @@ -8,5 +8,5 @@ Description: C++ bindings for notcurses Version: @PROJECT_VERSION@ Requires: notcurses >= @PROJECT_VERSION@ -Libs: -L${libdir} -lnotcurses++ +Libs: -L${libdir} -lnotcurses -lnotcurses++ Cflags: -I${includedir} diff --git a/tools/notcurses-core.pc.in b/tools/notcurses-core.pc.in new file mode 100644 index 000000000..e6bfe4f76 --- /dev/null +++ b/tools/notcurses-core.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: @PROJECT_NAME@ +Description: TUI library for modern terminal emulators +Version: @PROJECT_VERSION@ + +Requires: +Libs: -L${libdir} -lnotcurses-core +Cflags: -I${includedir} diff --git a/tools/notcurses.pc.in b/tools/notcurses.pc.in index 367523693..e6652d358 100644 --- a/tools/notcurses.pc.in +++ b/tools/notcurses.pc.in @@ -8,5 +8,5 @@ Description: TUI library for modern terminal emulators Version: @PROJECT_VERSION@ Requires: -Libs: -L${libdir} -lnotcurses +Libs: -L${libdir} -lnotcurses-core -lnotcurses Cflags: -I${includedir}