summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Houghton2017-06-29 13:11:05 -0400
committerJames Houghton2017-06-29 13:11:05 -0400
commit765865a7d019c500f293a7a653e7cc54aa832a75 (patch)
tree096fc8443e390ab404bdc926129d51d616582f97
downloadaur-765865a7d019c500f293a7a653e7cc54aa832a75.tar.gz
Add 0.0.1 ttyvideo
-rw-r--r--.SRCINFO16
-rw-r--r--.gitignore10
-rw-r--r--CMakeLists.txt21
-rw-r--r--PKGBUILD21
-rw-r--r--README.md21
-rw-r--r--handle.c174
-rw-r--r--handle.h30
-rw-r--r--ttyvideo.cpp306
8 files changed, 599 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO
new file mode 100644
index 000000000000..4e71dd43c3c8
--- /dev/null
+++ b/.SRCINFO
@@ -0,0 +1,16 @@
+pkgbase = ttyvideo
+ pkgdesc = ttyvideo displays videos in the terminal.
+ pkgver = 0.0.1
+ pkgrel = 1
+ url = https://github.com/jamesthoughton/ttyvideo
+ arch = any
+ license = MIT
+ depends = opencv,
+ depends = gsteamer,
+ depends = gst-plugins-base,
+ depends = cmake
+ source = https://github.com/jamesthoughton/ttyvideo/archive/0.0.1.tar.gz
+ md5sums = 862c8cf756ad896f82654565ffc7f5ad
+
+pkgname = ttyvideo
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000000..0c7f348b2bfc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+opencv/
+CMake*
+!CMakeLists.txt
+cmake_install.cmake
+*.a
+*.o
+*.so
+ttyvideo
+Makefile
+install_manifest.txt
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 000000000000..2cc034700bdd
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,21 @@
+cmake_minimum_required(VERSION 2.8)
+project(ttyvideo)
+find_package(OpenCV REQUIRED)
+
+if(MSVC)
+ # Visual Studio -- /W4
+ if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
+ string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+ else()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
+ endif()
+elseif(CMAKE_COMPILER_IS_GNUXX OR CMAKE_COMPILER_IS_GNUCC)
+ # GCC -- -Wall -pedantic
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic")
+endif()
+
+add_library(handle STATIC handle.c)
+add_executable(ttyvideo ttyvideo.cpp)
+target_link_libraries(ttyvideo ${OpenCV_LIBS} -lm handle)
+
+install(TARGETS ttyvideo DESTINATION bin)
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 000000000000..102c5468e152
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,21 @@
+pkgname=ttyvideo
+pkgver='0.0.1'
+pkgrel='1'
+pkgdesc="ttyvideo displays videos in the terminal."
+arch=('any')
+license=('MIT')
+depends=('opencv', 'gsteamer', 'gst-plugins-base', 'cmake')
+source=("https://github.com/jamesthoughton/ttyvideo/archive/0.0.1.tar.gz")
+url='https://github.com/jamesthoughton/ttyvideo'
+md5sums=('862c8cf756ad896f82654565ffc7f5ad')
+
+build() {
+ cd $srcdir/ttyvideo
+ cmake .
+ make
+}
+
+package() {
+ cd $srcdir/ttyvideo
+ install -Dm 755 ttyvideo $pkgdir/usr/bin/ttyvideo
+}
diff --git a/README.md b/README.md
new file mode 100644
index 000000000000..671d6f9be322
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+# ttyvideo
+Pushing the limits of resolution and color depth... backwards!
+ttyvideo is a video player that runs in a 256-color terminal.
+## Dependencies
+ttyvideo requires OpenCV (2 or 3) compiled with video codecs. If your package manager doesn't carry a version of OpenCV like this, you're going to need to download and compile its [source code](https://github.com/opencv/opencv "OpenCV source") before you can use ttyvideo.
+ttyvideo also uses CMake>=2.8 to generate files used in the installation process.
+## Installation
+You can compile ttyvideo with CMake and Make.
+```shell
+cmake .
+make
+```
+After compiling, it can be installed by performing
+```shell
+sudo make install
+```
+
+## How it works
+ttyvideo works with most visual media types: videos, images, GIFs, etc. This is due to the usage of OpenCV to read media.
+Right now, ttyvideo doesn't do anything special when rendering videos.
+ttyvideo will scale any video to fit the aspect ratio of the terminal it's running in. ttyvideo also does not perform any smoothing operations; each character slot in the terminal corresponds to exactly one pixel in the source video, not a combination of them. Feel free to add smoothing ttyvideo!
diff --git a/handle.c b/handle.c
new file mode 100644
index 000000000000..1d18b8c2fc82
--- /dev/null
+++ b/handle.c
@@ -0,0 +1,174 @@
+#include "handle.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+int error(char* message) {
+
+ fprintf(stderr, "%s: %s\n", "ttyvideo", message);
+
+ return 1;
+
+}
+
+static int numOptionsSpecified = 0;
+static int numUniqueOptionsSpecified = 0;
+static char** argumentCallMap = NULL;
+static char** argumentMemStore = NULL;
+static int* argumentNumStore = NULL;
+static char** helpMessages = NULL;
+static char* defaultArg = NULL;
+static char* defaultArgHelp = NULL;
+
+#define MAX_ARG_SIZE 200
+
+char* setDefaultArgument(char* helpText) {
+
+ defaultArg = (char*)malloc(sizeof(char) * MAX_ARG_SIZE);
+ defaultArgHelp = (char*)malloc(sizeof(char) * strlen(helpText));
+ strcpy(defaultArgHelp, helpText);
+
+ defaultArg[0] = '\0';
+
+ return defaultArg;
+
+}
+
+#define MAX_ACCESS_MAP_SIZE 100
+#define MAX_HELP_MESSAGE_ARRAY_SIZE 100
+
+char* addArgument(char* helpText, int numArguments, char* firstCall, char* secondCall) {
+
+ if(firstCall == NULL) {
+ fprintf(stderr, "No option specified for parameter\n");
+ return NULL;
+ }
+ if(firstCall[0] != '-') {
+ fprintf(stderr, "Option '%s' does not start with a '-'\n", firstCall);
+ return NULL;
+ }
+ if(secondCall != NULL && secondCall[0] != '-') {
+ fprintf(stderr, "Option '%s' does not start with a '-'\n", secondCall);
+ }
+
+ if(argumentCallMap == NULL) {
+ argumentCallMap = (char**)malloc(sizeof(char*) * MAX_ACCESS_MAP_SIZE);
+ }
+ if(argumentMemStore == NULL) {
+ argumentMemStore = (char**)malloc(sizeof(char*) * MAX_ACCESS_MAP_SIZE);
+ }
+ if(argumentNumStore == NULL) {
+ argumentNumStore = (int*)malloc(sizeof(int*) * MAX_ACCESS_MAP_SIZE);
+ }
+ if(helpMessages == NULL) {
+ helpMessages = (char**)malloc(sizeof(char*) * MAX_HELP_MESSAGE_ARRAY_SIZE);
+ }
+
+ // TODO: Add ability to allocate lots of these
+ char* argumentMemLocation = (char*)malloc(sizeof(char) * MAX_ARG_SIZE);
+ if(argumentMemLocation == NULL) {
+ fprintf(stderr, "Couldn't allocate memory to store argument to pass to %s", firstCall);
+ return NULL;
+ }
+ argumentMemLocation[0] = '\0';
+
+ int argumentAccessLocation = numOptionsSpecified++;
+ argumentCallMap[argumentAccessLocation] = (char*)malloc(sizeof(char) * strlen(firstCall));
+ strcpy(argumentCallMap[argumentAccessLocation], firstCall);
+ argumentMemStore[argumentAccessLocation] = argumentMemLocation;
+ argumentNumStore[argumentAccessLocation] = numArguments;
+
+
+ if(secondCall == NULL) {
+
+ helpMessages[numUniqueOptionsSpecified] = (char*)malloc(sizeof(char) * (strlen(helpText) + strlen(firstCall) + 4));
+
+ strcpy(helpMessages[numUniqueOptionsSpecified], firstCall);
+ strcat(helpMessages[numUniqueOptionsSpecified], ":\t");
+ strcat(helpMessages[numUniqueOptionsSpecified], helpText);
+
+ numUniqueOptionsSpecified++;
+
+ return argumentMemLocation;
+ }
+
+ helpMessages[numUniqueOptionsSpecified] = (char*)malloc(sizeof(char) * (strlen(helpText) + strlen(firstCall) + 2 + 4 + strlen(secondCall)));
+
+ strcpy(helpMessages[numUniqueOptionsSpecified], firstCall);
+ strcat(helpMessages[numUniqueOptionsSpecified], ", ");
+ strcat(helpMessages[numUniqueOptionsSpecified], secondCall);
+ strcat(helpMessages[numUniqueOptionsSpecified], ":\t");
+ strcat(helpMessages[numUniqueOptionsSpecified], helpText);
+
+ numUniqueOptionsSpecified++;
+
+ argumentAccessLocation = numOptionsSpecified++;
+ argumentCallMap[argumentAccessLocation] = (char*)malloc(sizeof(char) * strlen(secondCall));
+ strcpy(argumentCallMap[argumentAccessLocation], secondCall);
+ argumentMemStore[argumentAccessLocation] = argumentMemLocation;
+ argumentNumStore[argumentAccessLocation] = numArguments;
+
+ return argumentMemLocation;
+}
+
+void printUsage() {
+
+ if(helpMessages != NULL) {
+ register int i;
+ for(i = 0; i < numUniqueOptionsSpecified; ++i) {
+ printf("%s\n", helpMessages[i]);
+ }
+ } else {
+ printf("No options to display\n");
+ }
+
+}
+
+int handle(int argc, char** argv) {
+
+ register int i, j;
+ int numArguments;
+ for(i = 1; i < argc; ++i) {
+ for(j = 0; j < numOptionsSpecified; ++j) {
+ if(argv[i][0] != '-') {
+ if(defaultArg == NULL) {
+ fprintf(stderr, "Argument '%s' was passed without an option, and no default argument has been set\n", argv[0]);
+ return 1;
+ }
+ if(defaultArg[0] != '\0') {
+ fprintf(stderr, "Unrecognized option '%s'\n", argv[i]);
+ return 1;
+ }
+ strcpy(defaultArg, argv[i]);
+ break;
+ }
+ if(strcmp(argv[i], argumentCallMap[j]))
+ continue;
+ numArguments = argumentNumStore[j];
+ if(numArguments) {
+ if(i < argc - 1 && argv[i+1][0] != '-') {
+ strcpy(argumentMemStore[j], argv[++i]);
+ } else {
+ fprintf(stderr, "Option '%s' requires an argument\n", argv[i]);
+ return 1;
+ }
+ }
+ else
+ strcpy(argumentMemStore[j], "1"); // The option is present
+ break;
+ }
+ if(j == numOptionsSpecified) {
+ fprintf(stderr, "Unrecognized option '%s'\n", argv[i]);
+ return 1;
+ }
+ }
+
+ return 0;
+
+}
+
+void sig_handler(int signo) {
+ if(signo == SIGINT)
+ terminate = 1;
+}
diff --git a/handle.h b/handle.h
new file mode 100644
index 000000000000..5a8f6806628e
--- /dev/null
+++ b/handle.h
@@ -0,0 +1,30 @@
+#ifndef HANDLE_H
+#define HANDLE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+char* filename;
+
+char* setDefaultArgument(char* helpText);
+
+char* addArgument(char* helpText, int numArguments, char* firstCall, char* secondCall);
+
+void printUsage();
+
+int error(char* message);
+int handle(int argc, char** argv);
+
+int terminate;
+
+#define TAKES_NO_ARGUMENTS 0
+#define TAKES_ONE_ARGUMENT 1
+
+void sig_handler(int signo);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // HANDLE_H
diff --git a/ttyvideo.cpp b/ttyvideo.cpp
new file mode 100644
index 000000000000..361eaf20373b
--- /dev/null
+++ b/ttyvideo.cpp
@@ -0,0 +1,306 @@
+// #include <opencv2/core/core_c.h>
+// #include <opencv2/videoio/videoio_c.h>
+// #include <opencv2/videoio/videoio.hpp>
+#include <opencv2/core.hpp>
+#include <opencv2/video.hpp>
+#include <opencv2/highgui.hpp>
+#include <opencv2/opencv.hpp>
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "handle.h"
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <signal.h>
+
+#ifdef __MACH__
+#include <mach/clock.h>
+#include <mach/mach.h>
+#endif
+
+#define NANO_CONV_FACTOR 1000000000
+
+#define COLOR_TEXT_FORMAT "\x1B[48;05;%um\x1B[38;05;%um%c"
+#define COLOR_FORMAT "\x1B[48;05;%um "
+#define COLOR_RESET "\x1B[0m"
+
+int waitFrame(uint64_t, uint64_t);
+unsigned char generateANSIColor(unsigned char, unsigned char, unsigned char);
+void getTTYDims();
+int play(char*, char*, char*, int);
+void getSystemTime(struct timespec*);
+
+int tty_width;
+int tty_height;
+int tty_width_custom = 0;
+int tty_height_custom = 0;
+
+int no_interrupts = 0;
+
+#define C (char*)
+
+int main(int argc, char** argv) {
+
+ char* filename = setDefaultArgument(C"infile");
+ char* width_option = addArgument(C"Output width", TAKES_ONE_ARGUMENT, C"-w", C"--width");
+ char* height_option = addArgument(C"Output height", TAKES_ONE_ARGUMENT, C"-h", C"--height");
+ char* sleep_option = addArgument(C"Add a pause between loops or after plays (seconds)", TAKES_ONE_ARGUMENT, C"-s", C"--sleep");
+ char* noexit_option = addArgument(C"Prevent the program for exiting", TAKES_NO_ARGUMENTS, C"--no-exit", NULL);
+ char* loop_option = addArgument(C"Loop videos", TAKES_NO_ARGUMENTS, C"-l", C"--loop");
+ char* help_option = addArgument(C"Print usage", TAKES_NO_ARGUMENTS, C"--help", NULL);
+ char* nointer_option = addArgument(C"No interrupts", TAKES_NO_ARGUMENTS, C"--no-interrupts", NULL);
+ char* fps_option = addArgument(C"FPS", TAKES_ONE_ARGUMENT, C"--fps", NULL);
+ char* string_option = addArgument(C"String to place in the foreground", TAKES_ONE_ARGUMENT, C"--string", NULL);
+ char* info_option = addArgument(C"Print terminal dimensions", TAKES_NO_ARGUMENTS, C"-i", C"--info");
+
+ int argError;
+ argError = handle(argc, argv);
+ if(argError) {
+ error((char*)"Run ttyvideo --help for more information");
+ return argError;
+ }
+
+ if(help_option[0] != '\0') {
+ printUsage();
+ return 0;
+ }
+
+ if(info_option[0] != '\0') {
+ getTTYDims();
+ printf("Height: %d\nWidth: %d\n",
+ tty_height, tty_width);
+ return 0;
+ }
+
+ if(filename[0] == '\0')
+ return error((char*)"No input file specified");
+
+ getTTYDims(); // get initial dims
+
+ tty_height_custom = atoi(height_option);
+ tty_width_custom = atoi(width_option);
+
+ if(tty_height == 0 && tty_width == 0) {
+ if(tty_height_custom == 0 || tty_width_custom == 0) {
+ error((char*)"Could not detect terminal dimensions and specified dimensions are incomplete");
+ return 1;
+ }
+ } else {
+ if(tty_height_custom > tty_height || tty_width_custom > tty_width) {
+ error((char*)"The specified dimensions are too large, but we will play the video anyway");
+ }
+ }
+
+ getTTYDims(); // get real dims -- including user specification
+
+ int sleep_time = sleep_option[0] == '\0' ? 0 : atoi(sleep_option);
+
+ int noexit = noexit_option[0] == '\0' ? 0 : 1;
+
+ int loop = loop_option[0] == '\0' ? 0 : 1;
+
+ setvbuf(stdout, NULL, _IOFBF, 0);
+
+ signal(SIGINT, sig_handler);
+
+ no_interrupts = nointer_option[0] == '\0' ? 0 : 1;
+
+ register int frameNum = 0;
+ do {
+ frameNum = play(filename, string_option, fps_option, frameNum);
+
+ if(frameNum < 0)
+ return 1;
+
+ if(terminate && !no_interrupts) {
+ printf("\n");
+ fflush(stdout);
+ return 0;
+ }
+
+ if(sleep_time)
+ sleep(sleep_time);
+
+ } while(loop && frameNum > 1);
+
+ if((loop && frameNum == 1) || noexit) {
+ terminate = 0; // Terminate to 0 so we can work with it
+ while(true) {
+ sleep(1);
+ // When SIG_INT is sent to a frozen frame,
+ // re-render the frame
+ if(terminate) {
+ if(!no_interrupts) return 0;
+ frameNum = play(filename, string_option, fps_option, frameNum);
+ terminate = 0;
+ }
+ }
+ }
+
+ printf("\n");
+ fflush(stdout);
+ return 0;
+
+}
+
+int play(char* filename, char* string, char* fps_option, int subsequentPlay) {
+ cv::VideoCapture cap(filename);
+ if(!cap.isOpened()) return -1 * error((char*)"Can't read input");
+
+ double fps = fps_option[0] == '\0' ? cap.get(CV_CAP_PROP_FPS) : atof(fps_option);
+
+ struct timespec start, end;
+
+ uint64_t delta_ns;
+ uint64_t delayNecessary = fps == 0 || isnan(fps) ? 0 : NANO_CONV_FACTOR/fps;
+
+ register int frameNum = 0;
+
+ register int width, height, nchannels, step, offset;
+ register int i, j;
+ register unsigned char r_ch, b_ch, g_ch;
+ cv::Mat frame;
+ int ansiColor, ansiTextColor;
+ unsigned char* data;
+
+ int stringLength = strlen(string);
+
+ int stopAfterFrame = 0;
+
+ for(;;) {
+
+ getSystemTime(&start);
+
+ if(!frame.empty() || subsequentPlay) {
+
+ cap >> frame;
+
+ if(frame.empty()) break;
+
+ for(i = 0; i < tty_height - 1; ++i)
+ printf("\x1B[F");
+
+ } else {
+
+ cap >> frame;
+
+ if(frame.empty()) break;
+
+ }
+
+ if(frame.depth() != CV_8U) {
+ return -1 * error((char*)"Frame has incorrect depth");
+ }
+
+ width = frame.cols;
+ height = frame.rows;
+ nchannels = frame.channels();
+ step = width * nchannels;
+
+ getTTYDims();
+
+ register int stringIter = 0;
+
+ for(i = 0; i < tty_height; ++i) {
+ data = (unsigned char*)(frame.data + ((int)((float)i*height/tty_height)*step));
+ for(j = 0; j < tty_width; ++j) {
+ offset = (int)((float)j*width/tty_width) * nchannels;
+ b_ch = data[offset];
+ g_ch = data[offset+1];
+ r_ch = data[offset+2];
+
+ ansiColor = generateANSIColor(r_ch, g_ch, b_ch);
+
+ if(stringLength) {
+ ansiTextColor = b_ch + g_ch + r_ch > 0x17F ? generateANSIColor(r_ch - 50, g_ch - 50, b_ch - 50) :
+ generateANSIColor(r_ch + 50, g_ch + 50, b_ch + 50);
+
+ printf(COLOR_TEXT_FORMAT, ansiColor, ansiTextColor, string[stringIter++%stringLength]);
+ } else {
+ printf(COLOR_FORMAT, ansiColor);
+ }
+ }
+ printf(COLOR_RESET);
+ if(i < tty_height - 1) {
+ printf("\n");
+ } else {
+ printf("\x1B[m");
+ fflush(stdout);
+ }
+ }
+
+ if(stopAfterFrame) {
+ break;
+ }
+
+ if(terminate && !no_interrupts) {
+ stopAfterFrame = 1;
+ }
+
+ getSystemTime(&end);
+
+ delta_ns = (end.tv_sec - start.tv_sec) * NANO_CONV_FACTOR + (end.tv_nsec - start.tv_nsec);
+
+ waitFrame(delayNecessary, delta_ns);
+
+ frameNum++;
+
+ }
+
+ return frameNum;
+
+}
+
+int waitFrame(uint64_t delayNecessary, uint64_t delta_ns) {
+
+ struct timespec ts;
+ long totalNanoSec;
+
+ totalNanoSec = (delayNecessary - delta_ns);
+
+ if(totalNanoSec < 0) return 1;
+
+ ts.tv_sec = totalNanoSec/NANO_CONV_FACTOR;
+ ts.tv_nsec = totalNanoSec%NANO_CONV_FACTOR;
+ nanosleep(&ts, NULL);
+
+ return 0;
+
+}
+
+unsigned char generateANSIColor(unsigned char r, unsigned char g, unsigned char b) {
+
+ return 16 + (36 * lround(r*5.0/256)) + (6 * lround(g*5.0/256)) + lround(b*5.0/256);
+
+}
+
+void getTTYDims() {
+
+ struct winsize w;
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
+ tty_width = tty_width_custom > 0 ? tty_width_custom : w.ws_col;
+ tty_height = tty_height_custom > 0 ? tty_height_custom : w.ws_row;
+
+}
+
+void getSystemTime(struct timespec* tv) {
+
+ #ifdef __MACH__
+
+ clock_serv_t cclock;
+ mach_timespec_t mts;
+ host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+ clock_get_time(cclock, &mts);
+ mach_port_deallocate(mach_task_self(), cclock);
+
+ tv->tv_sec = mts.tv_sec;
+ tv->tv_nsec = mts.tv_nsec;
+
+ #else
+
+ clock_gettime(CLOCK_REALTIME, tv);
+
+ #endif
+
+}