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.
pull/1305/head
Nick Black 3 years ago committed by GitHub
parent c62a3f7464
commit 9112185657
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -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)

@ -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

@ -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.

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 23 KiB

@ -8,9 +8,10 @@ notcurses - TUI library for modern terminal emulators
# SYNOPSIS
**#include <notcurses/notcurses.h>**
**#include <notcurses/notcurses.h>** or
**#include <notcurses/notcurses-core.h>**
**-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).

@ -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;

@ -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);
}

@ -1,72 +0,0 @@
#ifndef NOTCURSES_FFMPEG
#define NOTCURSES_FFMPEG
#include "version.h"
#ifdef USE_FFMPEG
extern "C" {
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/pixdesc.h>
#include <libavutil/version.h>
#include <libavutil/imgutils.h>
#include <libavutil/rational.h>
#include <libswscale/swscale.h>
#include <libswscale/version.h>
#include <libavformat/version.h>
#include <libavformat/avformat.h>
} // 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

@ -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

@ -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

@ -1,38 +0,0 @@
#ifndef NOTCURSES_OIIO
#define NOTCURSES_OIIO
// OpenImageIO implementation of ncvisual_details
#include "version.h"
#ifdef USE_OIIO
#include <OpenImageIO/filter.h>
#include <OpenImageIO/version.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imagebufalgo.h>
typedef struct ncvisual_details {
std::unique_ptr<OIIO::ImageInput> image; // must be close()d
std::unique_ptr<uint32_t[]> frame;
std::unique_ptr<OIIO::ImageBuf> 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

@ -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 {

@ -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

@ -1,18 +1,47 @@
#include "builddef.h"
#ifdef USE_FFMPEG
#include "ffmpeg.h"
extern "C" {
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/pixdesc.h>
#include <libavutil/version.h>
#include <libavutil/imgutils.h>
#include <libavutil/rational.h>
#include <libswscale/swscale.h>
#include <libswscale/version.h>
#include <libavformat/version.h>
#include <libavformat/avformat.h>
}
#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<uint32_t*>(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<uint32_t*>(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<AVPixelFormat>(inframe->format),
cols, rows,
static_cast<AVPixelFormat>(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<uint8_t*>(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<uint8_t*>(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

@ -1,29 +1,63 @@
#include "builddef.h"
#ifdef USE_OIIO
#include "oiio.h"
#include <OpenImageIO/filter.h>
#include <OpenImageIO/version.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imagebufalgo.h>
#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<OIIO::ImageInput> image; // must be close()d
std::unique_ptr<uint32_t[]> frame;
std::unique_ptr<OIIO::ImageBuf> 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<uint32_t[]>(pixels);
nc->details->frame = std::make_unique<uint32_t[]>(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<OIIO::ImageBuf>(rgbaspec, nc->details.frame.get());
//fprintf(stderr, "SUBS: %d\n", nc->details.ibuf->nsubimages());
ncvisual_set_data(nc, static_cast<uint32_t*>(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<OIIO::ImageBuf>(rgbaspec, nc->details->frame.get());
//fprintf(stderr, "SUBS: %d\n", nc->details->ibuf->nsubimages());
ncvisual_set_data(nc, static_cast<uint32_t*>(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<OIIO::ImageBuf>();
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<uint32_t*>(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<OIIO::ImageBuf>();
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<OIIO::ImageBuf>();
auto tmpibuf = std::move(*ncv->details->ibuf);
ncv->details->ibuf = std::make_unique<OIIO::ImageBuf>();
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<uint32_t*>(ncv->details.ibuf->localpixels()), false);
ncvisual_set_data(ncv, static_cast<uint32_t*>(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

@ -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}

@ -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}

@ -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}

Loading…
Cancel
Save