├── .gitmodules ├── autogen.sh ├── test ├── data │ └── data.h ├── tests.h ├── tests.cpp ├── tools │ └── gendata ├── display_matrix.cpp ├── Makefile ├── indexer.cpp └── hdr.cpp ├── ffms2.pc.in ├── src ├── index │ ├── ffmsindex.manifest │ ├── vsutf16.h │ └── ffmsindex.cpp ├── core │ ├── filehandle.h │ ├── zipfile.h │ ├── videoutils.h │ ├── filehandle.cpp │ ├── indexing.h │ ├── track.h │ ├── videosource.h │ ├── utils.h │ ├── zipfile.cpp │ ├── utils.cpp │ ├── audiosource.h │ ├── videoutils.cpp │ └── ffms.cpp ├── vapoursynth │ ├── vapoursource.h │ ├── vapoursource4.h │ ├── VSHelper.h │ ├── VSHelper4.h │ ├── vapoursynth.cpp │ ├── vapoursynth4.cpp │ └── VapourSynth.h └── avisynth │ └── avssources.h ├── .gitattributes ├── m4 └── check_zlib.m4 ├── version.sh ├── .github └── workflows │ └── ci.yml ├── include └── ffmscompat.h ├── COPYING ├── .gitignore ├── Makefile.am ├── README.md ├── etc └── FFMS2.avsi ├── configure.ac └── doc └── ffms2-vapoursynth.md /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/googletest"] 2 | path = test/googletest 3 | url = https://github.com/google/googletest 4 | branch = master 5 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | mkdir -p src/config 3 | echo Running autoreconf... 4 | autoreconf -ivf 5 | echo Running configure... 6 | ./configure "$@" 7 | -------------------------------------------------------------------------------- /test/data/data.h: -------------------------------------------------------------------------------- 1 | #ifndef _TEST_DATA_H 2 | #define _TEST_DATA_H 3 | 4 | #include 5 | 6 | typedef struct TestFrameData { 7 | int64_t PTS; 8 | int64_t Duration; 9 | int Width; 10 | int Height; 11 | const char *PixelFormat; 12 | bool Keyframe; 13 | uint8_t SHA256[32]; // SHA256 of the frame's decode planes 14 | } TestFrameData; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /ffms2.pc.in: -------------------------------------------------------------------------------- 1 | # @configure_input@ 2 | 3 | prefix=@prefix@ 4 | exec_prefix=@exec_prefix@ 5 | libdir=@libdir@ 6 | includedir=@includedir@ 7 | 8 | Name: ffms2 9 | Description: The Fabulous FM Library 2 10 | Requires.private: libavformat libavcodec libswscale libavutil libswresample 11 | Version: @FFMS_VERSION@ 12 | Libs.private: @ZLIB_LDFLAGS@ -lz 13 | Libs: -L${libdir} -lffms2 14 | Cflags: -I${includedir} 15 | -------------------------------------------------------------------------------- /src/index/ffmsindex.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # But don't mess up Unix scripts in the process 5 | *.sh eol=lf 6 | *.in eol=lf 7 | *.am eol=lf 8 | *.ac eol=lf 9 | *.m4 eol=lf 10 | # Scripts that don't have extensions... 11 | /install-sh eol=lf 12 | /compile eol=lf 13 | /configure eol=lf 14 | /config.guess eol=lf 15 | /config.sub eol=lf 16 | /depcomp eol=lf 17 | /install-sh eol=lf 18 | /missing eol=lf 19 | -------------------------------------------------------------------------------- /test/tests.h: -------------------------------------------------------------------------------- 1 | #ifndef _FFMS2_TESTS_H 2 | #define _FFMS2_TESTS_H 3 | 4 | #include 5 | #include 6 | 7 | extern "C" { 8 | #include 9 | #include 10 | #include 11 | } 12 | 13 | #include "data/data.h" 14 | 15 | #define NULL_CHECK(arg) do { \ 16 | EXPECT_NE(nullptr, arg); \ 17 | if (arg == nullptr) \ 18 | return false; \ 19 | } while(0) 20 | 21 | #define EQ_CHECK(arg1,arg2) do { \ 22 | EXPECT_EQ(arg1, arg2); \ 23 | if (arg1 != arg2) \ 24 | return false; \ 25 | } while (0) 26 | 27 | #define STRINGIFY2(x) #x 28 | #define STRINGIFY(x) STRINGIFY2(x) 29 | 30 | #define TEST_ENTRY(file, data) { file, data, sizeof(data) / sizeof(*data) } 31 | 32 | typedef struct TestDataMap { 33 | const char *Filename; 34 | const TestFrameData *TestData; 35 | const size_t TestDataLen; 36 | } TestDataMap; 37 | 38 | bool CheckFrame(const FFMS_Frame *Frame, const FFMS_FrameInfo *info, const TestFrameData *Data); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /m4/check_zlib.m4: -------------------------------------------------------------------------------- 1 | AC_DEFUN([CHECK_ZLIB],[ 2 | AC_ARG_WITH(zlib,AS_HELP_STRING([--with-zlib=DIR],[specify zlib's root installation folder]), 3 | [ZLIB_HOME="$with_zlib"]) 4 | 5 | if test -n "${ZLIB_HOME}" -a "${ZLIB_HOME}" != "yes" -a "${ZLIB_HOME}" != "no" ; then 6 | ZLIB_CPPFLAGS="-I${ZLIB_HOME}/include" 7 | ZLIB_LDFLAGS="-L${ZLIB_HOME}/lib" 8 | fi 9 | 10 | ZLIB_OLD_LDFLAGS=$LDFLAGS 11 | ZLIB_OLD_CPPFLAGS=$CPPFLAGS 12 | LDFLAGS="$LDFLAGS $ZLIB_LDFLAGS" 13 | CPPFLAGS="$CPPFLAGS $ZLIB_CPPFLAGS" 14 | AC_LANG_PUSH(C) 15 | AC_CHECK_HEADER(zlib.h, [zlib_cv_zlib_h=yes], [zlib_cv_zlib_h=no]) 16 | AC_CHECK_LIB(z, inflateEnd, [zlib_cv_libz=yes], [zlib_cv_libz=no]) 17 | AC_LANG_POP(C) 18 | LDFLAGS=$ZLIB_OLD_LDFLAGS 19 | CPPFLAGS=$ZLIB_OLD_CPPFLAGS 20 | if test "$zlib_cv_libz" = "yes" -a "$zlib_cv_zlib_h" = "yes" 21 | then 22 | HAVE_ZLIB=yes 23 | AC_SUBST([ZLIB_CPPFLAGS]) 24 | AC_SUBST([ZLIB_LDFLAGS]) 25 | else 26 | AC_MSG_FAILURE([cannot locate zlib.h and -lz, specify a valid zlib installation using --with-zlib=DIR]) 27 | fi 28 | ]) 29 | -------------------------------------------------------------------------------- /version.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | strip_echo () { 4 | printf '%s' "$(echo "$@" | tr -d '\n')" 5 | } 6 | 7 | echo_maybe () { 8 | if test -n "$2"; then 9 | strip_echo "$1$2" 10 | exit 1 11 | fi 12 | } 13 | 14 | # Make sure we're in an useful dir (for out-of-tree builds) 15 | cd "$(dirname $0)" 16 | 17 | echo_maybe '' "$(cat .version 2>/dev/null)" 18 | echo_maybe '' "$(awk -F '[(< |)]+' '/define FFMS_VERSION/ { printf "%d.%d.%d%s", $3, $5, $7, ($9 ? sprintf(".%d", $9) : "") }' include/ffms.h)" 19 | echo_maybe '' "$(perl -ne '/define FFMS_VERSION.*?(\d+) << 24.*?(\d+) << 16.*?(\d+) << 8.*?(\d+)/ && print "$1.$2.$3". ($4 ? ".$4" : "")' include/ffms.h)" 20 | # All of the following break pkg-config version checking! 21 | # But if we get to this point we can't output anything "proper" anymore so at least try to be useful 22 | echo_maybe 'r' "$(git log HEAD~1.. 2>/dev/null | grep git-svn-id | cut -d@ -f2 | cut -d' ' -f1)" 23 | echo_maybe 'git-r' "$(git log HEAD~1.. 2>/dev/null | sed 1q | cut -d' ' -f2 | head -c7)" 24 | # While git returns no output when not on a git repository, 25 | # svnversion still does, so run it last 26 | echo_maybe 'r' "$(svnversion 2>/dev/null)" 27 | echo_maybe '' "unknown" 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Make Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest] 15 | cc: [gcc, clang] 16 | exclude: 17 | - os: macos-latest 18 | cc: gcc 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | with: 23 | submodules: true 24 | - name: install deps (ubuntu) 25 | if: matrix.os == 'ubuntu-latest' 26 | run: | 27 | sudo apt-get update 28 | sudo apt-get install autoconf automake wget ffmpeg libavformat-dev libavcodec-dev libswscale-dev libavutil-dev libswresample-dev 29 | - name: install deps (macOS) 30 | if: matrix.os == 'macos-latest' 31 | run: | 32 | brew update 33 | brew install autoconf automake wget ffmpeg 34 | - name: configure 35 | run: ./autogen.sh --enable-static --disable-shared 36 | - name: make 37 | run: make V=1 CXXFLAGS='-Werror -Wno-error=deprecated-declarations' -j2 -k 38 | - name: make test 39 | run: | 40 | make test-sync 41 | make test -j2 -k 42 | -------------------------------------------------------------------------------- /include/ffmscompat.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007-2011 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef FFMSCOMPAT_H 22 | #define FFMSCOMPAT_H 23 | 24 | #define VERSION_CHECK(LIB, cmp, major, minor, micro) ((LIB) cmp (AV_VERSION_INT(major, minor, micro))) 25 | 26 | #endif // FFMSCOMPAT_H 27 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | The FFMS2 source is licensed under the MIT license, but its binaries 2 | are licensed under the GPL because GPL components of FFmpeg are 3 | used. FFmpeg can be built as either LGPL, GPLv2, GPLv3, or even be 4 | nonredistributable. Refer to FFmpeg's sources for licensing information. 5 | 6 | Text of MIT license: 7 | ----------------------------------------------------------- 8 | Copyright (c) 9 | 10 | Permission is hereby granted, free of charge, to any person 11 | obtaining a copy of this software and associated documentation 12 | files (the "Software"), to deal in the Software without 13 | restriction, including without limitation the rights to use, 14 | copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the 16 | Software is furnished to do so, subject to the following 17 | conditions: 18 | 19 | The above copyright notice and this permission notice shall be 20 | included in all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 | OTHER DEALINGS IN THE SOFTWARE. 30 | ----------------------------------------------------------- 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # / 3 | /deps 4 | /testing 5 | /todo.txt 6 | 7 | # /build-msvc/ 8 | /build-msvc/*.sdf 9 | /build-msvc/*.suo 10 | /build-msvc/*.user 11 | /build-msvc/ffms2.ncb 12 | /build-msvc/ffmsindex 13 | /build-msvc/bin 14 | /build-msvc/obj 15 | 16 | # /src/config/ 17 | /src/config/config.h 18 | /src/config/config.h.in~ 19 | /src/config/auto_config.h 20 | /src/config/stamp-h1 21 | 22 | # /src/core/ 23 | /src/core/.deps 24 | /src/core/.libs 25 | /src/core/.dirstamp 26 | /src/core/*.o 27 | /src/core/*.lo 28 | /src/core/*.la 29 | 30 | # /src/index/ 31 | /src/index/.deps 32 | /src/index/.libs 33 | /src/index/.dirstamp 34 | /src/index/*.o 35 | /src/index/*.exe 36 | /src/index/ffmsindex 37 | 38 | # /src/vapoursynth/ 39 | /src/vapoursynth/.deps 40 | /src/vapoursynth/.libs 41 | /src/vapoursynth/.dirstamp 42 | /src/vapoursynth/*.o 43 | /src/vapoursynth/*.lo 44 | 45 | # /test 46 | /test/samples 47 | /test/display_matrix 48 | /test/hdr 49 | /test/.libs 50 | /test/*.o 51 | /test/*.a 52 | /test/indexer 53 | 54 | 55 | ### Autotools ### 56 | # automake 57 | Makefile.in 58 | .deps/ 59 | 60 | # autoconf 61 | /ffms2.pc 62 | /autom4te.cache 63 | /aclocal.m4 64 | /compile 65 | /config.cache 66 | /config.guess 67 | /config.guess~ 68 | config.h.in 69 | /config.log 70 | /config.status 71 | /config.sub 72 | /config.sub~ 73 | /configure 74 | /configure~ 75 | /configure.scan 76 | /depcomp 77 | /install-sh 78 | /missing 79 | /stamp-h1 80 | 81 | # libtool 82 | /libtool 83 | /ltmain.sh 84 | 85 | # m4 86 | m4/libtool.m4 87 | m4/ltoptions.m4 88 | m4/ltsugar.m4 89 | m4/ltversion.m4 90 | m4/lt~obsolete.m4 91 | 92 | # Generated Makefile 93 | Makefile 94 | -------------------------------------------------------------------------------- /test/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | extern "C" { 8 | #include 9 | #include 10 | #include 11 | } 12 | 13 | #include "tests.h" 14 | 15 | bool CheckFrame(const FFMS_Frame *Frame, const FFMS_FrameInfo *info, const TestFrameData *Data) { 16 | EQ_CHECK(info->PTS, Data->PTS); 17 | EQ_CHECK(!!info->KeyFrame, Data->Keyframe); 18 | EQ_CHECK(!!Frame->KeyFrame, Data->Keyframe); 19 | EQ_CHECK(Frame->EncodedWidth, Data->Width); 20 | EQ_CHECK(Frame->EncodedHeight, Data->Height); 21 | 22 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get((AVPixelFormat) Frame->ConvertedPixelFormat); 23 | NULL_CHECK(desc); 24 | 25 | EXPECT_STREQ(desc->name, Data->PixelFormat); 26 | if (!!strcmp(desc->name, Data->PixelFormat)) 27 | return false; 28 | 29 | struct AVSHA *sha = av_sha_alloc(); 30 | NULL_CHECK(sha); 31 | 32 | int ret = av_sha_init(sha, 256); 33 | EQ_CHECK(ret, 0); 34 | 35 | for (int i = 0; i < av_pix_fmt_count_planes((AVPixelFormat) Frame->ConvertedPixelFormat); i++) { 36 | const int subh = i == 0 ? 0 : desc->log2_chroma_h; 37 | const int subw = i == 0 ? 0 : desc->log2_chroma_w; 38 | for (int y = 0; y < Frame->EncodedHeight >> subh; y++) 39 | av_sha_update(sha, Frame->Data[i] + y * Frame->Linesize[i], Frame->EncodedWidth >> subw); 40 | } 41 | 42 | uint8_t digest[32]; 43 | av_sha_final(sha, &digest[0]); 44 | av_free(sha); 45 | 46 | bool ok; 47 | 48 | EXPECT_TRUE((ok = !memcmp(&Data->SHA256[0], &digest[0], 32))); 49 | 50 | return ok; 51 | } 52 | -------------------------------------------------------------------------------- /src/index/vsutf16.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Fredrik Mellbin 3 | * 4 | * This file is part of VapourSynth. 5 | * 6 | * VapourSynth is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2.1 of the License, or (at your option) any later version. 10 | * 11 | * VapourSynth is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with VapourSynth; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | #ifndef VSUTF16_H 22 | #define VSUTF16_H 23 | 24 | #include 25 | #ifndef NOMINMAX 26 | #define NOMINMAX 27 | #endif 28 | #include 29 | 30 | static std::string utf16_to_utf8(const std::wstring &wstr) { 31 | int required_size = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr); 32 | std::string buffer; 33 | buffer.resize(required_size - 1); 34 | WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), static_cast(wstr.size()), &buffer[0], required_size, nullptr, nullptr); 35 | return buffer; 36 | } 37 | 38 | static std::wstring utf16_from_utf8(const std::string &str) { 39 | int required_size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0); 40 | std::wstring wbuffer; 41 | wbuffer.resize(required_size - 1); 42 | MultiByteToWideChar(CP_UTF8, 0, str.c_str(), static_cast(str.size()), &wbuffer[0], required_size); 43 | return wbuffer; 44 | } 45 | 46 | #endif -------------------------------------------------------------------------------- /src/core/filehandle.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Thomas Goyne 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | 26 | struct AVIOContext; 27 | 28 | class FileHandle { 29 | AVIOContext *avio; 30 | std::string filename; 31 | int error_source; 32 | int error_cause; 33 | 34 | public: 35 | FileHandle(const char *filename, const char *mode, int error_source, int error_cause); 36 | FileHandle() : avio(nullptr) {} 37 | ~FileHandle(); 38 | 39 | void Seek(int64_t offset, int origin); 40 | int64_t Tell(); 41 | int64_t Size(); 42 | 43 | size_t Read(char *buffer, size_t size); 44 | size_t Write(const char *buffer, size_t size); 45 | int Printf(const char *fmt, ...) 46 | #ifdef __GNUC__ 47 | __attribute__((format(printf, 2, 3))) 48 | #endif 49 | ; 50 | }; 51 | -------------------------------------------------------------------------------- /src/core/zipfile.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Thomas Goyne 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef ZIPFILE_H 22 | #define ZIPFILE_H 23 | 24 | #include "filehandle.h" 25 | 26 | #include 27 | #include 28 | 29 | class ZipFile { 30 | FileHandle file; 31 | std::vector buffer; 32 | std::vector index_buffer; 33 | bool is_file; 34 | z_stream z; 35 | enum { 36 | Initial, 37 | Inflate, 38 | Deflate 39 | } state; 40 | 41 | public: 42 | ZipFile(const char *filename, const char *mode); 43 | ZipFile(const uint8_t *in_buffer, const size_t size); 44 | ZipFile(); 45 | ~ZipFile(); 46 | 47 | void Read(void *buffer, size_t size); 48 | int Write(const void *buffer, size_t size); 49 | void Finish(); 50 | uint8_t *GetBuffer(size_t *size); 51 | 52 | template 53 | T Read() { 54 | T ret = T(); 55 | Read(&ret, sizeof(T)); 56 | return ret; 57 | } 58 | 59 | template 60 | void Write(T const& value) { 61 | Write(&value, sizeof value); 62 | } 63 | }; 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/core/videoutils.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007-2015 The FFmpegSource Project 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | extern "C" { 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | } 28 | // must be included after ffmpeg headers 29 | #include "ffmscompat.h" 30 | 31 | #include 32 | 33 | #include "ffms.h" 34 | 35 | enum BCSType { 36 | cGRAY, 37 | cYUV, 38 | cRGB, 39 | cUNUSABLE 40 | }; 41 | 42 | // swscale and pp-related functions 43 | SwsContext *GetSwsContext(int SrcW, int SrcH, AVPixelFormat SrcFormat, int SrcColorSpace, int SrcColorRange, int DstW, int DstH, AVPixelFormat DstFormat, int DstColorSpace, int DstColorRange, int64_t Flags); 44 | BCSType GuessCSType(AVPixelFormat p); 45 | 46 | // timebase-related functions 47 | void CorrectRationalFramerate(int *Num, int *Den); 48 | void CorrectTimebase(FFMS_VideoProperties *VP, FFMS_TrackTimeBase *TTimebase); 49 | 50 | // our implementation of avcodec_find_best_pix_fmt() 51 | AVPixelFormat FindBestPixelFormat(const std::vector &Dsts, AVPixelFormat Src); 52 | 53 | // handling of alt-refs in VP8 and VP9 54 | void ParseVP8(const uint8_t Buf, bool *Invisible, int *PictType); 55 | void ParseVP9(const uint8_t Buf, bool *Invisible, int *PictType); 56 | -------------------------------------------------------------------------------- /test/tools/gendata: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | my $filename = shift; 7 | my $arrayname = shift; 8 | my $track = shift; 9 | my @timestamps; 10 | my @hashes; 11 | my @durations; 12 | my @widths; 13 | my @heights; 14 | my @pixfmts; 15 | my @keyframes; 16 | my $ticks = 1; 17 | my $frames = 0; 18 | 19 | if (!defined($filename) || !defined($arrayname) || !defined($track)) { 20 | print("Usage: gendata [filename] [arrayname] [track] > output.cpp\n"); 21 | exit(0); 22 | } 23 | 24 | my $version = "Unknown"; 25 | 26 | for (`ffmpeg -version 2>&1`) { 27 | chomp; 28 | s/^(ffmpeg version .+) Copyright.+/$1/; 29 | $version = $_; 30 | last; 31 | } 32 | 33 | print("// Autogenerated from $filename by gendata with $version\n\n"); 34 | print("#ifndef _FFMS_TEST_".uc($arrayname)."_H\n"); 35 | print("#define _FFMS_TEST_".uc($arrayname)."_H\n\n"); 36 | print("#include \"data.h\"\n\n"); 37 | 38 | for (`ffmpeg -i $filename -map $track:0 -f framehash - 2>/dev/null`) { 39 | if (/^#/) { 40 | if (!/^#tb/) { 41 | next; 42 | } 43 | chomp; 44 | s/^#tb \d: //; 45 | my @tb = split('/'); 46 | $ticks = int($tb[0]); 47 | next; 48 | } 49 | chomp; 50 | s/\s//g; 51 | my @fields = split(','); 52 | push(@hashes, $fields[5]); 53 | $frames++; 54 | } 55 | 56 | for (`ffprobe -show_frames -select_streams $track $filename 2>/dev/null`) { 57 | chomp; 58 | 59 | if (/^width/) { 60 | my @w = split('='); 61 | push(@widths, int($w[1])); 62 | } elsif (/^height/) { 63 | my @h = split('='); 64 | push(@heights, int($h[1])); 65 | } elsif (/^pix_fmt/) { 66 | my @p = split('='); 67 | push(@pixfmts, $p[1]); 68 | } elsif (/^key_frame/) { 69 | my @k = split('='); 70 | push(@keyframes, (int($k[1]) == 1 ? "true" : "false")); 71 | } elsif (/^pkt_pts=/) { 72 | my @t = split('='); 73 | push(@timestamps, int($t[1])); 74 | } elsif (/^pkt_duration=/) { 75 | my @d = split('='); 76 | push(@durations, int($d[1])); 77 | } else { 78 | next; 79 | } 80 | } 81 | 82 | print("const TestFrameData $arrayname\[$frames] = {\n"); 83 | 84 | for (my $i = 0; $i < $frames; $i++) { 85 | print(" { $timestamps[$i], $durations[$i], $widths[$i], $heights[$i], \"$pixfmts[$i]\", $keyframes[$i], {"); 86 | 87 | my $hash = $hashes[$i]; 88 | $hash =~ s/(..)/ 0x\U$1,/g; 89 | $hash =~ s/,$//; 90 | print($hash); 91 | 92 | print(" } }"); 93 | 94 | if ($i != $frames - 1) { 95 | print(","); 96 | } 97 | 98 | print("\n"); 99 | } 100 | 101 | print("};\n\n"); 102 | print("#endif // _FFMS_TEST_".uc($arrayname)."_H\n"); 103 | -------------------------------------------------------------------------------- /test/display_matrix.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "tests.h" 11 | 12 | namespace { 13 | 14 | class DisplayMatrixTest : public ::testing::Test { 15 | protected: 16 | virtual void SetUp(); 17 | virtual void TearDown(); 18 | bool DoIndexing(std::string); 19 | 20 | FFMS_Indexer* indexer; 21 | FFMS_Index* index; 22 | int video_track_idx; 23 | FFMS_VideoSource* video_source; 24 | const FFMS_VideoProperties* VP; 25 | 26 | FFMS_ErrorInfo E; 27 | char ErrorMsg[1024]; 28 | 29 | std::string SamplesDir; 30 | }; 31 | 32 | void DisplayMatrixTest::SetUp() { 33 | indexer = nullptr; 34 | index = nullptr; 35 | video_track_idx = -1; 36 | video_source = nullptr; 37 | VP = nullptr; 38 | E.Buffer = ErrorMsg; 39 | E.BufferSize = sizeof(ErrorMsg); 40 | SamplesDir = STRINGIFY(SAMPLES_DIR); 41 | 42 | FFMS_Init(0,0); 43 | } 44 | 45 | void DisplayMatrixTest::TearDown() { 46 | FFMS_DestroyIndex(index); 47 | FFMS_DestroyVideoSource(video_source); 48 | FFMS_Deinit(); 49 | } 50 | 51 | bool DisplayMatrixTest::DoIndexing(std::string file_name) { 52 | indexer = FFMS_CreateIndexer(file_name.c_str(), &E); 53 | NULL_CHECK(indexer); 54 | FFMS_TrackTypeIndexSettings(indexer, FFMS_TYPE_VIDEO, 1, 0); 55 | 56 | index = FFMS_DoIndexing2(indexer, 0, &E); 57 | NULL_CHECK(index); 58 | 59 | video_track_idx = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_VIDEO, &E); 60 | EXPECT_GE(0, video_track_idx); 61 | 62 | video_source = FFMS_CreateVideoSource(file_name.c_str(), video_track_idx, index, 1, FFMS_SEEK_NORMAL, &E); 63 | NULL_CHECK(video_source); 64 | 65 | VP = FFMS_GetVideoProperties(video_source); // Can't fail 66 | 67 | return true; 68 | } 69 | 70 | TEST_F(DisplayMatrixTest, HFlip270) { 71 | std::string FilePath = SamplesDir + "/qrvideo_hflip_90.mov"; 72 | 73 | ASSERT_TRUE(DoIndexing(FilePath)); 74 | 75 | ASSERT_EQ(VP->Flip, 1); 76 | ASSERT_EQ(VP->Rotation, 90); 77 | } 78 | 79 | TEST_F(DisplayMatrixTest, HFlip90) { 80 | std::string FilePath = SamplesDir + "/qrvideo_hflip_270.mov"; 81 | 82 | ASSERT_TRUE(DoIndexing(FilePath)); 83 | 84 | ASSERT_EQ(VP->Flip, 1); 85 | ASSERT_EQ(VP->Rotation, 270); 86 | } 87 | 88 | TEST_F(DisplayMatrixTest, VFlip180) { 89 | std::string FilePath = SamplesDir + "/qrvideo_vflip.mov"; 90 | 91 | ASSERT_TRUE(DoIndexing(FilePath)); 92 | 93 | ASSERT_EQ(VP->Flip, 1); 94 | ASSERT_EQ(VP->Rotation, 180); 95 | } 96 | 97 | } //namespace 98 | 99 | int main(int argc, char **argv) { 100 | ::testing::InitGoogleTest(&argc, argv); 101 | return RUN_ALL_TESTS(); 102 | } 103 | -------------------------------------------------------------------------------- /src/vapoursynth/vapoursource.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2017 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef FFVAPOURSOURCES_H 22 | #define FFVAPOURSOURCES_H 23 | 24 | extern "C" { 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | } 32 | 33 | #include "VapourSynth.h" 34 | #include "ffms.h" 35 | #include "ffmscompat.h" 36 | 37 | struct VSVideoSource { 38 | private: 39 | VSVideoInfo VI[2]; 40 | FFMS_VideoSource *V; 41 | int64_t FPSNum; 42 | int64_t FPSDen; 43 | int SARNum; 44 | int SARDen; 45 | bool OutputAlpha; 46 | 47 | void InitOutputFormat(int ResizeToWidth, int ResizeToHeight, 48 | const char *ResizerName, int ConvertToFormat, const VSAPI *vsapi, VSCore *core); 49 | static void OutputFrame(const FFMS_Frame *Frame, VSFrameRef *Dst, const VSAPI *vsapi); 50 | static void OutputAlphaFrame(const FFMS_Frame *Frame, int Plane, VSFrameRef *Dst, const VSAPI *vsapi); 51 | public: 52 | VSVideoSource(const char *SourceFile, int Track, FFMS_Index *Index, 53 | int AFPSNum, int AFPSDen, int Threads, int SeekMode, int RFFMode, 54 | int ResizeToWidth, int ResizeToHeight, const char *ResizerName, 55 | int Format, bool OutputAlpha, const VSAPI *vsapi, VSCore *core); 56 | ~VSVideoSource(); 57 | 58 | static void VS_CC Init(VSMap *in, VSMap *out, void **instanceData, VSNode *node, VSCore *core, const VSAPI *vsapi); 59 | static const VSFrameRef *VS_CC GetFrame(int n, int activationReason, void **instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi); 60 | static void VS_CC Free(void *instanceData, VSCore *core, const VSAPI *vsapi); 61 | }; 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | AUTOMAKE_OPTIONS = foreign 3 | 4 | pkgconfigdir = $(libdir)/pkgconfig 5 | pkgconfig_DATA = ffms2.pc 6 | 7 | dist_doc_DATA = doc/ffms2-api.md doc/ffms2-changelog.md 8 | 9 | AM_CPPFLAGS = \ 10 | -I. \ 11 | -I$(top_srcdir)/include \ 12 | -I$(top_srcdir)/src/config \ 13 | -D_FILE_OFFSET_BITS=64 \ 14 | -DFFMS_EXPORTS \ 15 | -D__STDC_CONSTANT_MACROS \ 16 | @FFMPEG_CFLAGS@ \ 17 | @ZLIB_CPPFLAGS@ \ 18 | -include config.h 19 | AM_CXXFLAGS = -std=c++11 -fvisibility=hidden 20 | 21 | lib_LTLIBRARIES = src/core/libffms2.la 22 | src_core_libffms2_la_LDFLAGS = @src_core_libffms2_la_LDFLAGS@ 23 | src_core_libffms2_la_LIBADD = @FFMPEG_LIBS@ @ZLIB_LDFLAGS@ -lz @LTUNDEF@ 24 | src_core_libffms2_la_SOURCES = \ 25 | src/core/audiosource.cpp \ 26 | src/core/audiosource.h \ 27 | src/core/ffms.cpp \ 28 | src/core/filehandle.cpp \ 29 | src/core/filehandle.h \ 30 | src/core/indexing.cpp \ 31 | src/core/indexing.h \ 32 | src/core/track.cpp \ 33 | src/core/track.h \ 34 | src/core/utils.cpp \ 35 | src/core/utils.h \ 36 | src/core/videosource.cpp \ 37 | src/core/videosource.h \ 38 | src/core/videoutils.cpp \ 39 | src/core/videoutils.h \ 40 | src/core/zipfile.cpp \ 41 | src/core/zipfile.h \ 42 | src/vapoursynth/VapourSynth.h \ 43 | src/vapoursynth/vapoursource.cpp \ 44 | src/vapoursynth/vapoursource.h \ 45 | src/vapoursynth/vapoursynth.cpp \ 46 | src/vapoursynth/vapoursource4.cpp \ 47 | src/vapoursynth/vapoursource4.h \ 48 | src/vapoursynth/vapoursynth4.cpp 49 | 50 | if AVISYNTH 51 | src_core_libffms2_la_SOURCES += \ 52 | src/avisynth/avssources.cpp \ 53 | src/avisynth/avssources.h \ 54 | src/avisynth/avisynth.cpp 55 | endif 56 | 57 | include_HEADERS = $(top_srcdir)/include/ffms.h $(top_srcdir)/include/ffmscompat.h 58 | 59 | bin_PROGRAMS = src/index/ffmsindex 60 | src_index_ffmsindex_SOURCES = src/index/ffmsindex.cpp 61 | src_index_ffmsindex_LDADD = src/core/libffms2.la 62 | 63 | .PHONY: test test-build test-clean test-sync test-run 64 | clean-local: test-clean 65 | 66 | SAMPLES_DIR = $(abs_top_builddir)/test/samples 67 | SAMPLES_URL = https://storage.googleapis.com/ffms2tests 68 | 69 | test: test-setup test-build test-run 70 | 71 | test-setup: 72 | @$(MKDIR_P) $(abs_top_builddir)/test 73 | @$(MKDIR_P) $(SAMPLES_DIR) 74 | @if [ ! -e "$(abs_top_builddir)/test/Makefile" ]; then \ 75 | $(LN_S) $(abs_top_srcdir)/test/Makefile $(abs_top_builddir)/test/Makefile; \ 76 | fi 77 | 78 | test-build: test-setup src/core/libffms2.la 79 | @if [ ! -d "$(abs_top_srcdir)/test/googletest" ]; then \ 80 | echo "googletest submodule not initalized."; \ 81 | fi 82 | @$(MAKE) -C test USER_DIR=$(abs_top_srcdir) SAMPLES_DIR=$(SAMPLES_DIR) CXX=$(CXX) AR=$(AR) 83 | 84 | test-sync: test-setup 85 | @$(MAKE) -C test sync USER_DIR=$(abs_top_srcdir) SAMPLES_DIR=$(SAMPLES_DIR) SAMPLES_URL=$(SAMPLES_URL) CXX=$(CXX) AR=$(AR) 86 | 87 | test-run: test-build 88 | @$(MAKE) -C test run USER_DIR=$(abs_top_srcdir) SAMPLES_DIR=$(SAMPLES_DIR) CXX=$(CXX) AR=$(AR) -k 89 | 90 | test-clean: 91 | @$(MAKE) -C test clean USER_DIR=$(abs_top_srcdir) CXX=$(CXX) AR=$(AR) 92 | -------------------------------------------------------------------------------- /src/vapoursynth/vapoursource4.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2017 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef FFVAPOURSOURCES_H 22 | #define FFVAPOURSOURCES_H 23 | 24 | extern "C" { 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | } 32 | 33 | #include "VapourSynth4.h" 34 | #include "ffms.h" 35 | #include "ffmscompat.h" 36 | 37 | struct VSVideoSource4 { 38 | private: 39 | VSVideoInfo VI[2]; 40 | FFMS_VideoSource *V; 41 | int64_t FPSNum; 42 | int64_t FPSDen; 43 | int SARNum; 44 | int SARDen; 45 | bool OutputAlpha; 46 | int LastFrame = -1; 47 | int CacheThreshold = 0; 48 | 49 | void InitOutputFormat(int ResizeToWidth, int ResizeToHeight, 50 | const char *ResizerName, int ConvertToFormat, const VSAPI *vsapi, VSCore *core); 51 | static void OutputFrame(const FFMS_Frame *Frame, VSFrame *Dst, const VSAPI *vsapi); 52 | static void OutputAlphaFrame(const FFMS_Frame *Frame, int Plane, VSFrame *Dst, const VSAPI *vsapi); 53 | public: 54 | VSVideoSource4(const char *SourceFile, int Track, FFMS_Index *Index, 55 | int AFPSNum, int AFPSDen, int Threads, int SeekMode, int RFFMode, 56 | int ResizeToWidth, int ResizeToHeight, const char *ResizerName, 57 | int Format, bool OutputAlpha, const VSAPI *vsapi, VSCore *core); 58 | ~VSVideoSource4(); 59 | 60 | const VSVideoInfo *GetVideoInfo() const; 61 | void SetCacheThreshold(int threshold); 62 | 63 | static void VS_CC Init(VSMap *in, VSMap *out, void **instanceData, VSNode *node, VSCore *core, const VSAPI *vsapi); 64 | const VSFrame *VS_CC GetVSFrame(int n, VSCore *core, const VSAPI *vsapi); 65 | static const VSFrame *VS_CC GetFrame(int n, int activationReason, void *instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi); 66 | static void VS_CC Free(void *instanceData, VSCore *core, const VSAPI *vsapi); 67 | }; 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://api.travis-ci.org/FFMS/ffms2.svg?branch=master)](https://travis-ci.org/FFMS/ffms2) 2 | 3 | **FFmpegSource** (usually known as **FFMS** or **FFMS2**) is a cross-platform wrapper library around [FFmpeg](http://ffmpeg.org). It gives you an easy, convenient way to say "open and decompress this media file for me, I don't care how you do it" and get frame- and sample-accurate access (usually), without having to bother with the sometimes less than straightforward and less than perfectly documented FFmpeg API. 4 | 5 | The library is written in C++, but the public API is pure C, so if you can link to a C library, you can use FFMS2. The source is available under the MIT license, but the license of the binaries depends on how FFmpeg was compiled. There are optional components that require a GPL FFmpeg, and if those are compiled in FFMS2 itself becomes covered by the GPL as well. The official Windows builds are GPLv3 for this reason. 6 | 7 | For more information on using the library, see the [API documentation](doc/ffms2-api.md) and the [changelog](doc/ffms2-changelog.md). 8 | 9 | ### Avisynth and VapourSynth plugin 10 | For the end user, the most visible use of FFMS is the implementation of both an [Avisynth](http://avisynth.nl) and a [VapourSynth](http://www.vapoursynth.com) source plugin that uses the FFMS library to open media files. This plugin is a part of the FFMS2 project and is available for download here; for documentation see the [Avisynth user guide](doc/ffms2-avisynth.md). 11 | 12 | ### Features 13 | In addition to being able to open almost any common audio or video format, the Avisynth plugin has a number of more or less unique properties that other Avisynth source filters lack: 14 | 15 | * It is the only source filter that has support for Unicode filenames that are not representable in the system codepage. 16 | * It is the only source filter that has proper variable framerate (VFR) support. 17 | * It is the only general-purpose (i.e. not restricted to one or a few formats) source filter that will work reliably when running Avisynth under Wine. 18 | * It is the only general-purpose source filter that does not rely on external decoders. 19 | * It is (probably) the only source filter that supports mid-stream video resolution switches. 20 | 21 | ### Versions and variants 22 | If you're confused by all the different variants, here's a small explanation: 23 | 24 | * Vanilla (no suffix): standard 32-bit version. If you don't know what you want, you want this. 25 | * -x64: 64-bit version; mostly for use with 64-bit Avisynth. 26 | * -avs-cplugin: Variant of the Avisynth plugin written in C. Primary purpose is to get access to the new colorspaces available in Avisynth 2.6. 27 | * SDK: software developer's kit, for people who want to develop Windows applications that use FFMS2, using Microsoft Visual Studio 2008 or later. 28 | 29 | Packages marked rNUMBER are testing builds made in-between releases. Download them if you need some bleeding-edge feature or just want to test out the upcoming version. Do note that they may be less stable than the official release versions. 30 | 31 | ### Why is it called FFmpegSource, that makes no sense at all!?! 32 | FFMS originated as an Avisynth file reader plugin, and those are traditionally called FooSource, where Foo usually is the method used to open the file. For historical reasons the entire project is still called FFmpegSource, although these days the name is pretty misleading and makes people think it has something to do with FFmpeg's source code or somesuch. To avoid confusion, it's probably better to refer to the library as FFMS (2, since version 1 was only an Avisynth plugin...) and keep the FFmpegSource name for the Avisynth plugin. 33 | -------------------------------------------------------------------------------- /src/avisynth/avssources.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007-2017 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef FFAVSSOURCES_H 22 | #define FFAVSSOURCES_H 23 | 24 | #include 25 | #ifdef _WIN32 26 | #include 27 | #endif 28 | #include 29 | #include "ffms.h" 30 | 31 | struct ErrorInfo : FFMS_ErrorInfo { 32 | char ErrorBuffer[1024]; 33 | ErrorInfo() { 34 | Buffer = ErrorBuffer; 35 | BufferSize = sizeof(ErrorBuffer); 36 | } 37 | }; 38 | 39 | class AvisynthVideoSource : public IClip { 40 | struct FrameFields { 41 | int Top; 42 | int Bottom; 43 | }; 44 | 45 | VideoInfo VI; 46 | bool HighBitDepth; 47 | FFMS_VideoSource *V; 48 | int FPSNum; 49 | int FPSDen; 50 | int RFFMode; 51 | std::vector FieldList; 52 | const char *VarPrefix; 53 | bool has_at_least_v8; 54 | 55 | void InitOutputFormat(int ResizeToWidth, int ResizeToHeight, 56 | const char *ResizerName, const char *ConvertToFormatName, IScriptEnvironment *Env); 57 | void OutputFrame(const FFMS_Frame *Frame, PVideoFrame &Dst, IScriptEnvironment *Env); 58 | void OutputField(const FFMS_Frame *Frame, PVideoFrame &Dst, int Field, IScriptEnvironment *Env); 59 | public: 60 | AvisynthVideoSource(const char *SourceFile, int Track, FFMS_Index *Index, 61 | int FPSNum, int FPSDen, int Threads, int SeekMode, int RFFMode, 62 | int ResizeToWidth, int ResizeToHeight, const char *ResizerName, 63 | const char *ConvertToFormatName, const char *VarPrefix, IScriptEnvironment* Env); 64 | ~AvisynthVideoSource(); 65 | bool __stdcall GetParity(int n); 66 | int __stdcall SetCacheHints(int cachehints, int frame_range) { return 0; } 67 | const VideoInfo& __stdcall GetVideoInfo() { return VI; } 68 | void __stdcall GetAudio(void* Buf, int64_t Start, int64_t Count, IScriptEnvironment *Env) {} 69 | PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment *Env); 70 | }; 71 | 72 | class AvisynthAudioSource : public IClip { 73 | VideoInfo VI; 74 | FFMS_AudioSource *A; 75 | public: 76 | AvisynthAudioSource(const char *SourceFile, int Track, FFMS_Index *Index, 77 | int AdjustDelay, int FillGaps, double DrcScale, const char *VarPrefix, IScriptEnvironment* Env); 78 | ~AvisynthAudioSource(); 79 | bool __stdcall GetParity(int n) { return false; } 80 | int __stdcall SetCacheHints(int cachehints, int frame_range) { return 0; } 81 | const VideoInfo& __stdcall GetVideoInfo() { return VI; } 82 | void __stdcall GetAudio(void* Buf, int64_t Start, int64_t Count, IScriptEnvironment *Env); 83 | PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment *Env) { return nullptr; }; 84 | }; 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /etc/FFMS2.avsi: -------------------------------------------------------------------------------- 1 | # FFmpegSource 2.22 helper functions 2 | # Created by TheFluff 3 | 4 | function FFFormatTime(int ms) { 5 | 6 | s = ms / 1000 7 | ms = ms % 1000 8 | m = s / 60 9 | s = s % 60 10 | h = m / 60 11 | m = m % 60 12 | 13 | return ms < 0 ? "" : string(h) + ":" + string(m,"%02.0f") + ":" + string(s,"%02.0f") + "." + string(ms,"%03.0f") 14 | } 15 | 16 | function FFColorSpace(int i) { 17 | i=(i<0||i>10) ? 2 : i 18 | return Select(i,"RGB","BT709 (ITU-R Rec.709)","Unspecified","Unspecified","FCC","BT470BG (ITU-R Rec.601)","SMPTE 170M (ITU-R Rec.601)","SMPTE 240M","YCoCg","BT2020 NCL","BT2020 NC") 19 | } 20 | 21 | function FFColorRange(int i) { 22 | i=(i<0||i>2) ? 0 : i 23 | return Select(i,"Unknown/Unspecified","Limited range","Full range") 24 | } 25 | 26 | function FFCropping(int l, int t, int r, int b) { 27 | return "Left="+String(l)+" Top="+String(t)+" Right="+String(r)+" Bottom="+String(b) 28 | } 29 | 30 | function FFSampAR(int num, int den) { 31 | return num > 0 ? String(num)+":"+String(den)+((num<=0 || den<=0)?"":string(Float(num)/den," (%.3f)")) : "Unknown/Unspecified" 32 | } 33 | 34 | function FFPictType(int ch) { 35 | s=chr(ch) 36 | s = s + ( 37 | \ ch==73?" (Intra)":ch==80?" (Predicted)":ch==66?" (Bi-dir predicted)":ch==83?" (S(GMC)-VOP MPEG4)" 38 | \ : ch==105?" (Switching Intra)":ch==112?" (Switching Predicted)":ch==98?" (FF_BI_TYPE)" 39 | \ : " (Unknown)") 40 | return s 41 | } 42 | 43 | function FFInfo(clip c, bool "framenum", bool "frametype", bool "cfrtime", bool "vfrtime", string "varprefix", 44 | \ bool "colorspace",bool "colorrange",bool "cropping",bool "sar",bool "version",bool "showprefix") { 45 | 46 | framenum = default(framenum,true) 47 | frametype = default(frametype,true) 48 | cfrtime = default(cfrtime,true) 49 | vfrtime = default(vfrtime,true) 50 | varprefix = default(varprefix, FFVAR_PREFIX) 51 | colorSpace = default(colorspace,true) 52 | colorrange = default(colorrange,true) 53 | cropping = default(cropping,true) 54 | sar = default(sar,true) 55 | version = default(version,true) 56 | showprefix = default(showprefix,false) 57 | 58 | c.frameevaluate(""" 59 | fftempstring = "" 60 | varprefix = """" + varprefix + """" 61 | """) 62 | 63 | version ? frameevaluate("""fftempstring = fftempstring + "Version: " + FFGetVersion + "\n" """, after_frame=true) : nop() 64 | framenum ? frameevaluate("""fftempstring = fftempstring + "Frame Number: " + string(current_frame) + " of " + string(framecount()) + "\n" """, after_frame=true) : nop() 65 | frametype ? frameevaluate("""fftempstring = fftempstring + "Picture Type: " + FFPictType(eval(varprefix + "FFPICT_TYPE")) + "\n" """, after_frame=true) : nop() 66 | cfrtime ? frameevaluate("""fftempstring = fftempstring + "CFR Time: " + FFFormatTime(round((current_frame * 1000) / framerate())) + "\n" """, after_frame=true) : nop() 67 | vfrtime ? frameevaluate("""fftempstring = fftempstring + "VFR Time: " + FFFormatTime(eval(varprefix + "FFVFR_TIME")) + "\n" """, after_frame=true) : nop() 68 | colorspace ? frameevaluate("""fftempstring = fftempstring + "ColorSpace: " + FFColorSpace(eval(varprefix + "FFCOLOR_SPACE")) + "\n" """, after_frame=true) : nop() 69 | colorrange ? frameevaluate("""fftempstring = fftempstring + "Color Range: " + FFColorRange(eval(varprefix + "FFCOLOR_RANGE")) + "\n" """, after_frame=true) : nop() 70 | cropping ? frameevaluate("""fftempstring = fftempstring + "Cropping: " + FFCropping(eval(varprefix + "FFCROP_LEFT"),eval(varprefix + "FFCROP_TOP"),eval(varprefix + "FFCROP_RIGHT"),eval(varprefix + "FFCROP_BOTTOM")) + "\n" """, after_frame=true) : nop() 71 | sar ? frameevaluate("""fftempstring = fftempstring + "SAR: " + FFSampAR(eval(varprefix + "FFSAR_NUM"),eval(varprefix + "FFSAR_DEN")) + "\n" """, after_frame=true) : nop() 72 | showprefix ? frameevaluate("""fftempstring = fftempstring + "Prefix: '" + varprefix + "'\n" """, after_frame=true) : nop() 73 | 74 | return scriptclip("subtitle(fftempstring, lsp = 1)", after_frame=true) 75 | } -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | # Where to find user code. 2 | USER_DIR = .. 3 | 4 | # Points to the root of Google Test, relative to where this file is. 5 | # Remember to tweak this if you move this file. 6 | GTEST_DIR = $(USER_DIR)/test/googletest/googletest 7 | 8 | # Where to find sample files 9 | SAMPLES_DIR = 10 | 11 | # URL to sync samples from 12 | SAMPLES_URL = 13 | 14 | # Flags passed to the preprocessor. 15 | # Set Google Test's header directory as a system directory, such that 16 | # the compiler doesn't generate warnings in Google Test headers. 17 | CPPFLAGS += -isystem $(GTEST_DIR)/include \ 18 | -I$(USER_DIR)/include \ 19 | -D_FILE_OFFSET_BITS=64 \ 20 | -DFFMS_EXPORTS \ 21 | -D__STDC_CONSTANT_MACROS \ 22 | -DSAMPLES_DIR=$(SAMPLES_DIR) 23 | 24 | # Flags passed to the C++ compiler. 25 | CXXFLAGS += -g -Wall -Wextra -pthread -std=c++11 -fvisibility=hidden 26 | 27 | # All tests produced by this Makefile. Remember to add new tests you 28 | # created to the list. 29 | TESTS = indexer \ 30 | hdr \ 31 | display_matrix 32 | 33 | RUNTESTS = $(subst $() $(),-run$() $(),$(TESTS))-run 34 | 35 | # All the sample files we need to sync 36 | SAMPLES = test.mp4 \ 37 | hdr10tags-both.mkv \ 38 | hdr10tags-container.mkv \ 39 | hdr10tags-stream.mp4 \ 40 | qrvideo_hflip_90.mov \ 41 | qrvideo_hflip_270.mov \ 42 | qrvideo_vflip.mov 43 | 44 | # All Google Test headers. Usually you shouldn't change this 45 | # definition. 46 | GTEST_HEADERS = $(GTEST_DIR)/include/gtest/*.h $(GTEST_DIR)/include/gtest/internal/*.h 47 | 48 | # House-keeping build targets. 49 | 50 | all: $(TESTS) 51 | 52 | run: all $(RUNTESTS) 53 | 54 | sync: 55 | @for i in $(SAMPLES); do \ 56 | if [ ! -f "$(SAMPLES_DIR)/$$i" ]; then \ 57 | wget -O $(SAMPLES_DIR)/$$i $(SAMPLES_URL)/$$i; \ 58 | fi \ 59 | done 60 | 61 | clean: 62 | rm -f $(TESTS) gtest.a gtest_main.a *.o 63 | rm -rf .libs 64 | 65 | # Builds gtest.a and gtest_main.a. 66 | 67 | # Usually you shouldn't tweak such internal variables, indicated by a 68 | # trailing _. 69 | GTEST_SRCS_ = $(GTEST_DIR)/src/*.cc $(GTEST_DIR)/src/*.h $(GTEST_HEADERS) 70 | 71 | # For simplicity and to avoid depending on Google Test's 72 | # implementation details, the dependencies specified below are 73 | # conservative and not optimized. This is fine as Google Test 74 | # compiles fast and for ordinary users its source rarely changes. 75 | gtest-all.o: $(GTEST_SRCS_) 76 | $(CXX) $(CPPFLAGS) -I$(GTEST_DIR) $(CXXFLAGS) -c $(GTEST_DIR)/src/gtest-all.cc 77 | 78 | gtest_main.o: $(GTEST_SRCS_) 79 | $(CXX) $(CPPFLAGS) -I$(GTEST_DIR) $(CXXFLAGS) -c $(GTEST_DIR)/src/gtest_main.cc 80 | 81 | gtest.a: gtest-all.o 82 | $(AR) $(ARFLAGS) $@ $^ 83 | 84 | gtest_main.a: gtest-all.o gtest_main.o 85 | $(AR) $(ARFLAGS) $@ $^ 86 | 87 | tests.o: $(USER_DIR)/test/tests.cpp $(USER_DIR)/include/ffms.h $(GTEST_HEADERS) 88 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/test/tests.cpp 89 | 90 | hdr.o: $(USER_DIR)/test/hdr.cpp $(USER_DIR)/include/ffms.h $(GTEST_HEADERS) 91 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/test/hdr.cpp 92 | 93 | display_matrix.o: $(USER_DIR)/test/display_matrix.cpp $(USER_DIR)/include/ffms.h $(GTEST_HEADERS) 94 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/test/display_matrix.cpp 95 | 96 | indexer.o: $(USER_DIR)/test/indexer.cpp $(USER_DIR)/include/ffms.h $(GTEST_HEADERS) 97 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/test/indexer.cpp 98 | 99 | indexer: indexer.o tests.o gtest_main.a ../src/core/libffms2.la 100 | ../libtool --tag=CXX --mode=link $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o indexer indexer.o tests.o gtest_main.a -lavutil ../src/core/libffms2.la 101 | 102 | indexer-run: 103 | @./indexer 104 | 105 | hdr: hdr.o gtest_main.a ../src/core/libffms2.la 106 | ../libtool --tag=CXX --mode=link $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o hdr hdr.o gtest_main.a -lavutil ../src/core/libffms2.la 107 | 108 | hdr-run: 109 | @./hdr 110 | 111 | display_matrix: display_matrix.o gtest_main.a ../src/core/libffms2.la 112 | ../libtool --tag=CXX --mode=link $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o display_matrix display_matrix.o gtest_main.a -lavutil ../src/core/libffms2.la 113 | 114 | display_matrix-run: 115 | @./display_matrix 116 | -------------------------------------------------------------------------------- /test/indexer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "data/test.mp4.cpp" 11 | #include "tests.h" 12 | 13 | 14 | namespace { 15 | 16 | const TestDataMap TestFiles[] = { 17 | TEST_ENTRY("test.mp4", testmp4data) 18 | }; 19 | 20 | class IndexerTest : public ::testing::TestWithParam { 21 | protected: 22 | virtual void SetUp(); 23 | virtual void TearDown(); 24 | bool DoIndexing(std::string); 25 | 26 | FFMS_Indexer* indexer; 27 | FFMS_Index* index; 28 | int video_track_idx; 29 | FFMS_VideoSource* video_source; 30 | const FFMS_VideoProperties* VP; 31 | 32 | FFMS_ErrorInfo E; 33 | char ErrorMsg[1024]; 34 | 35 | std::string SamplesDir; 36 | }; 37 | 38 | void IndexerTest::SetUp() { 39 | indexer = nullptr; 40 | index = nullptr; 41 | video_track_idx = -1; 42 | video_source = nullptr; 43 | VP = nullptr; 44 | E.Buffer = ErrorMsg; 45 | E.BufferSize = sizeof(ErrorMsg); 46 | SamplesDir = STRINGIFY(SAMPLES_DIR); 47 | 48 | FFMS_Init(0,0); 49 | } 50 | 51 | void IndexerTest::TearDown() { 52 | FFMS_DestroyIndex(index); 53 | FFMS_DestroyVideoSource(video_source); 54 | FFMS_Deinit(); 55 | } 56 | 57 | bool IndexerTest::DoIndexing(std::string file_name) { 58 | indexer = FFMS_CreateIndexer(file_name.c_str(), &E); 59 | NULL_CHECK(indexer); 60 | FFMS_TrackTypeIndexSettings(indexer, FFMS_TYPE_VIDEO, 1, 0); 61 | 62 | index = FFMS_DoIndexing2(indexer, 0, &E); 63 | NULL_CHECK(index); 64 | 65 | video_track_idx = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_VIDEO, &E); 66 | EXPECT_GE(0, video_track_idx); 67 | 68 | video_source = FFMS_CreateVideoSource(file_name.c_str(), video_track_idx, index, 1, FFMS_SEEK_NORMAL, &E); 69 | NULL_CHECK(video_source); 70 | 71 | VP = FFMS_GetVideoProperties(video_source); // Can't fail 72 | 73 | return true; 74 | } 75 | 76 | TEST_P(IndexerTest, ValidateFrameCount) { 77 | TestDataMap P = GetParam(); 78 | std::string FilePath = SamplesDir + "/" + P.Filename; 79 | 80 | ASSERT_TRUE(DoIndexing(FilePath)); 81 | 82 | ASSERT_EQ(P.TestDataLen, VP->NumFrames); 83 | } 84 | 85 | TEST_P(IndexerTest, ReverseAccessingFrame) { 86 | TestDataMap P = GetParam(); 87 | std::string FilePath = SamplesDir + "/" + P.Filename; 88 | 89 | ASSERT_TRUE(DoIndexing(FilePath)); 90 | for (int i = VP->NumFrames - 1; i > 0; i--) { 91 | std::stringstream ss; 92 | ss << "Testing Frame: " << i; 93 | SCOPED_TRACE(ss.str()); 94 | 95 | FFMS_Track *track = FFMS_GetTrackFromIndex(index, video_track_idx); 96 | const FFMS_FrameInfo *info = FFMS_GetFrameInfo(track, i); 97 | 98 | const FFMS_Frame* frame = FFMS_GetFrame(video_source, i, &E); 99 | ASSERT_NE(nullptr, frame); 100 | ASSERT_TRUE(CheckFrame(frame, info, &P.TestData[i])); 101 | } 102 | } 103 | 104 | TEST_P(IndexerTest, RandomAccessingFrame) { 105 | TestDataMap P = GetParam(); 106 | std::string FilePath = SamplesDir + "/" + P.Filename; 107 | 108 | ASSERT_TRUE(DoIndexing(FilePath)); 109 | 110 | std::vector FrameNums; 111 | for (int i = 0; i < VP->NumFrames; i++) 112 | FrameNums.push_back(i); 113 | 114 | std::random_device Device; 115 | std::mt19937 Gen(Device()); 116 | 117 | std::shuffle(FrameNums.begin(), FrameNums.end(), Gen); 118 | 119 | for (int i = 0; i < VP->NumFrames; i++) { 120 | int num = FrameNums[i]; 121 | 122 | std::stringstream ss; 123 | ss << "Testing Frame: " << num; 124 | SCOPED_TRACE(ss.str()); 125 | 126 | FFMS_Track *track = FFMS_GetTrackFromIndex(index, video_track_idx); 127 | const FFMS_FrameInfo *info = FFMS_GetFrameInfo(track, num); 128 | 129 | const FFMS_Frame* frame = FFMS_GetFrame(video_source, num, &E); 130 | ASSERT_NE(nullptr, frame); 131 | ASSERT_TRUE(CheckFrame(frame, info, &P.TestData[num])); 132 | } 133 | } 134 | 135 | INSTANTIATE_TEST_CASE_P(ValidateIndexer, IndexerTest, ::testing::ValuesIn(TestFiles)); 136 | 137 | } //namespace 138 | 139 | int main(int argc, char **argv) { 140 | ::testing::InitGoogleTest(&argc, argv); 141 | return RUN_ALL_TESTS(); 142 | } 143 | -------------------------------------------------------------------------------- /src/core/filehandle.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Thomas Goyne 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include "filehandle.h" 22 | 23 | #include "utils.h" 24 | 25 | #include 26 | 27 | extern "C" { 28 | #include 29 | } 30 | 31 | static AVIOContext *ffms_fopen(const char *filename, const char *mode) { 32 | int flags = 0; 33 | if (strchr(mode, 'r')) 34 | flags |= AVIO_FLAG_READ; 35 | if (strchr(mode, 'w')) 36 | flags |= AVIO_FLAG_WRITE; 37 | 38 | AVIOContext *ctx; 39 | int ret = avio_open2(&ctx, filename, flags, nullptr, nullptr); 40 | if (ret < 0) 41 | return nullptr; 42 | return ctx; 43 | } 44 | 45 | FileHandle::FileHandle(const char *filename, const char *mode, int error_source, int error_cause) 46 | : avio(ffms_fopen(filename, mode)) 47 | , filename(filename) 48 | , error_source(error_source) 49 | , error_cause(error_cause) { 50 | if (!avio) 51 | throw FFMS_Exception(error_source, FFMS_ERROR_NO_FILE, 52 | "Failed to open '" + this->filename + "'"); 53 | } 54 | 55 | FileHandle::~FileHandle() { 56 | avio_close(avio); 57 | } 58 | 59 | void FileHandle::Seek(int64_t offset, int origin) { 60 | int64_t ret = avio_seek(avio, offset, origin); 61 | if (ret < 0) 62 | throw FFMS_Exception(error_source, error_cause, 63 | "Failed to seek in '" + filename + "'"); 64 | } 65 | 66 | int64_t FileHandle::Tell() { 67 | int64_t ret = avio_tell(avio); 68 | if (ret < 0) 69 | throw FFMS_Exception(error_source, error_cause, 70 | "Failed to read position in '" + filename + "'"); 71 | return ret; 72 | } 73 | 74 | size_t FileHandle::Read(char *buffer, size_t size) { 75 | int count = avio_read(avio, (unsigned char *)buffer, size); 76 | if (count < 0) 77 | throw FFMS_Exception(error_source, FFMS_ERROR_FILE_READ, 78 | "Failed to read from '" + filename + "'"); 79 | return (size_t)count; 80 | } 81 | 82 | size_t FileHandle::Write(const char *buffer, size_t size) { 83 | avio_write(avio, (const unsigned char *)buffer, size); 84 | avio_flush(avio); 85 | if (avio->error < 0) 86 | throw FFMS_Exception(error_source, FFMS_ERROR_FILE_WRITE, 87 | "Failed to write to '" + filename + "'"); 88 | return size; 89 | } 90 | 91 | int64_t FileHandle::Size() { 92 | int64_t size = avio_size(avio); 93 | if (size < 0) 94 | throw FFMS_Exception(error_source, FFMS_ERROR_FILE_READ, 95 | "Failed to get file size for '" + filename + "'"); 96 | return size; 97 | } 98 | 99 | int FileHandle::Printf(const char *fmt, ...) { 100 | va_list args; 101 | va_start(args, fmt); 102 | 103 | std::vector OutBuffer(100); 104 | int ret = -1; 105 | while (OutBuffer.size() < 1024 * 1024) { 106 | ret = vsnprintf(OutBuffer.data(), OutBuffer.size(), fmt, args); 107 | if (ret > 0 && ret < (int)OutBuffer.size()) 108 | break; 109 | OutBuffer.resize(OutBuffer.size() * 2); 110 | } 111 | 112 | va_end(args); 113 | 114 | avio_write(avio, reinterpret_cast(OutBuffer.data()), ret); 115 | avio_flush(avio); 116 | 117 | return avio->error < 0 ? avio->error : ret; 118 | } 119 | -------------------------------------------------------------------------------- /src/core/indexing.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007-2017 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef INDEXING_H 22 | #define INDEXING_H 23 | 24 | #include "utils.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | extern "C" { 32 | #include 33 | } 34 | 35 | class Wave64Writer; 36 | class ZipFile; 37 | 38 | struct SharedAVContext { 39 | AVCodecContext *CodecContext = nullptr; 40 | AVCodecParserContext *Parser = nullptr; 41 | int64_t CurrentSample = 0; 42 | ~SharedAVContext(); 43 | }; 44 | 45 | struct FFMS_Index : public std::vector { 46 | FFMS_Index(FFMS_Index const&) = delete; 47 | FFMS_Index& operator=(FFMS_Index const&) = delete; 48 | void ReadIndex(ZipFile &zf, const char* IndexFile); 49 | void WriteIndex(ZipFile &zf); 50 | public: 51 | static void CalculateFileSignature(const char *Filename, int64_t *Filesize, uint8_t Digest[20]); 52 | 53 | int ErrorHandling; 54 | int64_t Filesize; 55 | uint8_t Digest[20]; 56 | std::map LAVFOpts; 57 | 58 | void Finalize(std::vector const& video_contexts, const char *Format); 59 | bool CompareFileSignature(const char *Filename); 60 | void WriteIndexFile(const char *IndexFile); 61 | uint8_t *WriteIndexBuffer(size_t *Size); 62 | 63 | FFMS_Index(const char *IndexFile); 64 | FFMS_Index(const uint8_t *Buffer, size_t Size); 65 | FFMS_Index(int64_t Filesize, uint8_t Digest[20], int ErrorHandling, const std::map &LAVFOpts); 66 | }; 67 | 68 | struct FFMS_Indexer { 69 | private: 70 | std::map LastAudioProperties; 71 | FFMS_Indexer(FFMS_Indexer const&) = delete; 72 | FFMS_Indexer& operator=(FFMS_Indexer const&) = delete; 73 | AVFormatContext *FormatContext = nullptr; 74 | std::set IndexMask; 75 | std::map LAVFOpts; 76 | int ErrorHandling = FFMS_IEH_CLEAR_TRACK; 77 | TIndexCallback IC = nullptr; 78 | void *ICPrivate = nullptr; 79 | std::string SourceFile; 80 | AVFrame *DecodeFrame = nullptr; 81 | 82 | int64_t Filesize; 83 | uint8_t Digest[20]; 84 | 85 | void ReadTS(const AVPacket *Packet, int64_t &TS, bool &UseDTS); 86 | void CheckAudioProperties(int Track, AVCodecContext *Context); 87 | uint32_t IndexAudioPacket(int Track, AVPacket *Packet, SharedAVContext &Context, FFMS_Index &TrackIndices); 88 | void ParseVideoPacket(SharedAVContext &VideoContext, AVPacket *pkt, int *RepeatPict, int *FrameType, bool *Invisible, enum AVPictureStructure *LastPicStruct); 89 | void Free(); 90 | public: 91 | FFMS_Indexer(const char *Filename, const FFMS_KeyValuePair *DemuxerOptions, int NumOptions); 92 | ~FFMS_Indexer(); 93 | 94 | void SetIndexTrack(int Track, bool Index); 95 | void SetIndexTrackType(int TrackType, bool Index); 96 | void SetErrorHandling(int ErrorHandling_); 97 | void SetProgressCallback(TIndexCallback IC_, void *ICPrivate_); 98 | 99 | FFMS_Index *DoIndexing(); 100 | int GetNumberOfTracks(); 101 | FFMS_TrackType GetTrackType(int Track); 102 | const char *GetTrackCodec(int Track); 103 | const char *GetFormatName(); 104 | }; 105 | 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /src/core/track.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Thomas Goyne 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef TRACK_H 22 | #define TRACK_H 23 | 24 | #include "ffms.h" 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | class ZipFile; 31 | 32 | struct FrameInfo { 33 | int64_t PTS; 34 | int64_t OriginalPTS; 35 | int64_t FilePos; 36 | int64_t SampleStart; 37 | uint32_t SampleCount; 38 | size_t OriginalPos; 39 | int FrameType; 40 | int RepeatPict; 41 | bool KeyFrame; 42 | bool Hidden; 43 | }; 44 | 45 | struct FFMS_Track { 46 | private: 47 | typedef std::vector frame_vec; 48 | struct TrackData { 49 | frame_vec Frames; 50 | std::vector RealFrameNumbers; 51 | std::vector PublicFrameInfo; 52 | }; 53 | 54 | std::shared_ptr Data; 55 | 56 | void MaybeReorderFrames(); 57 | void GeneratePublicInfo(); 58 | 59 | public: 60 | FFMS_TrackType TT = FFMS_TYPE_UNKNOWN; 61 | FFMS_TrackTimeBase TB = FFMS_TrackTimeBase{ 0, 0 }; 62 | int MaxBFrames = 0; 63 | bool UseDTS = false; 64 | bool HasTS = false; 65 | bool HasDiscontTS = false; 66 | int64_t LastDuration = 0; 67 | int SampleRate = 0; // not persisted 68 | 69 | void AddVideoFrame(int64_t PTS, int RepeatPict, bool KeyFrame, int FrameType, int64_t FilePos = 0, bool Invisible = false); 70 | void AddAudioFrame(int64_t PTS, int64_t SampleStart, uint32_t SampleCount, bool KeyFrame, int64_t FilePos = 0, bool Invisible = false); 71 | 72 | void MaybeHideFrames(); 73 | void FinalizeTrack(); 74 | void FillAudioGaps(); 75 | 76 | int FindClosestVideoKeyFrame(int Frame) const; 77 | int FrameFromPTS(int64_t PTS) const; 78 | int FrameFromPos(int64_t Pos) const; 79 | int ClosestFrameFromPTS(int64_t PTS) const; 80 | int RealFrameNumber(int Frame) const; 81 | int VisibleFrameCount() const; 82 | 83 | const FFMS_FrameInfo *GetFrameInfo(size_t N) const; 84 | 85 | void WriteTimecodes(const char *TimecodeFile) const; 86 | void Write(ZipFile &Stream) const; 87 | 88 | typedef frame_vec::allocator_type allocator_type; 89 | typedef frame_vec::size_type size_type; 90 | typedef frame_vec::difference_type difference_type; 91 | typedef frame_vec::const_pointer pointer; 92 | typedef frame_vec::const_reference reference; 93 | typedef frame_vec::value_type value_type; 94 | typedef frame_vec::const_iterator iterator; 95 | typedef frame_vec::const_reverse_iterator reverse_iterator; 96 | 97 | void clear() { 98 | Data = std::make_shared(); 99 | } 100 | 101 | bool empty() const { return Data->Frames.empty(); } 102 | size_type size() const { return Data->Frames.size(); } 103 | reference operator[](size_type pos) const { return Data->Frames[pos]; } 104 | reference front() const { return Data->Frames.front(); } 105 | reference back() const { return Data->Frames.back(); } 106 | iterator begin() const { return Data->Frames.begin(); } 107 | iterator end() const { return Data->Frames.end(); } 108 | 109 | FFMS_Track(); 110 | FFMS_Track(ZipFile &Stream); 111 | FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT, bool HasDiscontTS, bool UseDTS, bool HasTS = true); 112 | }; 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /src/core/videosource.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007-2017 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef FFVIDEOSOURCE_H 22 | #define FFVIDEOSOURCE_H 23 | 24 | extern "C" { 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | } 33 | 34 | #include 35 | 36 | #include "track.h" 37 | #include "utils.h" 38 | 39 | struct FFMS_VideoSource { 40 | private: 41 | SwsContext *SWS = nullptr; 42 | 43 | int Delay = 0; 44 | int DelayCounter = 0; 45 | int InitialDecode = 1; 46 | bool PAFFAdjusted = false; 47 | 48 | int LastFrameHeight = -1; 49 | int LastFrameWidth = -1; 50 | AVPixelFormat LastFramePixelFormat = AV_PIX_FMT_NONE; 51 | 52 | int TargetHeight = -1; 53 | int TargetWidth = -1; 54 | std::vector TargetPixelFormats; 55 | int TargetResizer = 0; 56 | 57 | AVPixelFormat OutputFormat = AV_PIX_FMT_NONE; 58 | AVColorRange OutputColorRange = AVCOL_RANGE_UNSPECIFIED; 59 | AVColorSpace OutputColorSpace = AVCOL_SPC_UNSPECIFIED; 60 | bool OutputColorRangeSet = false; 61 | bool OutputColorSpaceSet = false; 62 | 63 | int OutputColorPrimaries = -1; 64 | int OutputTransferCharateristics = -1; 65 | int OutputChromaLocation = -1; 66 | 67 | bool InputFormatOverridden = false; 68 | AVPixelFormat InputFormat = AV_PIX_FMT_NONE; 69 | AVColorRange InputColorRange = AVCOL_RANGE_UNSPECIFIED; 70 | AVColorSpace InputColorSpace = AVCOL_SPC_UNSPECIFIED; 71 | 72 | uint8_t *SWSFrameData[4] = {}; 73 | int SWSFrameLinesize[4] = {}; 74 | 75 | void DetectInputFormat(); 76 | bool HasPendingDelayedFrames(); 77 | 78 | FFMS_VideoProperties VP = {}; 79 | FFMS_Frame LocalFrame = {}; 80 | uint8_t *RPUBuffer = nullptr; 81 | size_t RPUBufferSize = 0; 82 | AVFrame *DecodeFrame = nullptr; 83 | AVFrame *LastDecodedFrame = nullptr; 84 | int LastFrameNum = 0; 85 | FFMS_Index &Index; 86 | FFMS_Track Frames; 87 | int VideoTrack; 88 | int CurrentFrame = 1; 89 | int DecodingThreads; 90 | AVCodecContext *CodecContext = nullptr; 91 | AVFormatContext *FormatContext = nullptr; 92 | int SeekMode; 93 | bool SeekByPos = false; 94 | int PosOffset = 0; 95 | bool HaveSeenInterlacedFrame = false; 96 | 97 | void ReAdjustOutputFormat(AVFrame *Frame); 98 | FFMS_Frame *OutputFrame(AVFrame *Frame); 99 | void SetVideoProperties(); 100 | bool DecodePacket(AVPacket *Packet); 101 | void DecodeNextFrame(int64_t &PTS, int64_t &Pos); 102 | bool SeekTo(int n, int SeekOffset); 103 | int Seek(int n); 104 | int ReadFrame(AVPacket *pkt); 105 | void Free(); 106 | static void SanityCheckFrameForData(AVFrame *Frame); 107 | public: 108 | FFMS_VideoSource(const char *SourceFile, FFMS_Index &Index, int Track, int Threads, int SeekMode); 109 | ~FFMS_VideoSource(); 110 | const FFMS_VideoProperties& GetVideoProperties() { return VP; } 111 | FFMS_Track *GetTrack() { return &Frames; } 112 | FFMS_Frame *GetFrame(int n); 113 | void GetFrameCheck(int n); 114 | FFMS_Frame *GetFrameByTime(double Time); 115 | void SetOutputFormat(const AVPixelFormat *TargetFormats, int Width, int Height, int Resizer); 116 | void ResetOutputFormat(); 117 | void SetInputFormat(int ColorSpace, int ColorRange, AVPixelFormat Format); 118 | void ResetInputFormat(); 119 | }; 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ([2.58]) 2 | AC_INIT([ffms2],[esyscmd([sh version.sh])]) 3 | AC_CONFIG_SRCDIR([src/core/ffms.cpp]) 4 | AC_CONFIG_MACRO_DIR([m4]) 5 | AM_INIT_AUTOMAKE([1.11 subdir-objects]) 6 | AM_SILENT_RULES([yes]) 7 | AM_MAINTAINER_MODE([disable]) 8 | 9 | VERSION_INFO="5:0:0" 10 | 11 | AC_MSG_CHECKING([if debug build is enabled]) 12 | 13 | AC_ARG_ENABLE([debug], 14 | [AC_HELP_STRING([--enable-debug], 15 | [Enable debug build. [default=no]])], 16 | [enable_debug=yes], 17 | [enable_debug=no] 18 | ) 19 | 20 | AC_MSG_RESULT([$enable_debug]) 21 | 22 | if test "$enable_debug" = yes; then 23 | OPT_FLAGS="-O0 -g" 24 | else 25 | OPT_FLAGS="-O3" 26 | fi 27 | 28 | if test -z "$CFLAGS"; then 29 | CFLAGS="$OPT_FLAGS -Wall -Wextra" 30 | fi 31 | 32 | if test -z "$CXXFLAGS"; then 33 | CXXFLAGS="$OPT_FLAGS -Wall -Wextra" 34 | fi 35 | 36 | AC_ARG_ENABLE([avisynth], 37 | [AC_HELP_STRING([--enable-avisynth], 38 | [Enable AviSynth+ plugin. [default=no]])], 39 | [enable_avisynth=yes], 40 | [enable_avisynth=no] 41 | ) 42 | 43 | AC_MSG_RESULT([$enable_avisynth]) 44 | AM_CONDITIONAL([AVISYNTH], [test "x$enable_avisynth" != "xno"]) 45 | 46 | AC_CONFIG_HEADERS([src/config/config.h]) 47 | AC_PROG_CC 48 | AC_PROG_CXX 49 | AC_PROG_LN_S 50 | AC_PROG_MKDIR_P 51 | 52 | AC_CANONICAL_HOST 53 | AS_CASE([$host], 54 | [*mingw* | *cygwin*], [ 55 | AC_ENABLE_STATIC 56 | AC_DISABLE_SHARED 57 | ], [ 58 | AC_ENABLE_SHARED 59 | AC_DISABLE_STATIC]) 60 | 61 | AC_PROG_LIBTOOL 62 | 63 | if echo "$host" | $GREP "cygwin" >/dev/null 2>&1 && test "$enable_shared" = "yes"; then 64 | AC_MSG_ERROR([Shared build is broken on cygwin. 65 | Please remove --enable-shared from your configure options or build with another --host.]) 66 | fi 67 | 68 | dnl Workaround for a bug in libtool 69 | dnl The windows libtool uses a file magic checking method that only accepts 70 | dnl dynamic libraries. Change it for libtool's alternative checking method. 71 | if test "$lt_cv_file_magic_cmd" = "func_win32_libid" ; then 72 | deplibs_check_method='file_magic file format pei*-(i386|x86-64)|(.*architecture: i386)?' 73 | file_magic_cmd='$OBJDUMP -f' 74 | fi 75 | 76 | FFMS_VERSION="$(sh $(dirname -- "$0")/version.sh)" 77 | AC_SUBST([FFMS_VERSION]) 78 | 79 | CHECK_ZLIB 80 | 81 | dnl Save CFLAGS and LIBS for later, as anything else we add will be from pkg-config 82 | dnl and thus should be separate in our .pc file. 83 | _CFLAGS="$CFLAGS" 84 | _CPPFLAGS="$CPPFLAGS" 85 | _LIBS="$LIBS" 86 | 87 | PKG_PROG_PKG_CONFIG([0.22]) 88 | pkgconfigdir="\$(libdir)/pkgconfig" 89 | AC_SUBST(pkgconfigdir) 90 | 91 | PKG_CHECK_MODULES(FFMPEG, [libavformat >= 53.20.0 libavcodec >= 53.24.0 libswscale >= 0.7.0 libavutil >= 51.21.0 libswresample >= 1.0.0]) 92 | 93 | dnl As of 0eec06ed8747923faa6a98e474f224d922dc487d ffmpeg only adds -lrt to lavc's 94 | dnl LIBS, but lavu needs it, so move it to the end if it's present 95 | FFMPEG_LIBS=$(echo $FFMPEG_LIBS | sed 's/\(.*\)-lrt \(.*\)/\1\2 -lrt/') 96 | 97 | AC_SUBST([FFMPEG_CFLAGS]) 98 | AC_SUBST([FFMPEG_LIBS]) 99 | 100 | CPPFLAGS="$CPPFLAGS -D__STDC_CONSTANT_MACROS" 101 | CFLAGS="$_CFLAGS $FFMPEG_CFLAGS" 102 | 103 | AC_DEFUN([TEST_FFMPEG], 104 | [AC_LINK_IFELSE([AC_LANG_PROGRAM([[ 105 | #include 106 | #include 107 | ]],[[ 108 | avformat_network_init(); 109 | swscale_version(); 110 | ]])], [eval $1=yes], [eval $1=no]) 111 | ]) 112 | 113 | AC_MSG_CHECKING([whether FFmpeg works]) 114 | LIBS="$_LIBS $FFMPEG_LIBS" 115 | TEST_FFMPEG([FFMPEG_WORKS]) 116 | AC_MSG_RESULT([$FFMPEG_WORKS]) 117 | if test "$FFMPEG_WORKS" = no; then 118 | AC_MSG_FAILURE([cannot link with FFmpeg]) 119 | fi 120 | 121 | src_core_libffms2_la_LDFLAGS="" 122 | AC_MSG_CHECKING([whether -Wl,-Bsymbolic is needed]) 123 | if test "$enable_shared" = yes; then 124 | _LDFLAGS="$LDFLAGS" 125 | LDFLAGS="$LDFLAGS -shared $lt_prog_compiler_pic" 126 | TEST_FFMPEG([no_bsymbolic]) 127 | if test "$no_bsymbolic" = "no"; then 128 | LDFLAGS="$LDFLAGS -Wl,-Bsymbolic" 129 | TEST_FFMPEG([bsymbolic]) 130 | if test "$bsymbolic" = "yes"; then 131 | src_core_libffms2_la_LDFLAGS="$src_core_libffms2_la_LDFLAGS -Wl,-Bsymbolic" 132 | else 133 | AC_MSG_RESULT($bsymbolic) 134 | AC_MSG_FAILURE([cannot build ffms2 as a shared library]) 135 | fi 136 | else 137 | bsymbolic=no 138 | fi 139 | LDFLAGS="$_LDFLAGS" 140 | src_core_libffms2_la_LDFLAGS="$src_core_libffms2_la_LDFLAGS -version-info $VERSION_INFO" 141 | else 142 | bsymbolic=no 143 | fi 144 | AC_SUBST([src_core_libffms2_la_LDFLAGS]) 145 | 146 | CFLAGS="$_CFLAGS" 147 | CPPFLAGS="$_CPPFLAGS" 148 | LIBS="$_LIBS" 149 | AC_MSG_RESULT($bsymbolic) 150 | 151 | if echo "$host" | $GREP "mingw" >/dev/null 2>&1; then 152 | LTUNDEF="-no-undefined" 153 | fi 154 | AC_SUBST([LTUNDEF]) 155 | 156 | AC_CONFIG_FILES([ 157 | Makefile 158 | ffms2.pc 159 | ]) 160 | AC_OUTPUT 161 | 162 | -------------------------------------------------------------------------------- /src/core/utils.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007-2011 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef UTILS_H 22 | #define UTILS_H 23 | 24 | #include "ffms.h" 25 | 26 | extern "C" { 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | } 34 | 35 | // must be included after ffmpeg headers 36 | #include "ffmscompat.h" 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | class FFMS_Exception { 45 | std::string _Message; 46 | int _ErrorType; 47 | int _SubType; 48 | 49 | public: 50 | FFMS_Exception(int ErrorType, int SubType, const char *Message = ""); 51 | FFMS_Exception(int ErrorType, int SubType, const std::string &Message); 52 | const std::string &GetErrorMessage() const { return _Message; } 53 | int CopyOut(FFMS_ErrorInfo *ErrorInfo) const; 54 | }; 55 | 56 | template 57 | std::unique_ptr make_unique(Args&&... args) { 58 | return std::unique_ptr(new T(std::forward(args)...)); 59 | } 60 | 61 | void ClearErrorInfo(FFMS_ErrorInfo *ErrorInfo); 62 | void FillAP(FFMS_AudioProperties &AP, AVCodecContext *CTX, FFMS_Track &Frames); 63 | 64 | void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext, int Track, const std::map &LAVFOpts); 65 | 66 | namespace optdetail { 67 | template 68 | T get_av_opt(void *v, const char *name) { 69 | int64_t value = 0; 70 | av_opt_get_int(v, name, 0, &value); 71 | return static_cast(value); 72 | } 73 | 74 | template<> 75 | inline double get_av_opt(void *v, const char *name) { 76 | double value = 0.0; 77 | av_opt_get_double(v, name, 0, &value); 78 | return value; 79 | } 80 | 81 | template 82 | void set_av_opt(void *v, const char *name, T value) { 83 | av_opt_set_int(v, name, value, 0); 84 | } 85 | 86 | template<> 87 | inline void set_av_opt(void *v, const char *name, double value) { 88 | av_opt_set_double(v, name, value, 0); 89 | } 90 | } 91 | 92 | template 93 | class OptionMapper { 94 | struct OptionMapperBase { 95 | virtual void ToOpt(FFMS_Struct const& src, void *dst) const = 0; 96 | virtual void FromOpt(FFMS_Struct &dst, void *src) const = 0; 97 | virtual ~OptionMapperBase() = default; 98 | }; 99 | 100 | template 101 | class OptionMapperImpl : public OptionMapperBase { 102 | T(FFMS_Struct::*ptr); 103 | const char *name; 104 | 105 | public: 106 | OptionMapperImpl(T(FFMS_Struct::*ptr), const char *name) : ptr(ptr), name(name) {} 107 | void ToOpt(FFMS_Struct const& src, void *dst) const { optdetail::set_av_opt(dst, name, src.*ptr); } 108 | void FromOpt(FFMS_Struct &dst, void *src) const { dst.*ptr = optdetail::get_av_opt(src, name); } 109 | }; 110 | 111 | std::unique_ptr impl; 112 | 113 | public: 114 | template 115 | OptionMapper(const char *opt_name, T(FFMS_Struct::*member)) : impl(new OptionMapperImpl(member, opt_name)) {} 116 | 117 | void ToOpt(FFMS_Struct const& src, void *dst) const { impl->ToOpt(src, dst); } 118 | void FromOpt(FFMS_Struct &dst, void *src) const { impl->FromOpt(dst, src); } 119 | }; 120 | 121 | template 122 | std::unique_ptr ReadOptions(void *opt, OptionMapper(&options)[N]) { 123 | auto ret = make_unique(); 124 | for (int i = 0; i < N; ++i) 125 | options[i].FromOpt(*ret, opt); 126 | return ret; 127 | } 128 | 129 | template 130 | void SetOptions(T const& src, void *opt, OptionMapper(&options)[N]) { 131 | for (int i = 0; i < N; ++i) 132 | options[i].ToOpt(src, opt); 133 | } 134 | 135 | int ResizerNameToSWSResizer(const char *ResizerName); 136 | bool IsSamePath(const char *p1, const char *p2); 137 | 138 | #endif 139 | -------------------------------------------------------------------------------- /src/core/zipfile.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Thomas Goyne 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include "zipfile.h" 22 | 23 | extern "C" { 24 | #include 25 | } 26 | 27 | #include "utils.h" 28 | 29 | ZipFile::ZipFile(const char *filename, const char *mode) 30 | : file(filename, mode, FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ) 31 | , is_file(true) 32 | , state(Initial) { 33 | buffer.resize(65536); 34 | z = {}; 35 | } 36 | 37 | ZipFile::ZipFile() 38 | : is_file(false) 39 | , state(Initial) { 40 | buffer.resize(65536); 41 | z = {}; 42 | } 43 | 44 | ZipFile::ZipFile(const uint8_t *in_buffer, const size_t size) 45 | : index_buffer(in_buffer, in_buffer + size) 46 | , is_file(false) 47 | , state(Initial) { 48 | z = {}; 49 | } 50 | 51 | ZipFile::~ZipFile() { 52 | if (state == Inflate) 53 | inflateEnd(&z); 54 | if (state == Deflate) 55 | deflateEnd(&z); 56 | } 57 | 58 | void ZipFile::Read(void *data, size_t size) { 59 | if (state == Deflate) { 60 | deflateEnd(&z); 61 | state = Initial; 62 | } 63 | if (state != Inflate) { 64 | if (inflateInit(&z) != Z_OK) 65 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to initialize zlib"); 66 | state = Inflate; 67 | } 68 | 69 | z.next_out = static_cast(data); 70 | z.avail_out = size; 71 | if (!z.avail_in && !is_file) { 72 | z.next_in = reinterpret_cast(&index_buffer[0]); 73 | z.avail_in = index_buffer.size(); 74 | } 75 | do { 76 | if (!z.avail_in && is_file) { 77 | z.next_in = reinterpret_cast(&buffer[0]); 78 | z.avail_in = file.Read(&buffer[0], buffer.size()); 79 | } 80 | if (!z.avail_in) { 81 | if (!is_file) 82 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Buffer is empty"); 83 | else if (!file.Tell()) 84 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: File is empty"); 85 | } 86 | 87 | switch (inflate(&z, Z_SYNC_FLUSH)) { 88 | case Z_NEED_DICT: 89 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Dictionary error."); 90 | case Z_DATA_ERROR: 91 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Data error."); 92 | case Z_MEM_ERROR: 93 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Memory error."); 94 | case Z_STREAM_END: 95 | if (z.avail_out > 0) 96 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Stream ended early"); 97 | } 98 | } while (z.avail_out); 99 | } 100 | 101 | int ZipFile::Write(const void *data, size_t size) { 102 | if (state == Inflate) { 103 | inflateEnd(&z); 104 | state = Initial; 105 | } 106 | if (state != Deflate) { 107 | if (deflateInit(&z, 5) != Z_OK) 108 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to initialize zlib"); 109 | state = Deflate; 110 | } 111 | 112 | z.next_in = static_cast(const_cast(data)); 113 | z.avail_in = size; 114 | int ret = 0; 115 | do { 116 | z.avail_out = buffer.size(); 117 | z.next_out = reinterpret_cast(&buffer[0]); 118 | ret = deflate(&z, size > 0 ? Z_NO_FLUSH : Z_FINISH); 119 | uInt written = buffer.size() - z.avail_out; 120 | if (written) { 121 | if (is_file) 122 | file.Write(&buffer[0], written); 123 | else 124 | index_buffer.insert(index_buffer.end(), &buffer[0], &buffer[written]); 125 | } 126 | } while (z.avail_out == 0); 127 | return ret; 128 | } 129 | 130 | void ZipFile::Finish() { 131 | while (Write(nullptr, 0) != Z_STREAM_END); 132 | deflateEnd(&z); 133 | state = Initial; 134 | } 135 | 136 | uint8_t *ZipFile::GetBuffer(size_t *size) { 137 | uint8_t *ret = (uint8_t *)av_malloc(index_buffer.size()); 138 | if (ret == nullptr) 139 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_ALLOCATION_FAILED, "Failed to allocate index return buffer"); 140 | 141 | memcpy(ret, reinterpret_cast(&index_buffer[0]), index_buffer.size()); 142 | *size = index_buffer.size(); 143 | 144 | return ret; 145 | } 146 | -------------------------------------------------------------------------------- /src/core/utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007-2015 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | extern "C" { 22 | #include 23 | } 24 | 25 | #include "utils.h" 26 | 27 | #include "indexing.h" 28 | #include "track.h" 29 | 30 | #ifdef _WIN32 31 | # define WIN32_LEAN_AND_MEAN 32 | # include 33 | #endif // _WIN32 34 | 35 | #include 36 | #include 37 | 38 | FFMS_Exception::FFMS_Exception(int ErrorType, int SubType, const char *Message) : _Message(Message), _ErrorType(ErrorType), _SubType(SubType) {} 39 | FFMS_Exception::FFMS_Exception(int ErrorType, int SubType, const std::string &Message) : _Message(Message), _ErrorType(ErrorType), _SubType(SubType) {} 40 | 41 | int FFMS_Exception::CopyOut(FFMS_ErrorInfo *ErrorInfo) const { 42 | if (ErrorInfo) { 43 | ErrorInfo->ErrorType = _ErrorType; 44 | ErrorInfo->SubType = _SubType; 45 | 46 | if (ErrorInfo->BufferSize > 0) { 47 | memset(ErrorInfo->Buffer, 0, ErrorInfo->BufferSize); 48 | _Message.copy(ErrorInfo->Buffer, ErrorInfo->BufferSize - 1); 49 | } 50 | } 51 | 52 | return (_ErrorType << 16) | _SubType; 53 | } 54 | 55 | void ClearErrorInfo(FFMS_ErrorInfo *ErrorInfo) { 56 | if (ErrorInfo) { 57 | ErrorInfo->ErrorType = FFMS_ERROR_SUCCESS; 58 | ErrorInfo->SubType = FFMS_ERROR_SUCCESS; 59 | 60 | if (ErrorInfo->BufferSize > 0) 61 | ErrorInfo->Buffer[0] = 0; 62 | } 63 | } 64 | 65 | void FillAP(FFMS_AudioProperties &AP, AVCodecContext *CTX, FFMS_Track &Frames) { 66 | AP.SampleFormat = static_cast(av_get_packed_sample_fmt(CTX->sample_fmt)); 67 | AP.BitsPerSample = av_get_bytes_per_sample(CTX->sample_fmt) * 8; 68 | AP.Channels = CTX->channels; 69 | AP.ChannelLayout = CTX->channel_layout; 70 | AP.SampleRate = CTX->sample_rate; 71 | if (!Frames.empty()) { 72 | AP.NumSamples = (Frames.back()).SampleStart + (Frames.back()).SampleCount; 73 | AP.FirstTime = ((Frames.front().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; 74 | AP.LastTime = ((Frames.back().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; 75 | AP.LastEndTime = (((Frames.back().PTS + Frames.LastDuration) * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; 76 | } 77 | 78 | if (AP.ChannelLayout == 0) 79 | AP.ChannelLayout = av_get_default_channel_layout(AP.Channels); 80 | } 81 | 82 | void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext, int Track, const std::map &LAVFOpts) { 83 | AVDictionary *Dict = nullptr; 84 | for (const auto &iter : LAVFOpts) 85 | av_dict_set(&Dict, iter.first.c_str(), iter.second.c_str(), 0); 86 | 87 | if (avformat_open_input(&FormatContext, SourceFile, nullptr, &Dict) != 0) 88 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, 89 | std::string("Couldn't open '") + SourceFile + "'"); 90 | 91 | av_dict_free(&Dict); 92 | 93 | if (avformat_find_stream_info(FormatContext, nullptr) < 0) { 94 | avformat_close_input(&FormatContext); 95 | FormatContext = nullptr; 96 | throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, 97 | "Couldn't find stream information"); 98 | } 99 | 100 | for (int i = 0; i < (int)FormatContext->nb_streams; i++) 101 | if (i != Track) 102 | FormatContext->streams[i]->discard = AVDISCARD_ALL; 103 | } 104 | 105 | int ResizerNameToSWSResizer(const char *ResizerName) { 106 | if (!ResizerName) 107 | return 0; 108 | std::string s = ResizerName; 109 | std::transform(s.begin(), s.end(), s.begin(), toupper); 110 | if (s == "FAST_BILINEAR") 111 | return SWS_FAST_BILINEAR; 112 | if (s == "BILINEAR") 113 | return SWS_BILINEAR; 114 | if (s == "BICUBIC") 115 | return SWS_BICUBIC; 116 | if (s == "X") 117 | return SWS_X; 118 | if (s == "POINT") 119 | return SWS_POINT; 120 | if (s == "AREA") 121 | return SWS_AREA; 122 | if (s == "BICUBLIN") 123 | return SWS_BICUBLIN; 124 | if (s == "GAUSS") 125 | return SWS_GAUSS; 126 | if (s == "SINC") 127 | return SWS_SINC; 128 | if (s == "LANCZOS") 129 | return SWS_LANCZOS; 130 | if (s == "SPLINE") 131 | return SWS_SPLINE; 132 | return 0; 133 | } 134 | 135 | bool IsSamePath(const char *p1, const char *p2) { 136 | // assume windows is the only OS with a case insensitive filesystem and ignore all path complications 137 | #ifndef _WIN32 138 | return !strcmp(p1, p2); 139 | #else 140 | return !_stricmp(p1, p2); 141 | #endif 142 | } 143 | -------------------------------------------------------------------------------- /src/core/audiosource.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007-2011 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef FFAUDIOSOURCE_H 22 | #define FFAUDIOSOURCE_H 23 | 24 | #include "utils.h" 25 | #include "track.h" 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | struct FFMS_AudioSource { 32 | struct AudioBlock { 33 | struct Free { 34 | void operator()(uint8_t *ptr) const { 35 | free(ptr); 36 | } 37 | }; 38 | 39 | int64_t Age; 40 | int64_t Start; 41 | int64_t Samples = 0; 42 | size_t DataSize = 0; 43 | std::unique_ptr Data; 44 | 45 | AudioBlock(int64_t Start) 46 | : Start(Start) { 47 | static std::atomic Now{ 0 }; 48 | Age = Now++; 49 | } 50 | 51 | uint8_t *Grow(size_t size) { 52 | auto ptr = static_cast(realloc(Data.get(), DataSize + size)); 53 | if (!ptr) 54 | throw std::bad_alloc(); 55 | Data.release(); 56 | Data.reset(ptr); 57 | ptr += DataSize; 58 | DataSize += size; 59 | return ptr; 60 | } 61 | }; 62 | typedef std::list::iterator CacheIterator; 63 | 64 | AVFormatContext *FormatContext = nullptr; 65 | std::map LAVFOpts; 66 | double DrcScale; 67 | int64_t LastValidTS; 68 | std::string SourceFile; 69 | 70 | // delay in samples to apply to the audio 71 | int64_t Delay = 0; 72 | // cache of decoded audio blocks 73 | std::list Cache; 74 | // max size of the cache in blocks 75 | size_t MaxCacheBlocks = 50; 76 | // pointer to last element of the cache which should never be deleted 77 | CacheIterator CacheNoDelete; 78 | // bytes per sample * number of channels, *after* resampling if applicable 79 | size_t BytesPerSample = 0; 80 | 81 | bool NeedsResample = false; 82 | 83 | struct SwrFreeWrapper { 84 | void operator()(SwrContext *c) const { 85 | swr_free(&c); 86 | } 87 | }; 88 | 89 | typedef std::unique_ptr FFResampleContext; 90 | FFResampleContext ResampleContext; 91 | 92 | // Insert the current audio frame into the cache 93 | AudioBlock *CacheBlock(CacheIterator &pos); 94 | 95 | // Interleave the current audio frame and insert it into the cache 96 | void ResampleAndCache(CacheIterator pos); 97 | 98 | // Cache the unseekable beginning of the file once the output format is set 99 | void CacheBeginning(); 100 | 101 | // Called after seeking 102 | void Seek(); 103 | // Read the next packet from the file 104 | bool ReadPacket(AVPacket *); 105 | 106 | // Close and reopen the source file to seek back to the beginning. Only 107 | // needs to do anything for formats that can't seek to the beginning otherwise. 108 | // 109 | // If the file is not already open, it is merely just opened. 110 | void OpenFile(); 111 | 112 | // First sample which is stored in the decoding buffer 113 | int64_t CurrentSample = -1; 114 | // Next packet to be read 115 | size_t PacketNumber = 0; 116 | // Current audio frame 117 | const FrameInfo *CurrentFrame = nullptr; 118 | // Track which this corresponds to 119 | int TrackNumber; 120 | // Number of packets which the demuxer requires to know where it is 121 | // If -1, seeking is assumed to be impossible 122 | int SeekOffset = 0; 123 | 124 | // Buffer which audio is decoded into 125 | AVFrame *DecodeFrame = nullptr; 126 | FFMS_Track Frames; 127 | AVCodecContext *CodecContext = nullptr; 128 | FFMS_AudioProperties AP = {}; 129 | 130 | int DecodeNextBlock(CacheIterator *cachePos = 0); 131 | // Initialization which has to be done after the codec is opened 132 | void Init(const FFMS_Index &Index, int DelayMode); 133 | 134 | int64_t FrameTS(size_t Packet) const; 135 | 136 | void Free(); 137 | public: 138 | FFMS_AudioSource(const char *SourceFile, FFMS_Index &Index, int Track, int DelayMode, int FillGaps, double DrcScale); 139 | ~FFMS_AudioSource(); 140 | FFMS_Track *GetTrack() { return &Frames; } 141 | const FFMS_AudioProperties& GetAudioProperties() const { return AP; } 142 | void GetAudio(void *Buf, int64_t Start, int64_t Count); 143 | 144 | std::unique_ptr CreateResampleOptions() const; 145 | void SetOutputFormat(FFMS_ResampleOptions const& opt); 146 | 147 | static size_t GetSeekablePacketNumber(FFMS_Track const& Frames, size_t PacketNumber); 148 | }; 149 | 150 | #endif 151 | -------------------------------------------------------------------------------- /src/vapoursynth/VSHelper.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Copyright (c) 2012-2015 Fredrik Mellbin 3 | * --- Legal stuff --- 4 | * This program is free software. It comes without any warranty, to 5 | * the extent permitted by applicable law. You can redistribute it 6 | * and/or modify it under the terms of the Do What The Fuck You Want 7 | * To Public License, Version 2, as published by Sam Hocevar. See 8 | * http://sam.zoy.org/wtfpl/COPYING for more details. 9 | *****************************************************************************/ 10 | 11 | #ifndef VSHELPER_H 12 | #define VSHELPER_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #ifdef _WIN32 21 | #include 22 | #endif 23 | #include "VapourSynth.h" 24 | 25 | /* Visual Studio doesn't recognize inline in c mode */ 26 | #if defined(_MSC_VER) && !defined(__cplusplus) 27 | #define inline _inline 28 | #endif 29 | 30 | /* A kinda portable definition of the C99 restrict keyword (or its inofficial C++ equivalent) */ 31 | #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* Available in C99 */ 32 | #define VS_RESTRICT restrict 33 | #elif defined(__cplusplus) || defined(_MSC_VER) /* Almost all relevant C++ compilers support it so just assume it works */ 34 | #define VS_RESTRICT __restrict 35 | #else /* Not supported */ 36 | #define VS_RESTRICT 37 | #endif 38 | 39 | #ifdef _WIN32 40 | #define VS_ALIGNED_MALLOC(pptr, size, alignment) do { *(pptr) = _aligned_malloc((size), (alignment)); } while (0) 41 | #define VS_ALIGNED_FREE(ptr) do { _aligned_free((ptr)); } while (0) 42 | #else 43 | #define VS_ALIGNED_MALLOC(pptr, size, alignment) do { if(posix_memalign((void**)(pptr), (alignment), (size))) *((void**)pptr) = NULL; } while (0) 44 | #define VS_ALIGNED_FREE(ptr) do { free((ptr)); } while (0) 45 | #endif 46 | 47 | #define VSMAX(a,b) ((a) > (b) ? (a) : (b)) 48 | #define VSMIN(a,b) ((a) > (b) ? (b) : (a)) 49 | 50 | #ifdef __cplusplus 51 | /* A nicer templated malloc for all the C++ users out there */ 52 | #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) 53 | template 54 | #else 55 | template 56 | #endif 57 | static inline T* vs_aligned_malloc(size_t size, size_t alignment) { 58 | #ifdef _WIN32 59 | return (T*)_aligned_malloc(size, alignment); 60 | #else 61 | void *tmp = NULL; 62 | if (posix_memalign(&tmp, alignment, size)) 63 | tmp = 0; 64 | return (T*)tmp; 65 | #endif 66 | } 67 | 68 | static inline void vs_aligned_free(void *ptr) { 69 | VS_ALIGNED_FREE(ptr); 70 | } 71 | #endif /* __cplusplus */ 72 | 73 | /* convenience function for checking if the format never changes between frames */ 74 | static inline int isConstantFormat(const VSVideoInfo *vi) { 75 | return vi->height > 0 && vi->width > 0 && vi->format; 76 | } 77 | 78 | /* convenience function to check for if two clips have the same format (unknown/changeable will be considered the same too) */ 79 | static inline int isSameFormat(const VSVideoInfo *v1, const VSVideoInfo *v2) { 80 | return v1->height == v2->height && v1->width == v2->width && v1->format == v2->format; 81 | } 82 | 83 | /* multiplies and divides a rational number, such as a frame duration, in place and reduces the result */ 84 | static inline void muldivRational(int64_t *num, int64_t *den, int64_t mul, int64_t div) { 85 | /* do nothing if the rational number is invalid */ 86 | if (!*den) 87 | return; 88 | 89 | /* nobody wants to accidentally divide by zero */ 90 | assert(div); 91 | 92 | int64_t a, b; 93 | *num *= mul; 94 | *den *= div; 95 | a = *num; 96 | b = *den; 97 | while (b != 0) { 98 | int64_t t = a; 99 | a = b; 100 | b = t % b; 101 | } 102 | if (a < 0) 103 | a = -a; 104 | *num /= a; 105 | *den /= a; 106 | } 107 | 108 | /* reduces a rational number */ 109 | static inline void vs_normalizeRational(int64_t *num, int64_t *den) { 110 | muldivRational(num, den, 1, 1); 111 | } 112 | 113 | /* add two rational numbers and reduces the result */ 114 | static inline void vs_addRational(int64_t *num, int64_t *den, int64_t addnum, int64_t addden) { 115 | /* do nothing if the rational number is invalid */ 116 | if (!*den) 117 | return; 118 | 119 | /* nobody wants to accidentally add an invalid rational number */ 120 | assert(addden); 121 | 122 | if (*den == addden) { 123 | *num += addnum; 124 | } else { 125 | int64_t temp = addden; 126 | addnum *= *den; 127 | addden *= *den; 128 | *num *= temp; 129 | *den *= temp; 130 | 131 | *num += addnum; 132 | 133 | vs_normalizeRational(num, den); 134 | } 135 | } 136 | 137 | /* converts an int64 to int with saturation, useful to silence warnings when reading int properties among other things */ 138 | static inline int int64ToIntS(int64_t i) { 139 | if (i > INT_MAX) 140 | return INT_MAX; 141 | else if (i < INT_MIN) 142 | return INT_MIN; 143 | else return (int)i; 144 | } 145 | 146 | static inline void vs_bitblt(void *dstp, int dst_stride, const void *srcp, int src_stride, size_t row_size, size_t height) { 147 | if (height) { 148 | if (src_stride == dst_stride && src_stride == (int)row_size) { 149 | memcpy(dstp, srcp, row_size * height); 150 | } else { 151 | const uint8_t *srcp8 = (const uint8_t *)srcp; 152 | uint8_t *dstp8 = (uint8_t *)dstp; 153 | size_t i; 154 | for (i = 0; i < height; i++) { 155 | memcpy(dstp8, srcp8, row_size); 156 | srcp8 += src_stride; 157 | dstp8 += dst_stride; 158 | } 159 | } 160 | } 161 | } 162 | 163 | /* check if the frame dimensions are valid for a given format */ 164 | /* returns non-zero for valid width and height */ 165 | static inline int areValidDimensions(const VSFormat *fi, int width, int height) { 166 | return !(width % (1 << fi->subSamplingW) || height % (1 << fi->subSamplingH)); 167 | } 168 | 169 | /* Visual Studio doesn't recognize inline in c mode */ 170 | #if defined(_MSC_VER) && !defined(__cplusplus) 171 | #undef inline 172 | #endif 173 | 174 | #endif 175 | -------------------------------------------------------------------------------- /test/hdr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "tests.h" 11 | 12 | typedef struct HDR10Data { 13 | double MasteringDisplayPrimariesX[3]; 14 | double MasteringDisplayPrimariesY[3]; 15 | double MasteringDisplayWhitePointX; 16 | double MasteringDisplayWhitePointY; 17 | double MasteringDisplayMinLuminance; 18 | double MasteringDisplayMaxLuminance; 19 | } HDR10Data; 20 | 21 | // Tests relative error with an error of 1e-5, given the accuracy of 22 | // the hardcoded test values, and how it would affect HDR. 23 | #define TEST_DOUBLE(A, B) (fabs((A / B) - 1) < (double) 1e-5) 24 | 25 | const HDR10Data StreamHDR10Data = { 26 | { 35400.0 / 50000.0, 8500.0 / 50000.0, 6550.0 / 50000.0 }, 27 | { 14599.0 / 50000.0 , 39850.0 / 50000.0 , 2300.0 / 50000.0 }, 28 | 15634.0 / 50000.0, 29 | 16450.0 / 50000.0, 30 | 10.0 / 10000.0, 31 | 10000000.0 / 10000.0 32 | }; 33 | 34 | const HDR10Data ContainerHDR10Data { 35 | { 34000.0 / 50000.0, 13250.0 / 50000.0, 7500.0 / 50000.0 }, 36 | { 16000.0 / 50000.0, 34500.0 / 50000.0, 3000.0 / 50000.0 }, 37 | 15635.0 / 50000.0, 38 | 16450.0 / 50000.0, 39 | 100.0 / 10000.0, 40 | 10000000.0 / 10000.0 41 | }; 42 | 43 | namespace { 44 | 45 | class HDR10Test : public ::testing::Test { 46 | protected: 47 | virtual void SetUp(); 48 | virtual void TearDown(); 49 | bool DoIndexing(std::string); 50 | 51 | FFMS_Indexer* indexer; 52 | FFMS_Index* index; 53 | int video_track_idx; 54 | FFMS_VideoSource* video_source; 55 | const FFMS_VideoProperties* VP; 56 | 57 | FFMS_ErrorInfo E; 58 | char ErrorMsg[1024]; 59 | 60 | std::string SamplesDir; 61 | }; 62 | 63 | void HDR10Test::SetUp() { 64 | indexer = nullptr; 65 | index = nullptr; 66 | video_track_idx = -1; 67 | video_source = nullptr; 68 | VP = nullptr; 69 | E.Buffer = ErrorMsg; 70 | E.BufferSize = sizeof(ErrorMsg); 71 | SamplesDir = STRINGIFY(SAMPLES_DIR); 72 | 73 | FFMS_Init(0,0); 74 | } 75 | 76 | void HDR10Test::TearDown() { 77 | FFMS_DestroyIndex(index); 78 | FFMS_DestroyVideoSource(video_source); 79 | FFMS_Deinit(); 80 | } 81 | 82 | bool HDR10Test::DoIndexing(std::string file_name) { 83 | indexer = FFMS_CreateIndexer(file_name.c_str(), &E); 84 | NULL_CHECK(indexer); 85 | FFMS_TrackTypeIndexSettings(indexer, FFMS_TYPE_VIDEO, 1, 0); 86 | 87 | index = FFMS_DoIndexing2(indexer, 0, &E); 88 | NULL_CHECK(index); 89 | 90 | video_track_idx = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_VIDEO, &E); 91 | EXPECT_GE(0, video_track_idx); 92 | 93 | video_source = FFMS_CreateVideoSource(file_name.c_str(), video_track_idx, index, 1, FFMS_SEEK_NORMAL, &E); 94 | NULL_CHECK(video_source); 95 | 96 | VP = FFMS_GetVideoProperties(video_source); // Can't fail 97 | 98 | return true; 99 | } 100 | 101 | TEST_F(HDR10Test, StreamData) { 102 | std::string FilePath = SamplesDir + "/hdr10tags-stream.mp4"; 103 | 104 | ASSERT_TRUE(DoIndexing(FilePath)); 105 | 106 | ASSERT_TRUE(!!VP->HasMasteringDisplayPrimaries); 107 | for (int i = 0; i < 3; i++) { 108 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayPrimariesX[i], StreamHDR10Data.MasteringDisplayPrimariesX[i])); 109 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayPrimariesY[i], StreamHDR10Data.MasteringDisplayPrimariesY[i])); 110 | } 111 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayWhitePointX, StreamHDR10Data.MasteringDisplayWhitePointX)); 112 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayWhitePointY, StreamHDR10Data.MasteringDisplayWhitePointY)); 113 | 114 | ASSERT_TRUE(!!VP->HasMasteringDisplayLuminance); 115 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayMinLuminance, StreamHDR10Data.MasteringDisplayMinLuminance)); 116 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayMaxLuminance, StreamHDR10Data.MasteringDisplayMaxLuminance)); 117 | } 118 | 119 | TEST_F(HDR10Test, ContainerData) { 120 | std::string FilePath = SamplesDir + "/hdr10tags-container.mkv"; 121 | 122 | ASSERT_TRUE(DoIndexing(FilePath)); 123 | 124 | // Stream HDR metadata should be used. 125 | ASSERT_TRUE(!!VP->HasMasteringDisplayPrimaries); 126 | for (int i = 0; i < 3; i++) { 127 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayPrimariesX[i], ContainerHDR10Data.MasteringDisplayPrimariesX[i])); 128 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayPrimariesY[i], ContainerHDR10Data.MasteringDisplayPrimariesY[i])); 129 | } 130 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayWhitePointX, ContainerHDR10Data.MasteringDisplayWhitePointX)); 131 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayWhitePointY, ContainerHDR10Data.MasteringDisplayWhitePointY)); 132 | 133 | ASSERT_TRUE(!!VP->HasMasteringDisplayLuminance); 134 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayMinLuminance, ContainerHDR10Data.MasteringDisplayMinLuminance)); 135 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayMaxLuminance, ContainerHDR10Data.MasteringDisplayMaxLuminance)); 136 | } 137 | 138 | TEST_F(HDR10Test, StreamAndContainerData) { 139 | std::string FilePath = SamplesDir + "/hdr10tags-both.mkv"; 140 | 141 | ASSERT_TRUE(DoIndexing(FilePath)); 142 | 143 | // Stream HDR metadata should be used. 144 | ASSERT_TRUE(!!VP->HasMasteringDisplayPrimaries); 145 | for (int i = 0; i < 3; i++) { 146 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayPrimariesX[i], StreamHDR10Data.MasteringDisplayPrimariesX[i])); 147 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayPrimariesY[i], StreamHDR10Data.MasteringDisplayPrimariesY[i])); 148 | } 149 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayWhitePointX, StreamHDR10Data.MasteringDisplayWhitePointX)); 150 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayWhitePointY, StreamHDR10Data.MasteringDisplayWhitePointY)); 151 | 152 | ASSERT_TRUE(!!VP->HasMasteringDisplayLuminance); 153 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayMinLuminance, StreamHDR10Data.MasteringDisplayMinLuminance)); 154 | ASSERT_TRUE(TEST_DOUBLE(VP->MasteringDisplayMaxLuminance, StreamHDR10Data.MasteringDisplayMaxLuminance)); 155 | } 156 | 157 | } //namespace 158 | 159 | int main(int argc, char **argv) { 160 | ::testing::InitGoogleTest(&argc, argv); 161 | return RUN_ALL_TESTS(); 162 | } 163 | -------------------------------------------------------------------------------- /src/vapoursynth/VSHelper4.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Copyright (c) 2012-2020 Fredrik Mellbin 3 | * --- Legal stuff --- 4 | * This program is free software. It comes without any warranty, to 5 | * the extent permitted by applicable law. You can redistribute it 6 | * and/or modify it under the terms of the Do What The Fuck You Want 7 | * To Public License, Version 2, as published by Sam Hocevar. See 8 | * http://sam.zoy.org/wtfpl/COPYING for more details. 9 | *****************************************************************************/ 10 | 11 | #ifndef VSHELPER4_H 12 | #define VSHELPER4_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #ifdef _WIN32 22 | #include 23 | #endif 24 | #include "VapourSynth4.h" 25 | 26 | #define VSH_STD_PLUGIN_ID "com.vapoursynth.std" 27 | #define VSH_RESIZE_PLUGIN_ID "com.vapoursynth.resize" 28 | #define VSH_TEXT_PLUGIN_ID "com.vapoursynth.text" 29 | 30 | #ifdef __cplusplus 31 | namespace vsh { 32 | #define VSH4_MANGLE_FUNCTION_NAME(name) name 33 | #define VSH4_BOOLEAN_TYPE bool 34 | #else 35 | #define VSH4_MANGLE_FUNCTION_NAME(name) vsh_##name 36 | #define VSH4_BOOLEAN_TYPE int 37 | #endif 38 | 39 | /* Visual Studio doesn't recognize inline in c mode */ 40 | #if defined(_MSC_VER) && !defined(__cplusplus) 41 | #define inline _inline 42 | #endif 43 | 44 | /* A kinda portable definition of the C99 restrict keyword (or its inofficial C++ equivalent) */ 45 | #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* Available in C99 */ 46 | #define VS_RESTRICT restrict 47 | #elif defined(__cplusplus) || defined(_MSC_VER) /* Almost all relevant C++ compilers support it so just assume it works */ 48 | #define VS_RESTRICT __restrict 49 | #else /* Not supported */ 50 | #define VS_RESTRICT 51 | #endif 52 | 53 | #ifdef _WIN32 54 | #define VSH_ALIGNED_MALLOC(pptr, size, alignment) do { *(pptr) = _aligned_malloc((size), (alignment)); } while (0) 55 | #define VSH_ALIGNED_FREE(ptr) do { _aligned_free((ptr)); } while (0) 56 | #else 57 | #define VSH_ALIGNED_MALLOC(pptr, size, alignment) do { if(posix_memalign((void**)(pptr), (alignment), (size))) *((void**)pptr) = NULL; } while (0) 58 | #define VSH_ALIGNED_FREE(ptr) do { free((ptr)); } while (0) 59 | #endif 60 | 61 | #define VSMAX(a,b) ((a) > (b) ? (a) : (b)) 62 | #define VSMIN(a,b) ((a) > (b) ? (b) : (a)) 63 | 64 | #ifdef __cplusplus 65 | /* A nicer templated malloc for all the C++ users out there */ 66 | #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) 67 | template 68 | #else 69 | template 70 | #endif 71 | static inline T *vsh_aligned_malloc(size_t size, size_t alignment) { 72 | #ifdef _WIN32 73 | return (T *)_aligned_malloc(size, alignment); 74 | #else 75 | void *tmp = NULL; 76 | if (posix_memalign(&tmp, alignment, size)) 77 | tmp = 0; 78 | return (T *)tmp; 79 | #endif 80 | } 81 | 82 | static inline void vsh_aligned_free(void *ptr) { 83 | VSH_ALIGNED_FREE(ptr); 84 | } 85 | #endif /* __cplusplus */ 86 | 87 | /* convenience function for checking if the format never changes between frames */ 88 | static inline VSH4_BOOLEAN_TYPE VSH4_MANGLE_FUNCTION_NAME(isConstantVideoFormat)(const VSVideoInfo *vi) { 89 | return vi->height > 0 && vi->width > 0 && vi->format.colorFamily != cfUndefined; 90 | } 91 | 92 | /* convenience function to check for if two clips have the same format (unknown/changeable will be considered the same too) */ 93 | static inline VSH4_BOOLEAN_TYPE VSH4_MANGLE_FUNCTION_NAME(isSameVideoFormat)(const VSVideoFormat *v1, const VSVideoFormat *v2) { 94 | return v1->colorFamily == v2->colorFamily && v1->sampleType == v2->sampleType && v1->bitsPerSample == v2->bitsPerSample && v1->subSamplingW == v2->subSamplingW && v1->subSamplingH == v2->subSamplingH; 95 | } 96 | 97 | /* convenience function to check for if two clips have the same format (but not framerate) while also including width and height (unknown/changeable will be considered the same too) */ 98 | static inline VSH4_BOOLEAN_TYPE VSH4_MANGLE_FUNCTION_NAME(isSameVideoInfo)(const VSVideoInfo *v1, const VSVideoInfo *v2) { 99 | return v1->height == v2->height && v1->width == v2->width && VSH4_MANGLE_FUNCTION_NAME(isSameVideoFormat)(&v1->format, &v2->format); 100 | } 101 | 102 | /* convenience function to check for if two clips have the same format while also including samplerate (unknown/changeable will be considered the same too) */ 103 | static inline VSH4_BOOLEAN_TYPE VSH4_MANGLE_FUNCTION_NAME(isSameAudioFormat)(const VSAudioFormat *a1, const VSAudioFormat *a2) { 104 | return a1->bitsPerSample == a2->bitsPerSample && a1->sampleType == a2->sampleType && a1->channelLayout == a2->channelLayout; 105 | } 106 | 107 | /* convenience function to check for if two clips have the same format while also including samplerate (unknown/changeable will be considered the same too) */ 108 | static inline VSH4_BOOLEAN_TYPE VSH4_MANGLE_FUNCTION_NAME(isSameAudioInfo)(const VSAudioInfo *a1, const VSAudioInfo *a2) { 109 | return a1->sampleRate == a2->sampleRate && VSH4_MANGLE_FUNCTION_NAME(isSameAudioFormat)(&a1->format, &a2->format); 110 | } 111 | 112 | /* multiplies and divides a rational number, such as a frame duration, in place and reduces the result */ 113 | static inline void VSH4_MANGLE_FUNCTION_NAME(muldivRational)(int64_t *num, int64_t *den, int64_t mul, int64_t div) { 114 | /* do nothing if the rational number is invalid */ 115 | if (!*den) 116 | return; 117 | 118 | /* nobody wants to accidentally divide by zero */ 119 | assert(div); 120 | 121 | int64_t a, b; 122 | *num *= mul; 123 | *den *= div; 124 | a = *num; 125 | b = *den; 126 | while (b != 0) { 127 | int64_t t = a; 128 | a = b; 129 | b = t % b; 130 | } 131 | if (a < 0) 132 | a = -a; 133 | *num /= a; 134 | *den /= a; 135 | } 136 | 137 | /* reduces a rational number */ 138 | static inline void VSH4_MANGLE_FUNCTION_NAME(reduceRational)(int64_t *num, int64_t *den) { 139 | VSH4_MANGLE_FUNCTION_NAME(muldivRational)(num, den, 1, 1); 140 | } 141 | 142 | /* add two rational numbers and reduces the result */ 143 | static inline void VSH4_MANGLE_FUNCTION_NAME(addRational)(int64_t *num, int64_t *den, int64_t addnum, int64_t addden) { 144 | /* do nothing if the rational number is invalid */ 145 | if (!*den) 146 | return; 147 | 148 | /* nobody wants to accidentally add an invalid rational number */ 149 | assert(addden); 150 | 151 | if (*den == addden) { 152 | *num += addnum; 153 | } else { 154 | int64_t temp = addden; 155 | addnum *= *den; 156 | addden *= *den; 157 | *num *= temp; 158 | *den *= temp; 159 | 160 | *num += addnum; 161 | 162 | VSH4_MANGLE_FUNCTION_NAME(reduceRational)(num, den); 163 | } 164 | } 165 | 166 | /* converts an int64 to int with saturation, useful to silence warnings when reading int properties among other things */ 167 | static inline int VSH4_MANGLE_FUNCTION_NAME(int64ToIntS)(int64_t i) { 168 | if (i > INT_MAX) 169 | return INT_MAX; 170 | else if (i < INT_MIN) 171 | return INT_MIN; 172 | else return (int)i; 173 | } 174 | 175 | /* converts a double to float with saturation, useful to silence warnings when reading float properties among other things */ 176 | static inline float VSH4_MANGLE_FUNCTION_NAME(doubleToFloatS)(double d) { 177 | if (!isfinite(d)) 178 | return (float)d; 179 | else if (d > FLT_MAX) 180 | return FLT_MAX; 181 | else if (d < -FLT_MAX) 182 | return -FLT_MAX; 183 | else 184 | return (float)d; 185 | } 186 | 187 | static inline void VSH4_MANGLE_FUNCTION_NAME(bitblt)(void *dstp, ptrdiff_t dst_stride, const void *srcp, ptrdiff_t src_stride, size_t row_size, size_t height) { 188 | if (height) { 189 | if (src_stride == dst_stride && src_stride == (ptrdiff_t)row_size) { 190 | memcpy(dstp, srcp, row_size * height); 191 | } else { 192 | const uint8_t *srcp8 = (const uint8_t *)srcp; 193 | uint8_t *dstp8 = (uint8_t *)dstp; 194 | size_t i; 195 | for (i = 0; i < height; i++) { 196 | memcpy(dstp8, srcp8, row_size); 197 | srcp8 += src_stride; 198 | dstp8 += dst_stride; 199 | } 200 | } 201 | } 202 | } 203 | 204 | /* check if the frame dimensions are valid for a given format */ 205 | /* returns non-zero for valid width and height */ 206 | static inline VSH4_BOOLEAN_TYPE VSH4_MANGLE_FUNCTION_NAME(areValidDimensions)(const VSVideoFormat *fi, int width, int height) { 207 | return !(width % (1 << fi->subSamplingW) || height % (1 << fi->subSamplingH)); 208 | } 209 | 210 | /* Visual Studio doesn't recognize inline in c mode */ 211 | #if defined(_MSC_VER) && !defined(__cplusplus) 212 | #undef inline 213 | #endif 214 | 215 | #ifdef __cplusplus 216 | } 217 | #endif 218 | 219 | #endif 220 | -------------------------------------------------------------------------------- /src/vapoursynth/vapoursynth.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2017 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include "ffms.h" 22 | #include "vapoursource.h" 23 | #include "VSHelper.h" 24 | #include "../core/utils.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | static void VS_CC CreateIndex(const VSMap *in, VSMap *out, void *, VSCore *, const VSAPI *vsapi) { 32 | FFMS_Init(0, 0); 33 | 34 | char ErrorMsg[1024]; 35 | FFMS_ErrorInfo E; 36 | E.Buffer = ErrorMsg; 37 | E.BufferSize = sizeof(ErrorMsg); 38 | int err; 39 | 40 | std::set IndexTracks; 41 | 42 | const char *Source = vsapi->propGetData(in, "source", 0, nullptr); 43 | const char *CacheFile = vsapi->propGetData(in, "cachefile", 0, &err); 44 | 45 | int NumIndexTracks = vsapi->propNumElements(in, "indextracks"); 46 | bool IndexAllTracks = (NumIndexTracks == 1) && (int64ToIntS(vsapi->propGetInt(in, "indextracks", 0, nullptr)) == -1); 47 | if (!IndexAllTracks) { 48 | for (int i = 0; i < NumIndexTracks; i++) { 49 | int Track = int64ToIntS(vsapi->propGetInt(in, "indextracks", i, nullptr)); 50 | IndexTracks.insert(Track); 51 | } 52 | } 53 | 54 | int ErrorHandling = int64ToIntS(vsapi->propGetInt(in, "errorhandling", 0, &err)); 55 | if (err) 56 | ErrorHandling = FFMS_IEH_IGNORE; 57 | bool OverWrite = !!vsapi->propGetInt(in, "overwrite", 0, &err); 58 | 59 | std::string DefaultCache(Source); 60 | if (!CacheFile || !strcmp(CacheFile, "")) { 61 | DefaultCache.append(".ffindex"); 62 | CacheFile = DefaultCache.c_str(); 63 | } 64 | 65 | bool EnableDrefs = !!vsapi->propGetInt(in, "enable_drefs", 0, &err); 66 | bool UseAbsolutePath = !!vsapi->propGetInt(in, "use_absolute_path", 0, &err); 67 | 68 | FFMS_Index *Index = FFMS_ReadIndex(CacheFile, &E); 69 | if (OverWrite || !Index || (Index && FFMS_IndexBelongsToFile(Index, Source, nullptr) != FFMS_ERROR_SUCCESS)) { 70 | FFMS_KeyValuePair LAVFOpts[] = {{ "enable_drefs", EnableDrefs ? "1" : "0" }, { "use_absolute_path", UseAbsolutePath ? "1" : "0" }}; 71 | FFMS_Indexer *Indexer = FFMS_CreateIndexer2(Source, LAVFOpts, 2, &E); 72 | if (!Indexer) { 73 | FFMS_DestroyIndex(Index); 74 | return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str()); 75 | } 76 | 77 | if (IndexAllTracks) { 78 | FFMS_TrackTypeIndexSettings(Indexer, FFMS_TYPE_AUDIO, 1, 0); 79 | } else { 80 | for (int i : IndexTracks) 81 | FFMS_TrackIndexSettings(Indexer, i, 1, 0); 82 | } 83 | 84 | Index = FFMS_DoIndexing2(Indexer, ErrorHandling, &E); 85 | if (!Index) 86 | return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str()); 87 | if (FFMS_WriteIndex(CacheFile, Index, &E)) { 88 | FFMS_DestroyIndex(Index); 89 | return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str()); 90 | } 91 | FFMS_DestroyIndex(Index); 92 | if (!OverWrite) 93 | vsapi->propSetData(out, "result", "Index generated", -1, paReplace); 94 | else 95 | vsapi->propSetData(out, "result", "Index generated (forced overwrite)", -1, paReplace); 96 | } else { 97 | FFMS_DestroyIndex(Index); 98 | vsapi->propSetData(out, "result", "Valid index already exists", -1, paReplace); 99 | } 100 | FFMS_Deinit(); 101 | } 102 | 103 | static void VS_CC CreateSource(const VSMap *in, VSMap *out, void *, VSCore *core, const VSAPI *vsapi) { 104 | FFMS_Init(0, 0); 105 | 106 | char ErrorMsg[1024]; 107 | FFMS_ErrorInfo E; 108 | E.Buffer = ErrorMsg; 109 | E.BufferSize = sizeof(ErrorMsg); 110 | int err; 111 | 112 | const char *Source = vsapi->propGetData(in, "source", 0, nullptr); 113 | int Track = int64ToIntS(vsapi->propGetInt(in, "track", 0, &err)); 114 | if (err) 115 | Track = -1; 116 | bool Cache = !!vsapi->propGetInt(in, "cache", 0, &err); 117 | if (err) 118 | Cache = true; 119 | const char *CacheFile = vsapi->propGetData(in, "cachefile", 0, &err); 120 | int FPSNum = int64ToIntS(vsapi->propGetInt(in, "fpsnum", 0, &err)); 121 | if (err) 122 | FPSNum = -1; 123 | int FPSDen = int64ToIntS(vsapi->propGetInt(in, "fpsden", 0, &err)); 124 | if (err) 125 | FPSDen = 1; 126 | int Threads = int64ToIntS(vsapi->propGetInt(in, "threads", 0, &err)); 127 | const char *Timecodes = vsapi->propGetData(in, "timecodes", 0, &err); 128 | int SeekMode = int64ToIntS(vsapi->propGetInt(in, "seekmode", 0, &err)); 129 | if (err) 130 | SeekMode = FFMS_SEEK_NORMAL; 131 | int RFFMode = int64ToIntS(vsapi->propGetInt(in, "rffmode", 0, &err)); 132 | int Width = int64ToIntS(vsapi->propGetInt(in, "width", 0, &err)); 133 | int Height = int64ToIntS(vsapi->propGetInt(in, "height", 0, &err)); 134 | const char *Resizer = vsapi->propGetData(in, "resizer", 0, &err); 135 | if (err) 136 | Resizer = "BICUBIC"; 137 | int Format = int64ToIntS(vsapi->propGetInt(in, "format", 0, &err)); 138 | 139 | bool OutputAlpha = !!vsapi->propGetInt(in, "alpha", 0, &err); 140 | 141 | if (FPSDen < 1) 142 | return vsapi->setError(out, "Source: FPS denominator needs to be 1 or higher"); 143 | if (Track <= -2) 144 | return vsapi->setError(out, "Source: No video track selected"); 145 | if (SeekMode < -1 || SeekMode > 3) 146 | return vsapi->setError(out, "Source: Invalid seekmode selected"); 147 | if (RFFMode < 0 || RFFMode > 2) 148 | return vsapi->setError(out, "Source: Invalid RFF mode selected"); 149 | if (RFFMode > 0 && FPSNum > 0) 150 | return vsapi->setError(out, "Source: RFF modes may not be combined with CFR conversion"); 151 | if (Timecodes && IsSamePath(Source, Timecodes)) 152 | return vsapi->setError(out, "Source: Timecodes will overwrite the source"); 153 | 154 | FFMS_Index *Index = nullptr; 155 | std::string DefaultCache; 156 | if (Cache) { 157 | if (CacheFile && *CacheFile) { 158 | if (IsSamePath(Source, CacheFile)) 159 | return vsapi->setError(out, "Source: Cache will overwrite the source"); 160 | Index = FFMS_ReadIndex(CacheFile, &E); 161 | } else { 162 | DefaultCache = Source; 163 | DefaultCache += ".ffindex"; 164 | CacheFile = DefaultCache.c_str(); 165 | Index = FFMS_ReadIndex(CacheFile, &E); 166 | // Reindex if the index doesn't match the file and its name wasn't 167 | // explicitly given 168 | if (Index && FFMS_IndexBelongsToFile(Index, Source, nullptr) != FFMS_ERROR_SUCCESS) { 169 | FFMS_DestroyIndex(Index); 170 | Index = nullptr; 171 | } 172 | } 173 | } 174 | 175 | if (!Index) { 176 | FFMS_Indexer *Indexer = FFMS_CreateIndexer(Source, &E); 177 | if (!Indexer) 178 | return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str()); 179 | 180 | Index = FFMS_DoIndexing2(Indexer, FFMS_IEH_CLEAR_TRACK, &E); 181 | if (!Index) 182 | return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str()); 183 | 184 | if (Cache) 185 | if (FFMS_WriteIndex(CacheFile, Index, &E)) { 186 | FFMS_DestroyIndex(Index); 187 | return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str()); 188 | } 189 | } 190 | 191 | if (Track == -1) 192 | Track = FFMS_GetFirstIndexedTrackOfType(Index, FFMS_TYPE_VIDEO, &E); 193 | if (Track < 0) { 194 | FFMS_DestroyIndex(Index); 195 | return vsapi->setError(out, "Source: No video track found"); 196 | } 197 | 198 | if (Timecodes && strcmp(Timecodes, "")) { 199 | if (FFMS_WriteTimecodes(FFMS_GetTrackFromIndex(Index, Track), Timecodes, &E)) { 200 | FFMS_DestroyIndex(Index); 201 | return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str()); 202 | } 203 | } 204 | 205 | VSVideoSource *vs; 206 | try { 207 | vs = new VSVideoSource(Source, Track, Index, FPSNum, FPSDen, Threads, SeekMode, RFFMode, Width, Height, Resizer, Format, OutputAlpha, vsapi, core); 208 | } catch (std::exception const& e) { 209 | FFMS_DestroyIndex(Index); 210 | return vsapi->setError(out, e.what()); 211 | } 212 | 213 | vsapi->createFilter(in, out, "Source", VSVideoSource::Init, VSVideoSource::GetFrame, VSVideoSource::Free, fmUnordered, nfMakeLinear, vs, core); 214 | 215 | FFMS_DestroyIndex(Index); 216 | } 217 | 218 | static void VS_CC GetLogLevel(const VSMap *, VSMap *out, void *, VSCore *, const VSAPI *vsapi) { 219 | vsapi->propSetInt(out, "level", FFMS_GetLogLevel(), paReplace); 220 | } 221 | 222 | static void VS_CC SetLogLevel(const VSMap *in, VSMap *out, void *, VSCore *, const VSAPI *vsapi) { 223 | FFMS_SetLogLevel((int)vsapi->propGetInt(in, "level", 0, nullptr)); 224 | vsapi->propSetInt(out, "level", FFMS_GetLogLevel(), paReplace); 225 | } 226 | 227 | static void VS_CC GetVersion(const VSMap *, VSMap *out, void *, VSCore *, const VSAPI *vsapi) { 228 | int Version = FFMS_GetVersion(); 229 | char buf[100]; 230 | sprintf(buf, "%d.%d.%d.%d", Version >> 24, (Version & 0xFF0000) >> 16, (Version & 0xFF00) >> 8, Version & 0xFF); 231 | vsapi->propSetData(out, "version", buf, -1, paReplace); 232 | } 233 | 234 | VS_EXTERNAL_API(void) VapourSynthPluginInit(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin *plugin) { 235 | configFunc("com.vapoursynth.ffms2", "ffms2", "FFmpegSource 2 for VapourSynth", VAPOURSYNTH_API_VERSION, 1, plugin); 236 | registerFunc("Index", "source:data;cachefile:data:opt;indextracks:int[]:opt;errorhandling:int:opt;overwrite:int:opt;enable_drefs:int:opt;use_absolute_path:int:opt;", CreateIndex, nullptr, plugin); 237 | registerFunc("Source", "source:data;track:int:opt;cache:int:opt;cachefile:data:opt;fpsnum:int:opt;fpsden:int:opt;threads:int:opt;timecodes:data:opt;seekmode:int:opt;width:int:opt;height:int:opt;resizer:data:opt;format:int:opt;alpha:int:opt;", CreateSource, nullptr, plugin); 238 | registerFunc("GetLogLevel", "", GetLogLevel, nullptr, plugin); 239 | registerFunc("SetLogLevel", "level:int;", SetLogLevel, nullptr, plugin); 240 | registerFunc("Version", "", GetVersion, nullptr, plugin); 241 | } 242 | -------------------------------------------------------------------------------- /src/vapoursynth/vapoursynth4.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2017 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include "ffms.h" 22 | #include "vapoursource4.h" 23 | #include "VSHelper4.h" 24 | #include "../core/utils.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | static void VS_CC CreateIndex(const VSMap *in, VSMap *out, void *, VSCore *, const VSAPI *vsapi) { 32 | FFMS_Init(0, 0); 33 | 34 | char ErrorMsg[1024]; 35 | FFMS_ErrorInfo E; 36 | E.Buffer = ErrorMsg; 37 | E.BufferSize = sizeof(ErrorMsg); 38 | int err; 39 | 40 | std::set IndexTracks; 41 | 42 | const char *Source = vsapi->mapGetData(in, "source", 0, nullptr); 43 | const char *CacheFile = vsapi->mapGetData(in, "cachefile", 0, &err); 44 | 45 | int NumIndexTracks = vsapi->mapNumElements(in, "indextracks"); 46 | bool IndexAllTracks = (NumIndexTracks == 1) && (vsapi->mapGetIntSaturated(in, "indextracks", 0, nullptr) == -1); 47 | if (!IndexAllTracks) { 48 | for (int i = 0; i < NumIndexTracks; i++) { 49 | int Track = vsapi->mapGetIntSaturated(in, "indextracks", i, nullptr); 50 | IndexTracks.insert(Track); 51 | } 52 | } 53 | 54 | int ErrorHandling = vsapi->mapGetIntSaturated(in, "errorhandling", 0, &err); 55 | if (err) 56 | ErrorHandling = FFMS_IEH_IGNORE; 57 | bool OverWrite = !!vsapi->mapGetInt(in, "overwrite", 0, &err); 58 | 59 | std::string DefaultCache(Source); 60 | if (!CacheFile || !strcmp(CacheFile, "")) { 61 | DefaultCache.append(".ffindex"); 62 | CacheFile = DefaultCache.c_str(); 63 | } 64 | 65 | bool EnableDrefs = !!vsapi->mapGetInt(in, "enable_drefs", 0, &err); 66 | bool UseAbsolutePath = !!vsapi->mapGetInt(in, "use_absolute_path", 0, &err); 67 | 68 | FFMS_Index *Index = FFMS_ReadIndex(CacheFile, &E); 69 | if (OverWrite || !Index || (Index && FFMS_IndexBelongsToFile(Index, Source, nullptr) != FFMS_ERROR_SUCCESS)) { 70 | FFMS_KeyValuePair LAVFOpts[] = {{ "enable_drefs", EnableDrefs ? "1" : "0" }, { "use_absolute_path", UseAbsolutePath ? "1" : "0" }}; 71 | FFMS_Indexer *Indexer = FFMS_CreateIndexer2(Source, LAVFOpts, 2, &E); 72 | if (!Indexer) { 73 | FFMS_DestroyIndex(Index); 74 | return vsapi->mapSetError(out, (std::string("Index: ") + E.Buffer).c_str()); 75 | } 76 | 77 | if (IndexAllTracks) { 78 | FFMS_TrackTypeIndexSettings(Indexer, FFMS_TYPE_AUDIO, 1, 0); 79 | } else { 80 | for (int i : IndexTracks) 81 | FFMS_TrackIndexSettings(Indexer, i, 1, 0); 82 | } 83 | 84 | Index = FFMS_DoIndexing2(Indexer, ErrorHandling, &E); 85 | if (!Index) 86 | return vsapi->mapSetError(out, (std::string("Index: ") + E.Buffer).c_str()); 87 | if (FFMS_WriteIndex(CacheFile, Index, &E)) { 88 | FFMS_DestroyIndex(Index); 89 | return vsapi->mapSetError(out, (std::string("Index: ") + E.Buffer).c_str()); 90 | } 91 | FFMS_DestroyIndex(Index); 92 | if (!OverWrite) 93 | vsapi->mapSetData(out, "result", "Index generated", -1, dtUtf8, maReplace); 94 | else 95 | vsapi->mapSetData(out, "result", "Index generated (forced overwrite)", -1, dtUtf8, maReplace); 96 | } else { 97 | FFMS_DestroyIndex(Index); 98 | vsapi->mapSetData(out, "result", "Valid index already exists", -1, dtUtf8, maReplace); 99 | } 100 | FFMS_Deinit(); 101 | } 102 | 103 | static void VS_CC CreateSource(const VSMap *in, VSMap *out, void *, VSCore *core, const VSAPI *vsapi) { 104 | FFMS_Init(0, 0); 105 | 106 | char ErrorMsg[1024]; 107 | FFMS_ErrorInfo E; 108 | E.Buffer = ErrorMsg; 109 | E.BufferSize = sizeof(ErrorMsg); 110 | int err; 111 | 112 | const char *Source = vsapi->mapGetData(in, "source", 0, nullptr); 113 | int Track = vsapi->mapGetIntSaturated(in, "track", 0, &err); 114 | if (err) 115 | Track = -1; 116 | bool Cache = !!vsapi->mapGetInt(in, "cache", 0, &err); 117 | if (err) 118 | Cache = true; 119 | const char *CacheFile = vsapi->mapGetData(in, "cachefile", 0, &err); 120 | int FPSNum = vsapi->mapGetIntSaturated(in, "fpsnum", 0, &err); 121 | if (err) 122 | FPSNum = -1; 123 | int FPSDen = vsapi->mapGetIntSaturated(in, "fpsden", 0, &err); 124 | if (err) 125 | FPSDen = 1; 126 | int Threads = vsapi->mapGetIntSaturated(in, "threads", 0, &err); 127 | const char *Timecodes = vsapi->mapGetData(in, "timecodes", 0, &err); 128 | int SeekMode = vsapi->mapGetIntSaturated(in, "seekmode", 0, &err); 129 | if (err) 130 | SeekMode = FFMS_SEEK_NORMAL; 131 | int RFFMode = vsapi->mapGetIntSaturated(in, "rffmode", 0, &err); 132 | int Width = vsapi->mapGetIntSaturated(in, "width", 0, &err); 133 | int Height = vsapi->mapGetIntSaturated(in, "height", 0, &err); 134 | const char *Resizer = vsapi->mapGetData(in, "resizer", 0, &err); 135 | if (err) 136 | Resizer = "BICUBIC"; 137 | int Format = vsapi->mapGetIntSaturated(in, "format", 0, &err); 138 | 139 | bool OutputAlpha = !!vsapi->mapGetInt(in, "alpha", 0, &err); 140 | 141 | if (FPSDen < 1) 142 | return vsapi->mapSetError(out, "Source: FPS denominator needs to be 1 or higher"); 143 | if (Track <= -2) 144 | return vsapi->mapSetError(out, "Source: No video track selected"); 145 | if (SeekMode < -1 || SeekMode > 3) 146 | return vsapi->mapSetError(out, "Source: Invalid seekmode selected"); 147 | if (RFFMode < 0 || RFFMode > 2) 148 | return vsapi->mapSetError(out, "Source: Invalid RFF mode selected"); 149 | if (RFFMode > 0 && FPSNum > 0) 150 | return vsapi->mapSetError(out, "Source: RFF modes may not be combined with CFR conversion"); 151 | if (Timecodes && IsSamePath(Source, Timecodes)) 152 | return vsapi->mapSetError(out, "Source: Timecodes will overwrite the source"); 153 | 154 | FFMS_Index *Index = nullptr; 155 | std::string DefaultCache; 156 | if (Cache) { 157 | if (CacheFile && *CacheFile) { 158 | if (IsSamePath(Source, CacheFile)) 159 | return vsapi->mapSetError(out, "Source: Cache will overwrite the source"); 160 | Index = FFMS_ReadIndex(CacheFile, &E); 161 | } else { 162 | DefaultCache = Source; 163 | DefaultCache += ".ffindex"; 164 | CacheFile = DefaultCache.c_str(); 165 | Index = FFMS_ReadIndex(CacheFile, &E); 166 | // Reindex if the index doesn't match the file and its name wasn't 167 | // explicitly given 168 | if (Index && FFMS_IndexBelongsToFile(Index, Source, nullptr) != FFMS_ERROR_SUCCESS) { 169 | FFMS_DestroyIndex(Index); 170 | Index = nullptr; 171 | } 172 | } 173 | } 174 | 175 | if (!Index) { 176 | FFMS_Indexer *Indexer = FFMS_CreateIndexer(Source, &E); 177 | if (!Indexer) 178 | return vsapi->mapSetError(out, (std::string("Index: ") + E.Buffer).c_str()); 179 | 180 | Index = FFMS_DoIndexing2(Indexer, FFMS_IEH_CLEAR_TRACK, &E); 181 | if (!Index) 182 | return vsapi->mapSetError(out, (std::string("Index: ") + E.Buffer).c_str()); 183 | 184 | if (Cache) 185 | if (FFMS_WriteIndex(CacheFile, Index, &E)) { 186 | FFMS_DestroyIndex(Index); 187 | return vsapi->mapSetError(out, (std::string("Index: ") + E.Buffer).c_str()); 188 | } 189 | } 190 | 191 | if (Track == -1) 192 | Track = FFMS_GetFirstIndexedTrackOfType(Index, FFMS_TYPE_VIDEO, &E); 193 | if (Track < 0) { 194 | FFMS_DestroyIndex(Index); 195 | return vsapi->mapSetError(out, "Source: No video track found"); 196 | } 197 | 198 | if (Timecodes && strcmp(Timecodes, "")) { 199 | if (FFMS_WriteTimecodes(FFMS_GetTrackFromIndex(Index, Track), Timecodes, &E)) { 200 | FFMS_DestroyIndex(Index); 201 | return vsapi->mapSetError(out, (std::string("Index: ") + E.Buffer).c_str()); 202 | } 203 | } 204 | 205 | VSVideoSource4 *vs; 206 | try { 207 | vs = new VSVideoSource4(Source, Track, Index, FPSNum, FPSDen, Threads, SeekMode, RFFMode, Width, Height, Resizer, Format, OutputAlpha, vsapi, core); 208 | } catch (std::exception const& e) { 209 | FFMS_DestroyIndex(Index); 210 | return vsapi->mapSetError(out, e.what()); 211 | } 212 | 213 | VSNode *node = vsapi->createVideoFilter2("Source", vs->GetVideoInfo(), VSVideoSource4::GetFrame, VSVideoSource4::Free, fmUnordered, nullptr, 0, vs, core); 214 | vs->SetCacheThreshold(vsapi->setLinearFilter(node)); 215 | vsapi->mapConsumeNode(out, "clip", node, maAppend); 216 | 217 | FFMS_DestroyIndex(Index); 218 | } 219 | 220 | static void VS_CC GetLogLevel(const VSMap *, VSMap *out, void *, VSCore *, const VSAPI *vsapi) { 221 | vsapi->mapSetInt(out, "level", FFMS_GetLogLevel(), maReplace); 222 | } 223 | 224 | static void VS_CC SetLogLevel(const VSMap *in, VSMap *out, void *, VSCore *, const VSAPI *vsapi) { 225 | FFMS_SetLogLevel(vsapi->mapGetIntSaturated(in, "level", 0, nullptr)); 226 | vsapi->mapSetInt(out, "level", FFMS_GetLogLevel(), maReplace); 227 | } 228 | 229 | static void VS_CC GetVersion(const VSMap *, VSMap *out, void *, VSCore *, const VSAPI *vsapi) { 230 | int Version = FFMS_GetVersion(); 231 | char buf[100]; 232 | sprintf(buf, "%d.%d.%d.%d", Version >> 24, (Version & 0xFF0000) >> 16, (Version & 0xFF00) >> 8, Version & 0xFF); 233 | vsapi->mapSetData(out, "version", buf, -1, maReplace, dtUtf8); 234 | } 235 | 236 | VS_EXTERNAL_API(void) VapourSynthPluginInit2(VSPlugin *plugin, const VSPLUGINAPI *vspapi) { 237 | vspapi->configPlugin("com.vapoursynth.ffms2", "ffms2", "FFmpegSource 2 for VapourSynth", FFMS_GetVersion(), VAPOURSYNTH_API_VERSION, 0, plugin); 238 | vspapi->registerFunction("Index", "source:data;cachefile:data:opt;indextracks:int[]:opt;errorhandling:int:opt;overwrite:int:opt;enable_drefs:int:opt;use_absolute_path:int:opt;", "result:data;", CreateIndex, nullptr, plugin); 239 | vspapi->registerFunction("Source", "source:data;track:int:opt;cache:int:opt;cachefile:data:opt;fpsnum:int:opt;fpsden:int:opt;threads:int:opt;timecodes:data:opt;seekmode:int:opt;width:int:opt;height:int:opt;resizer:data:opt;format:int:opt;alpha:int:opt;", "clip:vnode;", CreateSource, nullptr, plugin); 240 | vspapi->registerFunction("GetLogLevel", "", "level:int;", GetLogLevel, nullptr, plugin); 241 | vspapi->registerFunction("SetLogLevel", "level:int;", "level:int;", SetLogLevel, nullptr, plugin); 242 | vspapi->registerFunction("Version", "", "version:data;", GetVersion, nullptr, plugin); 243 | } 244 | -------------------------------------------------------------------------------- /src/core/videoutils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007-2015 The FFmpegSource Project 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | 22 | #include "videoutils.h" 23 | 24 | #include 25 | #include 26 | 27 | /* if you have this, we'll assume you have a new enough libavutil too */ 28 | extern "C" { 29 | #include 30 | } 31 | 32 | SwsContext *GetSwsContext(int SrcW, int SrcH, AVPixelFormat SrcFormat, int SrcColorSpace, int SrcColorRange, int DstW, int DstH, AVPixelFormat DstFormat, int DstColorSpace, int DstColorRange, int64_t Flags) { 33 | Flags |= SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP | SWS_ACCURATE_RND; 34 | SwsContext *Context = sws_alloc_context(); 35 | if (!Context) return nullptr; 36 | 37 | // 0 = limited range, 1 = full range 38 | int SrcRange = SrcColorRange == AVCOL_RANGE_JPEG; 39 | if (SrcColorRange == AVCOL_RANGE_UNSPECIFIED) 40 | SrcRange = (GuessCSType(SrcFormat) != cYUV); 41 | int DstRange = DstColorRange == AVCOL_RANGE_JPEG; 42 | if (DstColorRange == AVCOL_RANGE_UNSPECIFIED) 43 | DstRange = (GuessCSType(DstFormat) != cYUV); 44 | 45 | av_opt_set_int(Context, "sws_flags", Flags, 0); 46 | av_opt_set_int(Context, "srcw", SrcW, 0); 47 | av_opt_set_int(Context, "srch", SrcH, 0); 48 | av_opt_set_int(Context, "dstw", DstW, 0); 49 | av_opt_set_int(Context, "dsth", DstH, 0); 50 | av_opt_set_int(Context, "src_range", SrcRange, 0); 51 | av_opt_set_int(Context, "dst_range", DstRange, 0); 52 | av_opt_set_int(Context, "src_format", SrcFormat, 0); 53 | av_opt_set_int(Context, "dst_format", DstFormat, 0); 54 | 55 | sws_setColorspaceDetails(Context, 56 | sws_getCoefficients(SrcColorSpace), SrcRange, 57 | sws_getCoefficients(DstColorSpace), DstRange, 58 | 0, 1 << 16, 1 << 16); 59 | 60 | if (sws_init_context(Context, nullptr, nullptr) < 0) { 61 | sws_freeContext(Context); 62 | return nullptr; 63 | } 64 | 65 | return Context; 66 | } 67 | 68 | /*************************** 69 | ** 70 | ** Two functions for making FFMS pretend it's not quite as VFR-based as it really is. 71 | ** 72 | ***************************/ 73 | 74 | 75 | // attempt to correct framerate to a common fraction if close to one 76 | void CorrectRationalFramerate(int *Num, int *Den) { 77 | // Make sure fps is a normalized rational number 78 | av_reduce(Den, Num, *Den, *Num, INT_MAX); 79 | 80 | const double fps = static_cast(*Num) / *Den; 81 | const int fpsList[] = { 24, 25, 30, 48, 50, 60, 100, 120 }; 82 | 83 | for (size_t i = 0; i < sizeof(fpsList) / sizeof(fpsList[0]); i++) { 84 | const double delta = (fpsList[i] - static_cast(fpsList[i]) / 1.001) / 2.0; 85 | if (fabs(fps - fpsList[i]) < delta) { 86 | *Num = fpsList[i]; 87 | *Den = 1; 88 | break; 89 | } else if ((fpsList[i] % 25) && (fabs(fps - static_cast(fpsList[i]) / 1.001) < delta)) { 90 | *Num = fpsList[i] * 1000; 91 | *Den = 1001; 92 | break; 93 | } 94 | } 95 | } 96 | 97 | // correct the timebase if it is invalid 98 | void CorrectTimebase(FFMS_VideoProperties *VP, FFMS_TrackTimeBase *TTimebase) { 99 | double Timebase = (double)TTimebase->Num / TTimebase->Den; 100 | double FPS = (double)VP->FPSNumerator / VP->FPSDenominator; 101 | if ((1000 / Timebase) / FPS < 1) { 102 | TTimebase->Den = VP->FPSNumerator; 103 | TTimebase->Num = (int64_t)VP->FPSDenominator * 1000; 104 | } 105 | } 106 | 107 | /*************************** 108 | ** 109 | ** Since avcodec_find_best_pix_fmt() is broken, we have our own implementation of it here. 110 | ** 111 | ***************************/ 112 | 113 | BCSType GuessCSType(AVPixelFormat p) { 114 | // guessing the colorspace type from the name is kinda hackish but libav doesn't export this kind of metadata 115 | // special case since pal8 is assumed to be rgb32 116 | if (p == AV_PIX_FMT_PAL8) 117 | return cRGB; 118 | if (av_pix_fmt_desc_get(p)->flags & AV_PIX_FMT_FLAG_HWACCEL) 119 | return cUNUSABLE; 120 | if (av_pix_fmt_desc_get(p)->flags & AV_PIX_FMT_FLAG_PAL) 121 | return cUNUSABLE; 122 | if (av_pix_fmt_desc_get(p)->flags & AV_PIX_FMT_FLAG_RGB) 123 | return cRGB; 124 | if (av_pix_fmt_desc_get(p)->nb_components <= 2) 125 | return cGRAY; 126 | if (av_pix_fmt_desc_get(p)->nb_components >= 3) 127 | return cYUV; 128 | return cUNUSABLE; // should never come here 129 | } 130 | 131 | struct LossAttributes { 132 | AVPixelFormat Format; 133 | int ChromaUndersampling; 134 | int ChromaOversampling; 135 | int DepthDifference; 136 | int CSLoss; // 0 = no difference, 1 = unused, 2 = full conversion required, 3 = alpha loss, 4 = full conversion plus alpha loss, 5 = complete color loss 137 | int CSGain; // alpha plane added, gray converted to yuv/rgb 138 | }; 139 | 140 | static int GetPseudoDepth(const AVPixFmtDescriptor &Desc) { 141 | // Comparing the pseudo depth makes sure that rgb565-ish formats get selected over rgb555-ish ones 142 | int depth = 0; 143 | for (int i = 0; i < Desc.nb_components; i++) 144 | depth = FFMAX(depth, Desc.comp[i].depth); 145 | return depth; 146 | } 147 | 148 | static LossAttributes CalculateLoss(AVPixelFormat Dst, AVPixelFormat Src) { 149 | const AVPixFmtDescriptor &SrcDesc = *av_pix_fmt_desc_get(Src); 150 | const AVPixFmtDescriptor &DstDesc = *av_pix_fmt_desc_get(Dst); 151 | BCSType SrcCS = GuessCSType(Src); 152 | BCSType DstCS = GuessCSType(Dst); 153 | 154 | LossAttributes Loss; 155 | Loss.Format = Dst; 156 | Loss.DepthDifference = GetPseudoDepth(DstDesc) - GetPseudoDepth(SrcDesc);; 157 | Loss.ChromaOversampling = FFMAX(0, SrcDesc.log2_chroma_h - DstDesc.log2_chroma_h) + FFMAX(0, SrcDesc.log2_chroma_w - DstDesc.log2_chroma_w); 158 | Loss.ChromaUndersampling = FFMAX(0, DstDesc.log2_chroma_h - SrcDesc.log2_chroma_h) + FFMAX(0, DstDesc.log2_chroma_w - SrcDesc.log2_chroma_w); 159 | Loss.CSGain = 0; 160 | 161 | if (SrcCS == DstCS) { 162 | Loss.CSLoss = 0; 163 | } else if (SrcCS == cGRAY) { 164 | Loss.CSGain = 1; 165 | Loss.ChromaOversampling = 0; 166 | Loss.ChromaUndersampling = 0; 167 | Loss.CSLoss = 0; 168 | } else if (DstCS == cGRAY) { 169 | Loss.ChromaOversampling = 0; 170 | Loss.ChromaUndersampling = 0; 171 | Loss.CSLoss = 5; 172 | } else { 173 | Loss.CSLoss = 2; 174 | } 175 | 176 | if (Loss.CSLoss < 3 && (DstDesc.nb_components == SrcDesc.nb_components - 1)) { 177 | if (Loss.CSLoss == 2) 178 | Loss.CSLoss = 4; 179 | else 180 | Loss.CSLoss = 3; 181 | } 182 | 183 | // Added alpha plane 184 | if (Loss.CSLoss == 0 && (SrcDesc.nb_components == DstDesc.nb_components - 1)) { 185 | Loss.CSGain = 1; 186 | } 187 | 188 | return Loss; 189 | } 190 | 191 | AVPixelFormat FindBestPixelFormat(const std::vector &Dsts, AVPixelFormat Src) { 192 | // some trivial special cases to make sure there's as little conversion as possible 193 | if (Dsts.empty()) 194 | return AV_PIX_FMT_NONE; 195 | if (Dsts.size() == 1) 196 | return Dsts[0]; 197 | 198 | // is the input in the output? 199 | auto i = std::find(Dsts.begin(), Dsts.end(), Src); 200 | if (i != Dsts.end()) 201 | return Src; 202 | 203 | // If it's an evil paletted format pretend it's normal RGB when calculating loss 204 | if (Src == AV_PIX_FMT_PAL8) 205 | Src = AV_PIX_FMT_RGB32; 206 | 207 | std::vector OutputDsts; 208 | for (auto DstFmt = Dsts.begin(); DstFmt != Dsts.end(); ++DstFmt) { 209 | // Skip outputing to formwats we can't convert to, this avoids swscale init failures later in the code 210 | if (sws_isSupportedOutput(*DstFmt)) 211 | OutputDsts.push_back(*DstFmt); 212 | } 213 | 214 | if (OutputDsts.empty()) 215 | return AV_PIX_FMT_NONE; 216 | 217 | i = OutputDsts.begin(); 218 | LossAttributes Loss = CalculateLoss(*i++, Src); 219 | for (; i != OutputDsts.end(); ++i) { 220 | LossAttributes CLoss = CalculateLoss(*i, Src); 221 | if (Loss.CSLoss >= 3 && CLoss.CSLoss < Loss.CSLoss) { // favor the same color format output 222 | Loss = CLoss; 223 | } else if (Loss.DepthDifference >= 0 && CLoss.DepthDifference >= 0) { // focus on chroma undersamling and conversion loss if the target depth has been achieved 224 | if ((CLoss.ChromaUndersampling < Loss.ChromaUndersampling) 225 | || (CLoss.ChromaUndersampling == Loss.ChromaUndersampling && CLoss.CSLoss < Loss.CSLoss) 226 | || (CLoss.ChromaUndersampling == Loss.ChromaUndersampling && CLoss.CSLoss == Loss.CSLoss && CLoss.DepthDifference < Loss.DepthDifference) 227 | || (CLoss.ChromaUndersampling == Loss.ChromaUndersampling && CLoss.CSLoss == Loss.CSLoss 228 | && CLoss.DepthDifference == Loss.DepthDifference && CLoss.ChromaOversampling < Loss.ChromaOversampling)) 229 | Loss = CLoss; 230 | } else { // put priority on reaching the same depth as the input 231 | if ((CLoss.DepthDifference > Loss.DepthDifference) 232 | || (CLoss.DepthDifference == Loss.DepthDifference && CLoss.ChromaUndersampling < Loss.ChromaUndersampling) 233 | || (CLoss.DepthDifference == Loss.DepthDifference && CLoss.ChromaUndersampling == Loss.ChromaUndersampling && CLoss.CSLoss < Loss.CSLoss) 234 | || (CLoss.DepthDifference == Loss.DepthDifference && CLoss.ChromaUndersampling == Loss.ChromaUndersampling 235 | && CLoss.CSLoss == Loss.CSLoss && CLoss.ChromaOversampling < Loss.ChromaOversampling)) 236 | Loss = CLoss; 237 | else if (CLoss.DepthDifference == Loss.DepthDifference && CLoss.ChromaUndersampling == Loss.ChromaUndersampling 238 | && CLoss.CSLoss == Loss.CSLoss && CLoss.ChromaOversampling == Loss.ChromaOversampling && CLoss.CSGain < Loss.CSGain) 239 | Loss = CLoss; 240 | } 241 | } 242 | 243 | return Loss.Format; 244 | } 245 | 246 | void ParseVP8(const uint8_t Buf, bool *Invisible, int *PictType) { 247 | *PictType = (Buf & 0x01) ? AV_PICTURE_TYPE_P : AV_PICTURE_TYPE_I; 248 | *Invisible = (*Invisible || !(Buf & 0x10)); 249 | } 250 | 251 | void ParseVP9(const uint8_t Buf, bool *Invisible, int *PictType) 252 | { 253 | int profile = ((Buf & 0x20) >> 5) | ((Buf & 0x10) >> 3); 254 | int shift = (profile == 3); 255 | 256 | if (Buf & (0x8 >> shift)) 257 | *PictType = AV_PICTURE_TYPE_P; 258 | else 259 | *PictType = (Buf & (0x4 >> shift)) ? AV_PICTURE_TYPE_P : AV_PICTURE_TYPE_I; 260 | } 261 | -------------------------------------------------------------------------------- /doc/ffms2-vapoursynth.md: -------------------------------------------------------------------------------- 1 | # FFmpegSource2 VapourSynth User Manual 2 | 3 | Opens files using FFmpeg and (almost) nothing else. 4 | May be frame accurate on good days. 5 | The source is MIT licensed and can be obtained from https://github.com/FFMS/ffms2/. 6 | The precompiled binary is GPL3 licensed. 7 | If you are religious you may consider this the second coming. 8 | 9 | ## Donate 10 | 11 | Donate if you like this software. 12 | Collecting weird clips from the internet and making them play takes more time than you'd think. 13 | 14 | [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=fredrik%2emellbin%40gmail%2ecom&lc=US&item_name=IVTC%2eORG&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest) 15 | 16 | ## Limitations 17 | - No audio support in VapourSynth 18 | - Because of LAVF's demuxer, most raw streams (such as elementary h264 and other mpeg video streams) will fail to work properly. 19 | - Interlaced H.264 mostly works these days, but seeking may occasionally result in corruption. 20 | - Transport Streams will not decode reliably without seekmode -1. 21 | - Open-GOP H.264 will sometimes produces corruption when seeking. 22 | 23 | ## Compatibility 24 | 25 | - AVI, MKV, MP4, FLV: Frame accurate 26 | - WMV: Frame accurate(?) but avformat seems to pick keyframes relatively far away 27 | - OGM: Frame accurate(?) 28 | - VOB, MPG: Seeking seems to be off by one or two frames now and then 29 | - M2TS, TS: Seeking seems to be off a few frames here and there 30 | - Image files: Most formats can be opened if seekmode=-1 is set, no animation support 31 | 32 | ## Indexing and You 33 | 34 | Before FFMS2 can open a file, it must be indexed first so that keyframe/sample positions are known and seeking is easily accomplished. 35 | This is done automatically when using `Source()`, 36 | but if you want to you can invoke the indexing yourself by calling `Index()`, or by running `ffmsindex`. 37 | By default the index is written to a file so it can be reused the next time you open the same file, but this behavior can be turned off if desired. 38 | 39 | If you wonder why FFMS2 takes so long opening files, the indexing is the answer. 40 | If you want a progress report on the indexing, you can use the supplied `ffmsindex` commandline program. 41 | 42 | ## Function reference 43 | 44 | ### Index 45 | ``` 46 | ffms2.Index(string source[, string cachefile = source + ".ffindex", int[] indextracks = [], 47 | int errorhandling = 3, bint overwrite = False, bint enable_drefs = False, bint use_absolute_path = False]) 48 | ``` 49 | Indexes a number of tracks in a given source file and writes the index file to disk, where it can be picked up and used by `Source`. 50 | Normally you do not need to call this function manually; it's invoked automatically if necessary by `Source`. 51 | It does, however, give you more control over how indexing is done. 52 | 53 | Note that this function returns an integer, not a clip (since it doesn't open video, nor audio). 54 | The return value isn't particularly interesting, but for the record it's 0 if the index file already exists (and is valid) and overwrite was not enabled, 1 if the index file was created and no previous index existed, and 2 if the index file was created by overwriting an existing, valid index file. 55 | 56 | #### Arguments 57 | 58 | ##### string source 59 | The source file to index. 60 | 61 | ##### string cachefile = source + ".ffindex" 62 | The filename of the index file (where the indexing data is saved). 63 | Defaults to `sourcefilename.ffindex`. 64 | 65 | ##### int[] indextracks = [] 66 | An array containing a list of which audio tracks should be indexed (all video tracks are always indexed; you have no choice in the matter). 67 | It's possible to use -1 to simply tell it to index all tracks.´without having to list them individually. 68 | 69 | Note that FFMS2's idea about what track has what number may be completely different from what any other application might think. 70 | 71 | ##### int errorhandling = 3 72 | Controls what happens if an audio decoding error is encountered during indexing. 73 | Possible values are: 74 | 75 | - **0**: Raise an error and abort indexing. No index file is written. 76 | - **1**: Clear the affected track (effectively making it silent) and continue. 77 | - **2**: Stop indexing the track but keep all the index entries so far, effectively ending the track where the error occured. 78 | - **3**: Pretend it's raining and continue anyway. This is the default; if you encounter odd noises in the audio, try mode 0 instead and see if it's FFMS2's fault. 79 | 80 | ##### bint overwrite = False 81 | If set to true, `FFIndex()` will reindex the source file and overwrite the index file even if the index file already exists and is valid. 82 | Mostly useful for trackmask changes and testing. 83 | 84 | ##### bint enable_drefs = False 85 | Corresponds to the FFmpeg demuxer option of the same name. You will know if you need it. 86 | 87 | ##### bint use_absolute_path = False 88 | Corresponds to the FFmpeg demuxer option of the same name. You will know if you need it. 89 | 90 | ### Source 91 | ``` 92 | ffms2.Source(string source[, int track = -1, bint cache = True, 93 | string cachefile = source + ".ffindex", int fpsnum = -1, int fpsden = 1, 94 | int threads = -1, string timecodes = "", int seekmode = 1, 95 | int width = -1, int height = -1, string resizer = "BICUBIC", 96 | int format, bint alpha = False]) 97 | ``` 98 | Opens video. Will invoke indexing of all video tracks (but no audio tracks) if no valid index file is found. 99 | 100 | #### Arguments 101 | 102 | ##### string source 103 | The source file to open. 104 | 105 | ##### int track = -1 106 | The video track number to open, as seen by the relevant demuxer. 107 | Track numbers start from zero, and are guaranteed to be continous (i.e. there must be a track 1 if there is a track 0 and a track 2). -1 means open the first video track. 108 | Note that FFMS2's idea about what track has what number may (or may not) be completely different from what some other application might think. 109 | 110 | ##### bint cache = True 111 | If set to true (the default), `Source` will first check if the `cachefile` contains a valid index, and if it does, that index will be used. 112 | If no index is found, all video tracks will be indexed, and the indexing data will be written to `cachefile` afterwards. 113 | If set to false, `Source` will not look for an existing index file; instead all video tracks will be indexed when the script is opened, and the indexing data will be discarded after the script is closed; you will have to index again next time you open the script. 114 | 115 | ##### string cachefile = source + ".ffindex" 116 | The filename of the index file (where the indexing data is saved). 117 | Defaults to `sourcefilename.ffindex`. 118 | Note that if you didn't change this parameter from its default value and `Source` encounters an index file that doesn't seem to match the file it's trying to open, it will automatically reindex and then overwrite the old index file. 119 | On the other hand, if you *do* change it, `Source` will assume you have your reasons and throw an error instead if the index doesn't match the file. 120 | 121 | ##### int fpsnum = -1, int fpsden = 1 122 | Controls the framerate of the output; used for VFR to CFR conversions. 123 | If `fpsnum` is less than or equal to zero (the default), the output will contain the same frames that the input did, and the frame rate reported to VapourSynth will be set based on the input clip's average frame duration. 124 | If `fpsnum` is greater than zero, `Source` will force a constant frame rate, expressed as a rational number where `fpsnum` is the numerator and `fpsden` is the denominator. 125 | This may naturally cause `Source` to drop or duplicate frames to achieve the desired frame rate, and the output is not guaranteed to have the same number of frames that the input did. 126 | 127 | ##### int threads = -1 128 | The number of decoding threads to request from libavcodec. 129 | Setting it to less than or equal to zero means it defaults to the number of logical CPU's reported by the OS. 130 | Note that this setting might be completely ignored by libavcodec under a number of conditions; most commonly because a lot of decoders actually do not support multithreading. 131 | 132 | ##### string timecodes = "" 133 | Filename to write Matroska v2 timecodes for the opened video track to. 134 | If the file exists, it will be truncated and overwritten. 135 | Set to the empty string to disable timecodes writing (this is the default). 136 | 137 | ##### int seekmode = 1 138 | Controls how seeking is done. 139 | Mostly useful for getting uncooperative files to work. 140 | Valid modes are: 141 | 142 | - **-1**: Linear access without rewind; i.e. will throw an error if each successive requested frame number isn't bigger than the last one. 143 | Only intended for opening images but might work on well with some obscure video format. 144 | - **0**: Linear access (i.e. if you request frame `n` without having requested all frames from 0 to `n-1` in order first, all frames from 0 to `n` will have to be decoded before `n` can be delivered). 145 | The definition of slow, but should make some formats "usable". 146 | - **1**: Safe normal. Bases seeking decisions on the keyframe positions reported by libavformat. 147 | - **2**: Unsafe normal. Same as mode 1, but no error will be thrown if the exact seek destination has to be guessed. 148 | - **3**: Aggressive. Seeks in the forward direction even if no closer keyframe is known to exist. Only useful for testing and containers where libavformat doesn't report keyframes properly. 149 | 150 | ##### int width = -1, int height = -1 151 | Sets the resolution of the output video, in pixels. 152 | Setting either dimension to less than or equal to zero means the resolution of the first decoded video frame is used for that dimension. 153 | These parameters are mostly useful because FFMS2 supports video streams that change resolution mid-stream which would otherwise have to be handled with a more complicated script. 154 | 155 | ##### string resizer = "BICUBIC" 156 | The resizing algorithm to use if rescaling the image is necessary. 157 | If the video uses subsampled chroma but your chosen output colorspace does not, the chosen resizer will be used to upscale the chroma planes, even if you did not request an image rescaling. 158 | The available choices are `FAST_BILINEAR`, `BILINEAR`, `BICUBIC` (default), `X`, `POINT`, `AREA`, `BICUBLIN`, `GAUSS`, `SINC`, `LANCZOS` and `SPLINE`. 159 | 160 | ##### int format 161 | Convert the output from whatever it was to the given format. If not specified the best matching output format is used. 162 | 163 | ##### bint alpha = False 164 | Output the alpha channel as a second clip if it is present in the file. When set to True an array of two clips will be returned with alpha in the second one. If there is alpha information present. 165 | 166 | #### Exported VapourSynth frame properties 167 | There are several useful frame properties that are set. See the VapourSynth manual for a detailed explanation of them. 168 | 169 | - _DurationNum 170 | - _DurationDen 171 | - _AbsoluteTime 172 | - _SARNum 173 | - _SARDen 174 | - _Matrix 175 | - _Primaries 176 | - _Transfer 177 | - _ChromaLocation 178 | - _ColorRange 179 | - _PictType 180 | - _FieldBased 181 | 182 | ### SetLogLevel 183 | ``` 184 | ffms2.SetLogLevel(int Level = -8) 185 | ``` 186 | Sets the FFmpeg logging level, i.e. how much diagnostic spam it prints to STDERR. 187 | Since many applications that open VapourSynth scripts do not provide a way to display things printed to STDERR, and since it's rather hard to make any sense of the printed messages unless you're quite familiar with FFmpeg internals, the usefulness of this function is rather limited for end users. It's mostly intended for debugging. 188 | Defaults to quiet (no messages printed); a list of meaningful values can be found in `ffms.h`. 189 | 190 | ### GetLogLevel 191 | ``` 192 | ffms2.GetLogLevel() 193 | ``` 194 | Returns the current log level, as an integer. 195 | 196 | ### GetVersion 197 | ``` 198 | ffms2.GetVersion() 199 | ``` 200 | Returns the FFMS2 version, as a string. 201 | 202 | -------------------------------------------------------------------------------- /src/index/ffmsindex.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2008-2009 Karl Blomster 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include "ffms.h" 22 | 23 | #ifdef _WIN32 24 | #include "vsutf16.h" 25 | #endif 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | namespace { 40 | 41 | long long IndexMask = 0; 42 | int Verbose = 0; 43 | int IgnoreErrors = 0; 44 | bool Overwrite = false; 45 | bool PrintProgress = true; 46 | bool WriteTC = false; 47 | bool WriteKF = false; 48 | bool EnableDrefs = false; 49 | bool UseAbsolutePath = false; 50 | int64_t ProgressInterval = 1000000; // One second 51 | std::string InputFile; 52 | std::string CacheFile; 53 | 54 | struct Error { 55 | std::string msg; 56 | Error(const char *msg) : msg(msg) {} 57 | Error(const char *msg, FFMS_ErrorInfo const& e) : msg(msg) { 58 | this->msg.append(e.Buffer); 59 | } 60 | }; 61 | 62 | struct Progress { 63 | int Percent; 64 | int64_t Time; 65 | }; 66 | 67 | void PrintUsage() { 68 | std::cout << 69 | "FFmpegSource2 indexing app\n" 70 | "Usage: ffmsindex [options] inputfile [outputfile]\n" 71 | "If no output filename is specified, inputfile.ffindex will be used.\n" 72 | "\n" 73 | "Options:\n" 74 | "-f Force overwriting of existing index file, if any (default: no)\n" 75 | "-v Set FFmpeg verbosity level. Can be repeated for more verbosity. (default: no messages printed)\n" 76 | "-p Disable progress reporting. (default: progress reporting on)\n" 77 | "-c Write timecodes for all video tracks to outputfile_track00.tc.txt (default: no)\n" 78 | "-k Write keyframes for all video tracks to outputfile_track00.kf.txt (default: no)\n" 79 | "-t N Set the audio indexing mask to N (-1 means index all tracks, 0 means index none, default: 0)\n" 80 | "-s N Set audio decoding error handling. See the documentation for details. (default: 0)\n" 81 | "-u N Set the progress update frequency in seconds. Set to 0 for every percent. (default: 1)\n" 82 | "\n" 83 | "FFmpeg Demuxer Options:\n" 84 | "--enable_drefs\n" 85 | "--use_absolute_path\n" 86 | << std::endl; 87 | } 88 | 89 | int64_t getTimeInMicroSeconds() { 90 | static std::chrono::time_point StartTime = std::chrono::steady_clock::now(); 91 | std::chrono::time_point Now = std::chrono::steady_clock::now(); 92 | return std::chrono::duration_cast(Now - StartTime).count(); 93 | } 94 | 95 | int64_t parseSecondsToMicroseconds(const char *str) { 96 | double val = std::strtod(str, nullptr); 97 | int64_t ret = (int64_t) (val * 1000000.0); 98 | return ret; 99 | } 100 | 101 | void ParseCMDLine(int argc, const char *argv[]) { 102 | for (int i = 1; i < argc; ++i) { 103 | const char *Option = argv[i]; 104 | #define OPTION_ARG(dst, flag, parse) try { dst = parse(i + 1 < argc ? argv[i+1] : throw Error("Error: missing argument for -" flag)); i++; } catch (std::logic_error &) { throw Error("Error: invalid argument specified for -" flag); } 105 | 106 | if (!strcmp(Option, "-f")) { 107 | Overwrite = true; 108 | } else if (!strcmp(Option, "-v")) { 109 | Verbose++; 110 | } else if (!strcmp(Option, "-p")) { 111 | PrintProgress = false; 112 | } else if (!strcmp(Option, "-c")) { 113 | WriteTC = true; 114 | } else if (!strcmp(Option, "-k")) { 115 | WriteKF = true; 116 | } else if (!strcmp(Option, "-t")) { 117 | OPTION_ARG(IndexMask, "t", std::stoll); 118 | } else if (!strcmp(Option, "-s")) { 119 | OPTION_ARG(IgnoreErrors, "s", std::stoi); 120 | } else if (!strcmp(Option, "-u")) { 121 | OPTION_ARG(ProgressInterval, "u", parseSecondsToMicroseconds); 122 | } else if (!strcmp(Option, "--enable_drefs")) { 123 | EnableDrefs = true; 124 | } else if (!strcmp(Option, "--use_absolute_path")) { 125 | UseAbsolutePath = true; 126 | } else if (InputFile.empty()) { 127 | InputFile = Option; 128 | } else if (CacheFile.empty()) { 129 | CacheFile = Option; 130 | } else { 131 | std::cout << "Warning: ignoring unknown option " << Option << std::endl; 132 | } 133 | } 134 | 135 | if (IgnoreErrors < 0 || IgnoreErrors > 3) 136 | throw Error("Error: invalid error handling mode"); 137 | if (InputFile.empty()) 138 | throw Error("Error: no input file specified"); 139 | 140 | if (CacheFile.empty()) { 141 | CacheFile = InputFile; 142 | CacheFile.append(".ffindex"); 143 | } 144 | } 145 | 146 | int FFMS_CC UpdateProgress(int64_t Current, int64_t Total, void *Private) { 147 | if (!PrintProgress) 148 | return 0; 149 | 150 | int Percentage = int((double(Current) / double(Total)) * 100); 151 | 152 | if (Private) { 153 | Progress *LastProgress = (Progress *)Private; 154 | int64_t CurTime = getTimeInMicroSeconds(); 155 | if (Percentage <= LastProgress->Percent || (LastProgress->Time != 0 && (CurTime - LastProgress->Time) <= ProgressInterval)) 156 | return 0; 157 | LastProgress->Percent = Percentage; 158 | LastProgress->Time = CurTime; 159 | } 160 | 161 | std::cout << "Indexing, please wait... " << Percentage << "% \r" << std::flush; 162 | 163 | return 0; 164 | } 165 | 166 | std::string DumpFilename(FFMS_Track *Track, int TrackNum, const char *Suffix) { 167 | if (FFMS_GetTrackType(Track) != FFMS_TYPE_VIDEO || !FFMS_GetNumFrames(Track)) 168 | return ""; 169 | 170 | char tn[11]; 171 | snprintf(tn, 11, "%02" PRIu32"", (uint32_t) TrackNum); 172 | return CacheFile + "_track" + tn + Suffix; 173 | } 174 | 175 | void DoIndexing() { 176 | char ErrorMsg[1024]; 177 | FFMS_ErrorInfo E; 178 | E.Buffer = ErrorMsg; 179 | E.BufferSize = sizeof(ErrorMsg); 180 | 181 | Progress ProgressTracker = { 0, getTimeInMicroSeconds() }; 182 | 183 | FFMS_Index *Index = FFMS_ReadIndex(CacheFile.c_str(), &E); 184 | if (Index) { 185 | FFMS_DestroyIndex(Index); 186 | if (!Overwrite) 187 | throw Error("Error: index file already exists, use -f if you are sure you want to overwrite it."); 188 | } 189 | 190 | UpdateProgress(0, 100, nullptr); 191 | 192 | FFMS_KeyValuePair LAVFOpts[] = {{ "enable_drefs", EnableDrefs ? "1" : "0" }, { "use_absolute_path", UseAbsolutePath ? "1" : "0" }}; 193 | 194 | FFMS_Indexer *Indexer = FFMS_CreateIndexer2(InputFile.c_str(), LAVFOpts, 2, &E); 195 | if (Indexer == nullptr) 196 | throw Error("\nFailed to initialize indexing: ", E); 197 | 198 | FFMS_SetProgressCallback(Indexer, UpdateProgress, &ProgressTracker); 199 | 200 | // Treat -1 as meaning track numbers above sizeof(long long) * 8 too, dumping implies indexing 201 | if (IndexMask == -1) 202 | FFMS_TrackTypeIndexSettings(Indexer, FFMS_TYPE_AUDIO, 1, 0); 203 | 204 | // Apply attributes to remaining tracks (will set the attributes again on some tracks) 205 | for (int i = 0; i < static_cast(sizeof(IndexMask) * 8); i++) { 206 | if ((IndexMask >> i) & 1) 207 | FFMS_TrackIndexSettings(Indexer, i, 1, 0); 208 | } 209 | 210 | Index = FFMS_DoIndexing2(Indexer, IgnoreErrors, &E); 211 | 212 | // The indexer is always freed 213 | Indexer = nullptr; 214 | 215 | if (Index == nullptr) 216 | throw Error("\nIndexing error: ", E); 217 | 218 | UpdateProgress(100, 100, nullptr); 219 | 220 | std::cout << std::endl; 221 | 222 | if (WriteTC) { 223 | if (PrintProgress) 224 | std::cout << "Writing timecodes... "; 225 | int NumTracks = FFMS_GetNumTracks(Index); 226 | for (int t = 0; t < NumTracks; t++) { 227 | FFMS_Track *Track = FFMS_GetTrackFromIndex(Index, t); 228 | std::string Filename = DumpFilename(Track, t, ".tc.txt"); 229 | if (!Filename.empty()) { 230 | if (FFMS_WriteTimecodes(Track, Filename.c_str(), &E)) 231 | std::cout << std::endl << "Failed to write timecodes file " 232 | << Filename << ": " << E.Buffer << std::endl; 233 | } 234 | } 235 | if (PrintProgress) 236 | std::cout << "done." << std::endl; 237 | } 238 | 239 | if (WriteKF) { 240 | if (PrintProgress) 241 | std::cout << "Writing keyframes... "; 242 | int NumTracks = FFMS_GetNumTracks(Index); 243 | for (int t = 0; t < NumTracks; t++) { 244 | FFMS_Track *Track = FFMS_GetTrackFromIndex(Index, t); 245 | std::string Filename = DumpFilename(Track, t, ".kf.txt"); 246 | if (!Filename.empty()) { 247 | std::ofstream kf(Filename.c_str()); 248 | kf << "# keyframe format v1\n" 249 | "fps 0\n"; 250 | 251 | int FrameCount = FFMS_GetNumFrames(Track); 252 | for (int CurFrameNum = 0; CurFrameNum < FrameCount; CurFrameNum++) { 253 | if (FFMS_GetFrameInfo(Track, CurFrameNum)->KeyFrame) 254 | kf << CurFrameNum << "\n"; 255 | } 256 | } 257 | } 258 | if (PrintProgress) 259 | std::cout << "done. " << std::endl; 260 | } 261 | 262 | if (PrintProgress) 263 | std::cout << "Writing index... "; 264 | 265 | int error = FFMS_WriteIndex(CacheFile.c_str(), Index, &E); 266 | FFMS_DestroyIndex(Index); 267 | if (error) 268 | throw Error("Error writing index: ", E); 269 | 270 | if (PrintProgress) 271 | std::cout << "done." << std::endl; 272 | } 273 | 274 | } // namespace { 275 | 276 | #ifdef _WIN32 277 | int wmain(int argc, const wchar_t *_argv[]) { 278 | std::vector StringPtrs(argc); 279 | std::vector StringStorage(argc); 280 | 281 | for (int i = 0; i < argc; i++) { 282 | StringStorage[i] = utf16_to_utf8(_argv[i]); 283 | StringPtrs[i] = StringStorage[i].c_str(); 284 | } 285 | 286 | const char **argv = StringPtrs.data(); 287 | #else 288 | int main(int argc, const char *argv[]) { 289 | #endif 290 | try { 291 | if (argc <= 1) { 292 | PrintUsage(); 293 | return 0; 294 | } 295 | 296 | ParseCMDLine(argc, argv); 297 | } catch (Error const& e) { 298 | std::cout << e.msg << std::endl << std::flush; 299 | return 1; 300 | } 301 | 302 | FFMS_Init(0, 0); 303 | 304 | switch (Verbose) { 305 | case 0: FFMS_SetLogLevel(FFMS_LOG_QUIET); break; 306 | case 1: FFMS_SetLogLevel(FFMS_LOG_WARNING); break; 307 | case 2: FFMS_SetLogLevel(FFMS_LOG_INFO); break; 308 | case 3: FFMS_SetLogLevel(FFMS_LOG_VERBOSE); break; 309 | default: FFMS_SetLogLevel(FFMS_LOG_DEBUG); // if user used -v 4 or more times, he deserves the spam 310 | } 311 | 312 | try { 313 | DoIndexing(); 314 | } catch (Error const& e) { 315 | std::cout << e.msg << std::endl << std::flush; 316 | FFMS_Deinit(); 317 | return 1; 318 | } 319 | 320 | FFMS_Deinit(); 321 | return 0; 322 | } 323 | -------------------------------------------------------------------------------- /src/core/ffms.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007-2017 Fredrik Mellbin 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include "ffms.h" 22 | 23 | #include "audiosource.h" 24 | #include "indexing.h" 25 | #include "videosource.h" 26 | #include "videoutils.h" 27 | 28 | extern "C" { 29 | #include 30 | #include 31 | } 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #ifdef FFMS_WIN_DEBUG 38 | # include 39 | #endif 40 | 41 | static std::once_flag FFmpegOnce; 42 | static std::mutex FFmpegNetwork; 43 | static bool FFmpegNetworkInited = false; 44 | 45 | #ifdef FFMS_WIN_DEBUG 46 | 47 | static void av_log_windebug_callback(void* ptr, int level, const char* fmt, va_list vl) { 48 | if (level > av_log_get_level()) 49 | return; 50 | 51 | static int print_prefix = 1; 52 | static int count; 53 | static char line[1024] = {}, prev[1024] = {}; 54 | auto avc = ptr ? *static_cast(ptr) : nullptr; 55 | 56 | int written = 0; 57 | if (print_prefix && avc) { 58 | written = snprintf(line, sizeof(line), "[%s @ %p]", avc->item_name(ptr), ptr); 59 | } 60 | 61 | written += vsnprintf(line + written, sizeof(line) - written, fmt, vl); 62 | 63 | print_prefix = line[written - 1] == '\n'; 64 | line[sizeof(line) - 1] = 0; 65 | if (print_prefix && !strcmp(line, prev)) { 66 | count++; 67 | return; 68 | } 69 | if (count > 0) { 70 | std::stringstream ss; 71 | ss << " Last message repeated " << count << " times\n"; 72 | OutputDebugStringA(ss.str().c_str()); 73 | count = 0; 74 | } 75 | OutputDebugStringA(line); 76 | strcpy(prev, line); 77 | } 78 | 79 | #endif 80 | 81 | FFMS_API(void) FFMS_Init(int, int) { 82 | std::call_once(FFmpegOnce, []() { 83 | #ifdef FFMS_WIN_DEBUG 84 | av_log_set_callback(av_log_windebug_callback); 85 | av_log_set_level(AV_LOG_INFO); 86 | #else 87 | av_log_set_level(AV_LOG_QUIET); 88 | #endif 89 | }); 90 | FFmpegNetwork.lock(); 91 | if (!FFmpegNetworkInited) { 92 | avformat_network_init(); 93 | FFmpegNetworkInited = true; 94 | } 95 | FFmpegNetwork.unlock(); 96 | } 97 | 98 | FFMS_API(void) FFMS_Deinit() { 99 | FFmpegNetwork.lock(); 100 | if (FFmpegNetworkInited) { 101 | avformat_network_deinit(); 102 | FFmpegNetworkInited = false; 103 | } 104 | FFmpegNetwork.unlock(); 105 | } 106 | 107 | FFMS_API(int) FFMS_GetVersion() { 108 | return FFMS_VERSION; 109 | } 110 | 111 | FFMS_API(int) FFMS_GetLogLevel() { 112 | return av_log_get_level(); 113 | } 114 | 115 | FFMS_API(void) FFMS_SetLogLevel(int Level) { 116 | av_log_set_level(Level); 117 | } 118 | 119 | FFMS_API(FFMS_VideoSource *) FFMS_CreateVideoSource(const char *SourceFile, int Track, FFMS_Index *Index, int Threads, int SeekMode, FFMS_ErrorInfo *ErrorInfo) { 120 | try { 121 | return new FFMS_VideoSource(SourceFile, *Index, Track, Threads, SeekMode); 122 | } catch (FFMS_Exception &e) { 123 | e.CopyOut(ErrorInfo); 124 | return nullptr; 125 | } 126 | } 127 | 128 | FFMS_API(FFMS_AudioSource *) FFMS_CreateAudioSource(const char *SourceFile, int Track, FFMS_Index *Index, int DelayMode, FFMS_ErrorInfo *ErrorInfo) { 129 | return FFMS_CreateAudioSource2(SourceFile, Track, Index, DelayMode, -1, 0, ErrorInfo); 130 | } 131 | 132 | FFMS_API(FFMS_AudioSource *) FFMS_CreateAudioSource2(const char *SourceFile, int Track, FFMS_Index *Index, int DelayMode, int FillGaps, double DrcScale, FFMS_ErrorInfo *ErrorInfo) { 133 | try { 134 | return new FFMS_AudioSource(SourceFile, *Index, Track, DelayMode, FillGaps, DrcScale); 135 | } catch (FFMS_Exception &e) { 136 | e.CopyOut(ErrorInfo); 137 | return nullptr; 138 | } 139 | } 140 | 141 | FFMS_API(void) FFMS_DestroyVideoSource(FFMS_VideoSource *V) { 142 | delete V; 143 | } 144 | 145 | FFMS_API(void) FFMS_DestroyAudioSource(FFMS_AudioSource *A) { 146 | delete A; 147 | } 148 | 149 | FFMS_API(const FFMS_VideoProperties *) FFMS_GetVideoProperties(FFMS_VideoSource *V) { 150 | return &V->GetVideoProperties(); 151 | } 152 | 153 | FFMS_API(const FFMS_AudioProperties *) FFMS_GetAudioProperties(FFMS_AudioSource *A) { 154 | return &A->GetAudioProperties(); 155 | } 156 | 157 | FFMS_API(const FFMS_Frame *) FFMS_GetFrame(FFMS_VideoSource *V, int n, FFMS_ErrorInfo *ErrorInfo) { 158 | ClearErrorInfo(ErrorInfo); 159 | try { 160 | return V->GetFrame(n); 161 | } catch (FFMS_Exception &e) { 162 | e.CopyOut(ErrorInfo); 163 | return nullptr; 164 | } 165 | } 166 | 167 | FFMS_API(const FFMS_Frame *) FFMS_GetFrameByTime(FFMS_VideoSource *V, double Time, FFMS_ErrorInfo *ErrorInfo) { 168 | ClearErrorInfo(ErrorInfo); 169 | try { 170 | return V->GetFrameByTime(Time); 171 | } catch (FFMS_Exception &e) { 172 | e.CopyOut(ErrorInfo); 173 | return nullptr; 174 | } 175 | } 176 | 177 | FFMS_API(int) FFMS_GetAudio(FFMS_AudioSource *A, void *Buf, int64_t Start, int64_t Count, FFMS_ErrorInfo *ErrorInfo) { 178 | ClearErrorInfo(ErrorInfo); 179 | try { 180 | A->GetAudio(Buf, Start, Count); 181 | } catch (FFMS_Exception &e) { 182 | return e.CopyOut(ErrorInfo); 183 | } 184 | return FFMS_ERROR_SUCCESS; 185 | } 186 | 187 | FFMS_API(int) FFMS_SetOutputFormatV2(FFMS_VideoSource *V, const int *TargetFormats, int Width, int Height, int Resizer, FFMS_ErrorInfo *ErrorInfo) { 188 | ClearErrorInfo(ErrorInfo); 189 | try { 190 | V->SetOutputFormat(reinterpret_cast(TargetFormats), Width, Height, Resizer); 191 | } catch (FFMS_Exception &e) { 192 | return e.CopyOut(ErrorInfo); 193 | } 194 | return FFMS_ERROR_SUCCESS; 195 | } 196 | 197 | FFMS_API(void) FFMS_ResetOutputFormatV(FFMS_VideoSource *V) { 198 | V->ResetOutputFormat(); 199 | } 200 | 201 | FFMS_API(int) FFMS_SetInputFormatV(FFMS_VideoSource *V, int ColorSpace, int ColorRange, int Format, FFMS_ErrorInfo *ErrorInfo) { 202 | ClearErrorInfo(ErrorInfo); 203 | try { 204 | V->SetInputFormat(ColorSpace, ColorRange, static_cast(Format)); 205 | } catch (FFMS_Exception &e) { 206 | return e.CopyOut(ErrorInfo); 207 | } 208 | return FFMS_ERROR_SUCCESS; 209 | } 210 | 211 | FFMS_API(void) FFMS_ResetInputFormatV(FFMS_VideoSource *V) { 212 | V->ResetInputFormat(); 213 | } 214 | 215 | FFMS_API(FFMS_ResampleOptions *) FFMS_CreateResampleOptions(FFMS_AudioSource *A) { 216 | return A->CreateResampleOptions().release(); 217 | } 218 | 219 | FFMS_API(void) FFMS_DestroyResampleOptions(FFMS_ResampleOptions *options) { 220 | delete options; 221 | } 222 | 223 | FFMS_API(int) FFMS_SetOutputFormatA(FFMS_AudioSource *A, const FFMS_ResampleOptions *options, FFMS_ErrorInfo *ErrorInfo) { 224 | ClearErrorInfo(ErrorInfo); 225 | try { 226 | A->SetOutputFormat(*options); 227 | } catch (FFMS_Exception &e) { 228 | return e.CopyOut(ErrorInfo); 229 | } 230 | return FFMS_ERROR_SUCCESS; 231 | } 232 | 233 | FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index) { 234 | delete Index; 235 | } 236 | 237 | FFMS_API(FFMS_IndexErrorHandling) FFMS_GetErrorHandling(FFMS_Index *Index) { 238 | return static_cast(Index->ErrorHandling); 239 | } 240 | 241 | FFMS_API(int) FFMS_GetFirstTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo) { 242 | ClearErrorInfo(ErrorInfo); 243 | for (int i = 0; i < static_cast(Index->size()); i++) 244 | if ((*Index)[i].TT == TrackType) 245 | return i; 246 | 247 | try { 248 | throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_NOT_AVAILABLE, 249 | "No suitable, indexed track found"); 250 | } catch (FFMS_Exception &e) { 251 | e.CopyOut(ErrorInfo); 252 | return -1; 253 | } 254 | } 255 | 256 | FFMS_API(int) FFMS_GetFirstIndexedTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo) { 257 | ClearErrorInfo(ErrorInfo); 258 | for (int i = 0; i < static_cast(Index->size()); i++) 259 | if ((*Index)[i].TT == TrackType && !(*Index)[i].empty()) 260 | return i; 261 | try { 262 | throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_NOT_AVAILABLE, 263 | "No suitable, indexed track found"); 264 | } catch (FFMS_Exception &e) { 265 | e.CopyOut(ErrorInfo); 266 | return -1; 267 | } 268 | } 269 | 270 | FFMS_API(int) FFMS_GetNumTracks(FFMS_Index *Index) { 271 | return static_cast(Index->size()); 272 | } 273 | 274 | FFMS_API(int) FFMS_GetNumTracksI(FFMS_Indexer *Indexer) { 275 | return Indexer->GetNumberOfTracks(); 276 | } 277 | 278 | FFMS_API(int) FFMS_GetTrackType(FFMS_Track *T) { 279 | return T->TT; 280 | } 281 | 282 | FFMS_API(int) FFMS_GetTrackTypeI(FFMS_Indexer *Indexer, int Track) { 283 | return Indexer->GetTrackType(Track); 284 | } 285 | 286 | FFMS_API(const char *) FFMS_GetCodecNameI(FFMS_Indexer *Indexer, int Track) { 287 | return Indexer->GetTrackCodec(Track); 288 | } 289 | 290 | FFMS_API(int) FFMS_GetNumFrames(FFMS_Track *T) { 291 | return T->VisibleFrameCount(); 292 | } 293 | 294 | FFMS_API(const FFMS_FrameInfo *) FFMS_GetFrameInfo(FFMS_Track *T, int Frame) { 295 | return T->GetFrameInfo(static_cast(Frame)); 296 | } 297 | 298 | FFMS_API(FFMS_Track *) FFMS_GetTrackFromIndex(FFMS_Index *Index, int Track) { 299 | return &(*Index)[Track]; 300 | } 301 | 302 | FFMS_API(FFMS_Track *) FFMS_GetTrackFromVideo(FFMS_VideoSource *V) { 303 | return V->GetTrack(); 304 | } 305 | 306 | FFMS_API(FFMS_Track *) FFMS_GetTrackFromAudio(FFMS_AudioSource *A) { 307 | return A->GetTrack(); 308 | } 309 | 310 | FFMS_API(const FFMS_TrackTimeBase *) FFMS_GetTimeBase(FFMS_Track *T) { 311 | return &T->TB; 312 | } 313 | 314 | FFMS_API(int) FFMS_WriteTimecodes(FFMS_Track *T, const char *TimecodeFile, FFMS_ErrorInfo *ErrorInfo) { 315 | ClearErrorInfo(ErrorInfo); 316 | try { 317 | T->WriteTimecodes(TimecodeFile); 318 | } catch (FFMS_Exception &e) { 319 | return e.CopyOut(ErrorInfo); 320 | } 321 | return FFMS_ERROR_SUCCESS; 322 | } 323 | 324 | FFMS_API(FFMS_Indexer *) FFMS_CreateIndexer(const char *SourceFile, FFMS_ErrorInfo *ErrorInfo) { 325 | return FFMS_CreateIndexer2(SourceFile, nullptr, 0, ErrorInfo); 326 | } 327 | 328 | FFMS_API(FFMS_Indexer *) FFMS_CreateIndexer2(const char *SourceFile, const FFMS_KeyValuePair *DemuxerOptions, int NumOptions, FFMS_ErrorInfo *ErrorInfo) { 329 | ClearErrorInfo(ErrorInfo); 330 | try { 331 | return new FFMS_Indexer(SourceFile, DemuxerOptions, NumOptions); 332 | } catch (FFMS_Exception &e) { 333 | e.CopyOut(ErrorInfo); 334 | return nullptr; 335 | } 336 | } 337 | 338 | FFMS_API(FFMS_Index *) FFMS_DoIndexing2(FFMS_Indexer *Indexer, int ErrorHandling, FFMS_ErrorInfo *ErrorInfo) { 339 | ClearErrorInfo(ErrorInfo); 340 | 341 | Indexer->SetErrorHandling(ErrorHandling); 342 | 343 | FFMS_Index *Index = nullptr; 344 | try { 345 | Index = Indexer->DoIndexing(); 346 | } catch (FFMS_Exception &e) { 347 | e.CopyOut(ErrorInfo); 348 | } 349 | delete Indexer; 350 | return Index; 351 | } 352 | 353 | FFMS_API(void) FFMS_TrackIndexSettings(FFMS_Indexer *Indexer, int Track, int Index, int) { 354 | Indexer->SetIndexTrack(Track, !!Index); 355 | } 356 | 357 | FFMS_API(void) FFMS_TrackTypeIndexSettings(FFMS_Indexer *Indexer, int TrackType, int Index, int) { 358 | Indexer->SetIndexTrackType(TrackType, !!Index); 359 | } 360 | 361 | FFMS_API(void) FFMS_SetProgressCallback(FFMS_Indexer *Indexer, TIndexCallback IC, void *ICPrivate) { 362 | Indexer->SetProgressCallback(IC, ICPrivate); 363 | } 364 | 365 | FFMS_API(void) FFMS_CancelIndexing(FFMS_Indexer *Indexer) { 366 | delete Indexer; 367 | } 368 | 369 | FFMS_API(FFMS_Index *) FFMS_ReadIndex(const char *IndexFile, FFMS_ErrorInfo *ErrorInfo) { 370 | ClearErrorInfo(ErrorInfo); 371 | try { 372 | return new FFMS_Index(IndexFile); 373 | } catch (FFMS_Exception &e) { 374 | e.CopyOut(ErrorInfo); 375 | return nullptr; 376 | } 377 | } 378 | 379 | FFMS_API(FFMS_Index *) FFMS_ReadIndexFromBuffer(const uint8_t *Buffer, size_t Size, FFMS_ErrorInfo *ErrorInfo) { 380 | ClearErrorInfo(ErrorInfo); 381 | try { 382 | return new FFMS_Index(Buffer, Size); 383 | } catch (FFMS_Exception &e) { 384 | e.CopyOut(ErrorInfo); 385 | return nullptr; 386 | } 387 | } 388 | 389 | FFMS_API(int) FFMS_IndexBelongsToFile(FFMS_Index *Index, const char *SourceFile, FFMS_ErrorInfo *ErrorInfo) { 390 | ClearErrorInfo(ErrorInfo); 391 | try { 392 | if (!Index->CompareFileSignature(SourceFile)) 393 | throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_FILE_MISMATCH, 394 | "The index does not belong to the file"); 395 | } catch (FFMS_Exception &e) { 396 | return e.CopyOut(ErrorInfo); 397 | } 398 | return FFMS_ERROR_SUCCESS; 399 | } 400 | 401 | FFMS_API(int) FFMS_WriteIndex(const char *IndexFile, FFMS_Index *Index, FFMS_ErrorInfo *ErrorInfo) { 402 | ClearErrorInfo(ErrorInfo); 403 | try { 404 | Index->WriteIndexFile(IndexFile); 405 | } catch (FFMS_Exception &e) { 406 | return e.CopyOut(ErrorInfo); 407 | } 408 | return FFMS_ERROR_SUCCESS; 409 | } 410 | 411 | FFMS_API(int) FFMS_WriteIndexToBuffer(uint8_t **BufferPtr, size_t *Size, FFMS_Index *Index, FFMS_ErrorInfo *ErrorInfo) { 412 | ClearErrorInfo(ErrorInfo); 413 | uint8_t *buf; 414 | 415 | try { 416 | buf = Index->WriteIndexBuffer(Size); 417 | } catch (FFMS_Exception &e) { 418 | *Size = 0; 419 | *BufferPtr = nullptr; 420 | return e.CopyOut(ErrorInfo); 421 | } 422 | 423 | *BufferPtr = buf; 424 | 425 | return FFMS_ERROR_SUCCESS; 426 | } 427 | 428 | FFMS_API(void) FFMS_FreeIndexBuffer(uint8_t **BufferPtr) { 429 | av_freep(BufferPtr); 430 | } 431 | 432 | FFMS_API(int) FFMS_GetPixFmt(const char *Name) { 433 | return av_get_pix_fmt(Name); 434 | } 435 | 436 | FFMS_API(const char *) FFMS_GetFormatNameI(FFMS_Indexer *Indexer) { 437 | return Indexer->GetFormatName(); 438 | } 439 | -------------------------------------------------------------------------------- /src/vapoursynth/VapourSynth.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2017 Fredrik Mellbin 3 | * 4 | * This file is part of VapourSynth. 5 | * 6 | * VapourSynth is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2.1 of the License, or (at your option) any later version. 10 | * 11 | * VapourSynth is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with VapourSynth; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | #ifndef VAPOURSYNTH_H 22 | #define VAPOURSYNTH_H 23 | 24 | #include 25 | 26 | #define VAPOURSYNTH_API_MAJOR 3 27 | #define VAPOURSYNTH_API_MINOR 5 28 | #define VAPOURSYNTH_API_VERSION ((VAPOURSYNTH_API_MAJOR << 16) | (VAPOURSYNTH_API_MINOR)) 29 | 30 | /* Convenience for C++ users. */ 31 | #ifdef __cplusplus 32 | # define VS_EXTERN_C extern "C" 33 | # if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) 34 | # define VS_NOEXCEPT noexcept 35 | # else 36 | # define VS_NOEXCEPT 37 | # endif 38 | #else 39 | # define VS_EXTERN_C 40 | # define VS_NOEXCEPT 41 | #endif 42 | 43 | #if defined(_WIN32) && !defined(_WIN64) 44 | # define VS_CC __stdcall 45 | #else 46 | # define VS_CC 47 | #endif 48 | 49 | /* And now for some symbol hide-and-seek... */ 50 | #if defined(_WIN32) /* Windows being special */ 51 | # define VS_EXTERNAL_API(ret) VS_EXTERN_C __declspec(dllexport) ret VS_CC 52 | #elif defined(__GNUC__) && __GNUC__ >= 4 53 | # define VS_EXTERNAL_API(ret) VS_EXTERN_C __attribute__((visibility("default"))) ret VS_CC 54 | #else 55 | # define VS_EXTERNAL_API(ret) VS_EXTERN_C ret VS_CC 56 | #endif 57 | 58 | #if !defined(VS_CORE_EXPORTS) && defined(_WIN32) 59 | # define VS_API(ret) VS_EXTERN_C __declspec(dllimport) ret VS_CC 60 | #else 61 | # define VS_API(ret) VS_EXTERNAL_API(ret) 62 | #endif 63 | 64 | typedef struct VSFrameRef VSFrameRef; 65 | typedef struct VSNodeRef VSNodeRef; 66 | typedef struct VSCore VSCore; 67 | typedef struct VSPlugin VSPlugin; 68 | typedef struct VSNode VSNode; 69 | typedef struct VSFuncRef VSFuncRef; 70 | typedef struct VSMap VSMap; 71 | typedef struct VSAPI VSAPI; 72 | typedef struct VSFrameContext VSFrameContext; 73 | 74 | typedef enum VSColorFamily { 75 | /* all planar formats */ 76 | cmGray = 1000000, 77 | cmRGB = 2000000, 78 | cmYUV = 3000000, 79 | cmYCoCg = 4000000, 80 | /* special for compatibility */ 81 | cmCompat = 9000000 82 | } VSColorFamily; 83 | 84 | typedef enum VSSampleType { 85 | stInteger = 0, 86 | stFloat = 1 87 | } VSSampleType; 88 | 89 | /* The +10 is so people won't be using the constants interchangably "by accident" */ 90 | typedef enum VSPresetFormat { 91 | pfNone = 0, 92 | 93 | pfGray8 = cmGray + 10, 94 | pfGray16, 95 | 96 | pfGrayH, 97 | pfGrayS, 98 | 99 | pfYUV420P8 = cmYUV + 10, 100 | pfYUV422P8, 101 | pfYUV444P8, 102 | pfYUV410P8, 103 | pfYUV411P8, 104 | pfYUV440P8, 105 | 106 | pfYUV420P9, 107 | pfYUV422P9, 108 | pfYUV444P9, 109 | 110 | pfYUV420P10, 111 | pfYUV422P10, 112 | pfYUV444P10, 113 | 114 | pfYUV420P16, 115 | pfYUV422P16, 116 | pfYUV444P16, 117 | 118 | pfYUV444PH, 119 | pfYUV444PS, 120 | 121 | pfYUV420P12, 122 | pfYUV422P12, 123 | pfYUV444P12, 124 | 125 | pfYUV420P14, 126 | pfYUV422P14, 127 | pfYUV444P14, 128 | 129 | pfRGB24 = cmRGB + 10, 130 | pfRGB27, 131 | pfRGB30, 132 | pfRGB48, 133 | 134 | pfRGBH, 135 | pfRGBS, 136 | 137 | /* special for compatibility, if you implement these in any filter I'll personally kill you */ 138 | /* I'll also change their ids around to break your stuff regularly */ 139 | pfCompatBGR32 = cmCompat + 10, 140 | pfCompatYUY2 141 | } VSPresetFormat; 142 | 143 | typedef enum VSFilterMode { 144 | fmParallel = 100, /* completely parallel execution */ 145 | fmParallelRequests = 200, /* for filters that are serial in nature but can request one or more frames they need in advance */ 146 | fmUnordered = 300, /* for filters that modify their internal state every request */ 147 | fmSerial = 400 /* for source filters and compatibility with other filtering architectures */ 148 | } VSFilterMode; 149 | 150 | typedef struct VSFormat { 151 | char name[32]; 152 | int id; 153 | int colorFamily; /* see VSColorFamily */ 154 | int sampleType; /* see VSSampleType */ 155 | int bitsPerSample; /* number of significant bits */ 156 | int bytesPerSample; /* actual storage is always in a power of 2 and the smallest possible that can fit the number of bits used per sample */ 157 | 158 | int subSamplingW; /* log2 subsampling factor, applied to second and third plane */ 159 | int subSamplingH; 160 | 161 | int numPlanes; /* implicit from colorFamily */ 162 | } VSFormat; 163 | 164 | typedef enum VSNodeFlags { 165 | nfNoCache = 1, 166 | nfIsCache = 2, 167 | nfMakeLinear = 4 /* api 3.3 */ 168 | } VSNodeFlags; 169 | 170 | typedef enum VSPropTypes { 171 | ptUnset = 'u', 172 | ptInt = 'i', 173 | ptFloat = 'f', 174 | ptData = 's', 175 | ptNode = 'c', 176 | ptFrame = 'v', 177 | ptFunction = 'm' 178 | } VSPropTypes; 179 | 180 | typedef enum VSGetPropErrors { 181 | peUnset = 1, 182 | peType = 2, 183 | peIndex = 4 184 | } VSGetPropErrors; 185 | 186 | typedef enum VSPropAppendMode { 187 | paReplace = 0, 188 | paAppend = 1, 189 | paTouch = 2 190 | } VSPropAppendMode; 191 | 192 | typedef struct VSCoreInfo { 193 | const char *versionString; 194 | int core; 195 | int api; 196 | int numThreads; 197 | int64_t maxFramebufferSize; 198 | int64_t usedFramebufferSize; 199 | } VSCoreInfo; 200 | 201 | typedef struct VSVideoInfo { 202 | const VSFormat *format; 203 | int64_t fpsNum; 204 | int64_t fpsDen; 205 | int width; 206 | int height; 207 | int numFrames; /* api 3.2 - no longer allowed to be 0 */ 208 | int flags; 209 | } VSVideoInfo; 210 | 211 | typedef enum VSActivationReason { 212 | arInitial = 0, 213 | arFrameReady = 1, 214 | arAllFramesReady = 2, 215 | arError = -1 216 | } VSActivationReason; 217 | 218 | typedef enum VSMessageType { 219 | mtDebug = 0, 220 | mtWarning = 1, 221 | mtCritical = 2, 222 | mtFatal = 3 223 | } VSMessageType; 224 | 225 | /* core entry point */ 226 | typedef const VSAPI *(VS_CC *VSGetVapourSynthAPI)(int version); 227 | 228 | /* plugin function and filter typedefs */ 229 | typedef void (VS_CC *VSPublicFunction)(const VSMap *in, VSMap *out, void *userData, VSCore *core, const VSAPI *vsapi); 230 | typedef void (VS_CC *VSRegisterFunction)(const char *name, const char *args, VSPublicFunction argsFunc, void *functionData, VSPlugin *plugin); 231 | typedef void (VS_CC *VSConfigPlugin)(const char *identifier, const char *defaultNamespace, const char *name, int apiVersion, int readonly, VSPlugin *plugin); 232 | typedef void (VS_CC *VSInitPlugin)(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin *plugin); 233 | typedef void (VS_CC *VSFreeFuncData)(void *userData); 234 | typedef void (VS_CC *VSFilterInit)(VSMap *in, VSMap *out, void **instanceData, VSNode *node, VSCore *core, const VSAPI *vsapi); 235 | typedef const VSFrameRef *(VS_CC *VSFilterGetFrame)(int n, int activationReason, void **instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi); 236 | typedef void (VS_CC *VSFilterFree)(void *instanceData, VSCore *core, const VSAPI *vsapi); 237 | 238 | /* other */ 239 | typedef void (VS_CC *VSFrameDoneCallback)(void *userData, const VSFrameRef *f, int n, VSNodeRef *, const char *errorMsg); 240 | typedef void (VS_CC *VSMessageHandler)(int msgType, const char *msg, void *userData); 241 | 242 | struct VSAPI { 243 | VSCore *(VS_CC *createCore)(int threads) VS_NOEXCEPT; 244 | void (VS_CC *freeCore)(VSCore *core) VS_NOEXCEPT; 245 | const VSCoreInfo *(VS_CC *getCoreInfo)(VSCore *core) VS_NOEXCEPT; 246 | 247 | const VSFrameRef *(VS_CC *cloneFrameRef)(const VSFrameRef *f) VS_NOEXCEPT; 248 | VSNodeRef *(VS_CC *cloneNodeRef)(VSNodeRef *node) VS_NOEXCEPT; 249 | VSFuncRef *(VS_CC *cloneFuncRef)(VSFuncRef *f) VS_NOEXCEPT; 250 | 251 | void (VS_CC *freeFrame)(const VSFrameRef *f) VS_NOEXCEPT; 252 | void (VS_CC *freeNode)(VSNodeRef *node) VS_NOEXCEPT; 253 | void (VS_CC *freeFunc)(VSFuncRef *f) VS_NOEXCEPT; 254 | 255 | VSFrameRef *(VS_CC *newVideoFrame)(const VSFormat *format, int width, int height, const VSFrameRef *propSrc, VSCore *core) VS_NOEXCEPT; 256 | VSFrameRef *(VS_CC *copyFrame)(const VSFrameRef *f, VSCore *core) VS_NOEXCEPT; 257 | void (VS_CC *copyFrameProps)(const VSFrameRef *src, VSFrameRef *dst, VSCore *core) VS_NOEXCEPT; 258 | 259 | void (VS_CC *registerFunction)(const char *name, const char *args, VSPublicFunction argsFunc, void *functionData, VSPlugin *plugin) VS_NOEXCEPT; 260 | VSPlugin *(VS_CC *getPluginById)(const char *identifier, VSCore *core) VS_NOEXCEPT; 261 | VSPlugin *(VS_CC *getPluginByNs)(const char *ns, VSCore *core) VS_NOEXCEPT; 262 | VSMap *(VS_CC *getPlugins)(VSCore *core) VS_NOEXCEPT; 263 | VSMap *(VS_CC *getFunctions)(VSPlugin *plugin) VS_NOEXCEPT; 264 | void (VS_CC *createFilter)(const VSMap *in, VSMap *out, const char *name, VSFilterInit init, VSFilterGetFrame getFrame, VSFilterFree free, int filterMode, int flags, void *instanceData, VSCore *core) VS_NOEXCEPT; 265 | void (VS_CC *setError)(VSMap *map, const char *errorMessage) VS_NOEXCEPT; /* use to signal errors outside filter getframe functions */ 266 | const char *(VS_CC *getError)(const VSMap *map) VS_NOEXCEPT; /* use to query errors, returns 0 if no error */ 267 | void (VS_CC *setFilterError)(const char *errorMessage, VSFrameContext *frameCtx) VS_NOEXCEPT; /* use to signal errors in the filter getframe function */ 268 | VSMap *(VS_CC *invoke)(VSPlugin *plugin, const char *name, const VSMap *args) VS_NOEXCEPT; 269 | 270 | const VSFormat *(VS_CC *getFormatPreset)(int id, VSCore *core) VS_NOEXCEPT; 271 | const VSFormat *(VS_CC *registerFormat)(int colorFamily, int sampleType, int bitsPerSample, int subSamplingW, int subSamplingH, VSCore *core) VS_NOEXCEPT; 272 | 273 | const VSFrameRef *(VS_CC *getFrame)(int n, VSNodeRef *node, char *errorMsg, int bufSize) VS_NOEXCEPT; /* do never use inside a filter's getframe function, for external applications using the core as a library or for requesting frames in a filter constructor */ 274 | void (VS_CC *getFrameAsync)(int n, VSNodeRef *node, VSFrameDoneCallback callback, void *userData) VS_NOEXCEPT; /* do never use inside a filter's getframe function, for external applications using the core as a library or for requesting frames in a filter constructor */ 275 | const VSFrameRef *(VS_CC *getFrameFilter)(int n, VSNodeRef *node, VSFrameContext *frameCtx) VS_NOEXCEPT; /* only use inside a filter's getframe function */ 276 | void (VS_CC *requestFrameFilter)(int n, VSNodeRef *node, VSFrameContext *frameCtx) VS_NOEXCEPT; /* only use inside a filter's getframe function */ 277 | void (VS_CC *queryCompletedFrame)(VSNodeRef **node, int *n, VSFrameContext *frameCtx) VS_NOEXCEPT; /* only use inside a filter's getframe function */ 278 | void (VS_CC *releaseFrameEarly)(VSNodeRef *node, int n, VSFrameContext *frameCtx) VS_NOEXCEPT; /* only use inside a filter's getframe function */ 279 | 280 | int (VS_CC *getStride)(const VSFrameRef *f, int plane) VS_NOEXCEPT; 281 | const uint8_t *(VS_CC *getReadPtr)(const VSFrameRef *f, int plane) VS_NOEXCEPT; 282 | uint8_t *(VS_CC *getWritePtr)(VSFrameRef *f, int plane) VS_NOEXCEPT; 283 | 284 | VSFuncRef *(VS_CC *createFunc)(VSPublicFunction func, void *userData, VSFreeFuncData free, VSCore *core, const VSAPI *vsapi) VS_NOEXCEPT; 285 | void (VS_CC *callFunc)(VSFuncRef *func, const VSMap *in, VSMap *out, VSCore *core, const VSAPI *vsapi) VS_NOEXCEPT; /* core and vsapi arguments are completely ignored, they only remain to preserve ABI */ 286 | 287 | /* property access functions */ 288 | VSMap *(VS_CC *createMap)(void) VS_NOEXCEPT; 289 | void (VS_CC *freeMap)(VSMap *map) VS_NOEXCEPT; 290 | void (VS_CC *clearMap)(VSMap *map) VS_NOEXCEPT; 291 | 292 | const VSVideoInfo *(VS_CC *getVideoInfo)(VSNodeRef *node) VS_NOEXCEPT; 293 | void (VS_CC *setVideoInfo)(const VSVideoInfo *vi, int numOutputs, VSNode *node) VS_NOEXCEPT; 294 | const VSFormat *(VS_CC *getFrameFormat)(const VSFrameRef *f) VS_NOEXCEPT; 295 | int (VS_CC *getFrameWidth)(const VSFrameRef *f, int plane) VS_NOEXCEPT; 296 | int (VS_CC *getFrameHeight)(const VSFrameRef *f, int plane) VS_NOEXCEPT; 297 | const VSMap *(VS_CC *getFramePropsRO)(const VSFrameRef *f) VS_NOEXCEPT; 298 | VSMap *(VS_CC *getFramePropsRW)(VSFrameRef *f) VS_NOEXCEPT; 299 | 300 | int (VS_CC *propNumKeys)(const VSMap *map) VS_NOEXCEPT; 301 | const char *(VS_CC *propGetKey)(const VSMap *map, int index) VS_NOEXCEPT; 302 | int (VS_CC *propNumElements)(const VSMap *map, const char *key) VS_NOEXCEPT; 303 | char (VS_CC *propGetType)(const VSMap *map, const char *key) VS_NOEXCEPT; 304 | 305 | int64_t(VS_CC *propGetInt)(const VSMap *map, const char *key, int index, int *error) VS_NOEXCEPT; 306 | double(VS_CC *propGetFloat)(const VSMap *map, const char *key, int index, int *error) VS_NOEXCEPT; 307 | const char *(VS_CC *propGetData)(const VSMap *map, const char *key, int index, int *error) VS_NOEXCEPT; 308 | int (VS_CC *propGetDataSize)(const VSMap *map, const char *key, int index, int *error) VS_NOEXCEPT; 309 | VSNodeRef *(VS_CC *propGetNode)(const VSMap *map, const char *key, int index, int *error) VS_NOEXCEPT; 310 | const VSFrameRef *(VS_CC *propGetFrame)(const VSMap *map, const char *key, int index, int *error) VS_NOEXCEPT; 311 | VSFuncRef *(VS_CC *propGetFunc)(const VSMap *map, const char *key, int index, int *error) VS_NOEXCEPT; 312 | 313 | int (VS_CC *propDeleteKey)(VSMap *map, const char *key) VS_NOEXCEPT; 314 | int (VS_CC *propSetInt)(VSMap *map, const char *key, int64_t i, int append) VS_NOEXCEPT; 315 | int (VS_CC *propSetFloat)(VSMap *map, const char *key, double d, int append) VS_NOEXCEPT; 316 | int (VS_CC *propSetData)(VSMap *map, const char *key, const char *data, int size, int append) VS_NOEXCEPT; 317 | int (VS_CC *propSetNode)(VSMap *map, const char *key, VSNodeRef *node, int append) VS_NOEXCEPT; 318 | int (VS_CC *propSetFrame)(VSMap *map, const char *key, const VSFrameRef *f, int append) VS_NOEXCEPT; 319 | int (VS_CC *propSetFunc)(VSMap *map, const char *key, VSFuncRef *func, int append) VS_NOEXCEPT; 320 | 321 | int64_t (VS_CC *setMaxCacheSize)(int64_t bytes, VSCore *core) VS_NOEXCEPT; 322 | int (VS_CC *getOutputIndex)(VSFrameContext *frameCtx) VS_NOEXCEPT; 323 | VSFrameRef *(VS_CC *newVideoFrame2)(const VSFormat *format, int width, int height, const VSFrameRef **planeSrc, const int *planes, const VSFrameRef *propSrc, VSCore *core) VS_NOEXCEPT; 324 | void (VS_CC *setMessageHandler)(VSMessageHandler handler, void *userData) VS_NOEXCEPT; 325 | int (VS_CC *setThreadCount)(int threads, VSCore *core) VS_NOEXCEPT; 326 | 327 | const char *(VS_CC *getPluginPath)(const VSPlugin *plugin) VS_NOEXCEPT; 328 | 329 | /* api 3.1 */ 330 | const int64_t *(VS_CC *propGetIntArray)(const VSMap *map, const char *key, int *error) VS_NOEXCEPT; 331 | const double *(VS_CC *propGetFloatArray)(const VSMap *map, const char *key, int *error) VS_NOEXCEPT; 332 | 333 | int (VS_CC *propSetIntArray)(VSMap *map, const char *key, const int64_t *i, int size) VS_NOEXCEPT; 334 | int (VS_CC *propSetFloatArray)(VSMap *map, const char *key, const double *d, int size) VS_NOEXCEPT; 335 | 336 | /* api 3.4 */ 337 | void (VS_CC *logMessage)(int msgType, const char *msg) VS_NOEXCEPT; 338 | }; 339 | 340 | VS_API(const VSAPI *) getVapourSynthAPI(int version) VS_NOEXCEPT; 341 | 342 | #endif /* VAPOURSYNTH_H */ 343 | --------------------------------------------------------------------------------