pax_global_header00006660000000000000000000000064134623157070014522gustar00rootroot0000000000000052 comment=d25a91c18beb69cb230f9ce2d2072b3359b71d8c flvmeta-1.2.2/000077500000000000000000000000001346231570700131625ustar00rootroot00000000000000flvmeta-1.2.2/.gitignore000066400000000000000000000003731346231570700151550ustar00rootroot00000000000000# Object files *.o *.obj # Libraries *.lib *.a # Shared objects (inc. Windows DLLs) *.dll *.so # Executables *.exe *.out # CMake CMakeCache.txt CMakeFiles Makefile cmake_install.cmake install_manifest.txt /*build* # QTCreator CMakeLists.txt.user flvmeta-1.2.2/.travis.yml000066400000000000000000000004431346231570700152740ustar00rootroot00000000000000language: c os: - linux - osx addons: apt: packages: - check - pandoc before_install: - if [ $TRAVIS_OS_NAME == osx ]; then brew update && brew install check; fi script: - mkdir build - cd build - cmake .. - make - ctest -V compiler: - clang - gcc flvmeta-1.2.2/CHANGELOG.md000066400000000000000000000165661346231570700150110ustar00rootroot00000000000000# Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [1.2.2] - 2019-05-01 ### Fixed - Fixed heap overflow in AVC resolution parsing. - Fixed invalid memory access in metadata name parsing. - Fixed invalid memory access when reading invalid AMF date. ## [1.2.1] - 2016-08-09 ### Fixed - Fixed spelling in documentation. - Fixed changelog. ## [1.2.0] - 2016-08-08 ### Added - Added dumping of AVC and AAC packet information. - Added unit tests to the continuous integration system. ### Changed - Switched to Semver 2.0 as versioning scheme. - Improved 64-bit and file handling compatibility. - Improved FLV file checking. ### Removed - Removed support for the autotools build system. ### Fixed - Fixed timestamp distance computation in check. - Fixed installation instructions for CMake. - Fixed JSON output of non-finite numbers. - Fixed number printing. ## [1.1.2] - 2013-08-04 ### Added - Added JSON as output format for check reports. - Added support for the Travis continuous integration platform. ### Removed - Removed man file from Windows binary packages. ### Fixed - Fixed automake distribution of the CmakeLists.txt file used to build the man page. - Fixed inconsistencies in FLVMeta product spelling. ## [1.1.1] - 2013-05-09 ### Changed - Improved file duration detection. - Updated copyright notices to 2013. ### Fixed - Fixed FLVMeta product spelling. ## [1.1.0.1] - 2012-10-18 ### Fixed - Fixed spelling errors in the man page. ## [1.1.0] - 2012-05-03 Beta release. All features should be considered relatively stable unless explicitely stated otherwise. ### Added - Added proper command line handling and help. - Added the possibility to overwrite the input file when the output file is not specified or when both files are physically the same. - Added support for CMake builds in addition to autotools. It is now the official way to build flvmeta on Windows. - Added metadata and full file dumping, integrating former flvdump functionality into flvmeta. - Added support for XML, YAML, and JSON formats for dumping. - Added XML schemas describing the various formats used by flvmeta. - Added a file checking feature. - Added the possibility to print output file metadata after a successful update using one of the supported formats. - Added a feature to insert custom metadata strings while updating. - Added an option to disable insertion of the onLastSecond event. - Added an option to preserve existing metadata tags if possible. - Added an option to fix invalid tags while updating (this is a highly experimental feature, should be used with caution) - Added an option to ignore invalid tags as much as possible instead of exiting at the first error detected. - Added an option to reset the file timestamps in order to correctly start at zero, for example if the file has been incorrectly split by buggy tools. - Added an option to display informative messages while processing (not quite exhaustive for the moment). ### Changed - Changed keyframe index generation so only the first keyframe for a given timestamp will be indexed. This behaviour can be overriden with the --all-keyframes/-k option. ## [1.0.11] - 2010-01-25 ### Fixed - Fixed video resolution detection when the first video frame is not a keyframe. - Fixed invalid timestamp handling in the case of decreasing timestamps. - Fixed AVC resolution computation when frame cropping rectangle is used. - Fixed handling of files with a non-zero starting timestamp. - Fixed datasize tag computation so only metadata are taken into account. ## [1.0.10] - 2009-09-02 ### Fixed - Fixed amf_data_dump numeric format. - Fixed extended timestamp handling. - Fixed video resolution detection causing a crash in the case the video tag data body has less data than required. ## [1.0.9] - 2009-06-23 ### Fixed - Fixed large file support so it will work for files bigger than 4GB. - Fixed date handling in AMF according to the official spec. - Fixed extended timestamp handling. - Fixed a bug causing reading invalid tags to potentially lead to memory overflow and creation of invalid files. ## [1.0.8] - 2009-05-08 ### Added - Added support for arbitrary large files (2GB+). - Added support for AVC (H.264) video resolution detection. - Added support for the Speex codec and rarely met video frame types. ### Fixed - Fixed a bug where two consecutive tags of different types and with decreasing timestamps would cause extended timestamps to be incorrectly used for the next tags. - Fixed a bug where zero would be used as video height and width if video resolution could not be detected. - Fixed a bug causing flvdump to crash after reading invalid tags. Flvdump now exits after the first invalid tag read. ## [1.0.7] - 2008-09-25 ### Added - Added support for extended timestamps. Now flvmeta can read and write FLV files longer than 4:39:37, as well as fix files with incorrect timestamps. - Added support for all codecs from the official specification. ### Fixed - Fixed a bug causing flvdump to lose track of tags in case of invalid metadata. ## [1.0.6] - 2008-05-28 ### Fixed - Fixed a flvdump crash under Linux caused by incorrect string handling. ## [1.0.5] - 2008-04-03 ### Added - Added support for the AMF NULL type. ### Changed - Simplified the AMF parser/writer. ### Fixed - Fixed a bug in the video size detection for VP60. ## [1.0.4] - 2008-01-04 ### Changed - Modified flvdump to make it more tolerant to malformed files. ### Fixed - Fixed a bug where some data tags wouldn't be written. - Fixed a date computation bug related to daylight saving. ## [1.0.3] - 2007-10-21 ### Fixed - Fixed major bugs in the AMF parser/writer. - Fixed a bug in the video size detection for VP6 alpha. - Fixed minor bugs. ## [1.0.2] - 2007-09-30 ### Fixed - Fixed issues on 64-bits architectures. - Fixed "times" metadata tag, which was incorrectly written as "timestamps". - Fixed audio delay computation. ## [1.0.1] - 2007-09-25 ### Fixed - Fixed a critical bug where file size and offsets would not be correctly computed if the input file did not have an onMetaData event. - Audio related metadata are not added anymore if the FLV file has no audio data. ## [1.0] - 2007-09-21 This is the first public release. [1.2.2]: https://github.com/noirotm/flvmeta/releases/tag/v1.2.2 [1.2.1]: https://github.com/noirotm/flvmeta/releases/tag/v1.2.1 [1.2.0]: https://github.com/noirotm/flvmeta/releases/tag/v1.2.0 [1.1.2]: https://github.com/noirotm/flvmeta/releases/tag/v1.1.2 [1.1.1]: https://github.com/noirotm/flvmeta/releases/tag/v1.1.1 [1.1.0.1]: https://github.com/noirotm/flvmeta/releases/tag/v1.1.0.1 [1.1.0]: https://github.com/noirotm/flvmeta/releases/tag/v1.1.0 [1.0.11]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.11 [1.0.10]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.10 [1.0.9]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.9 [1.0.8]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.8 [1.0.7]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.7 [1.0.6]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.6 [1.0.5]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.5 [1.0.4]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.4 [1.0.3]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.3 [1.0.2]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.2 [1.0.1]: https://github.com/noirotm/flvmeta/releases/tag/v1.0.1 [1.0]: https://github.com/noirotm/flvmeta/releases/tag/v1.0 flvmeta-1.2.2/CMakeLists.txt000066400000000000000000000111631346231570700157240ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.6) project(flvmeta C) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) set(FLVMETA_VERSION "1.2.2") set(FLVMETA_RELEASE yes) # check whether we are building a release or a git snapshot if(NOT FLVMETA_RELEASE) # check for git find_program(GIT "git") mark_as_advanced(GIT) # check whether we are in a git repository find_file(DOT_GIT ".git" ${CMAKE_SOURCE_DIR}) mark_as_advanced(DOT_GIT) if(GIT AND DOT_GIT) # retrieve current revision execute_process( COMMAND "${GIT}" rev-parse --short HEAD WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_RELEASE OUTPUT_STRIP_TRAILING_WHITESPACE ) set(FLVMETA_VERSION "${FLVMETA_VERSION}-dev+g${GIT_RELEASE}") endif(GIT AND DOT_GIT) endif(NOT FLVMETA_RELEASE) # generic variables set(PACKAGE "flvmeta") set(PACKAGE_NAME ${PACKAGE}) set(PACKAGE_BUGREPORT "flvmeta-discussion@googlegroups.com") set(PACKAGE_VERSION "${FLVMETA_VERSION}") set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") # build options set( FLVMETA_USE_SYSTEM_LIBYAML FALSE CACHE BOOL "Link flvmeta to the installed version of libyaml" ) #platform tests include(CheckFunctionExists) include(CheckSymbolExists) include(CheckIncludeFile) include(CheckTypeSize) include(TestBigEndian) check_include_file(sys/types.h HAVE_SYS_TYPES_H) check_include_file(stdint.h HAVE_STDINT_H) check_include_file(stddef.h HAVE_STDDEF_H) check_include_file(inttypes.h HAVE_INTTYPES_H) check_type_size("double" SIZEOF_DOUBLE) check_type_size("float" SIZEOF_FLOAT) check_type_size("long double" SIZEOF_LONG_DOUBLE) check_type_size("long" SIZEOF_LONG) check_type_size("long long" SIZEOF_LONG_LONG) # MSVC before VS 2010 did not have stdint.h if(MSVC AND NOT HAVE_STDINT_H) set(int16_t 1) set(int32_t 1) set(int64_t 1) set(int8_t 1) set(uint16_t 1) set(uint32_t 1) set(uint64_t 1) set(uint8_t 1) endif(MSVC AND NOT HAVE_STDINT_H) test_big_endian(IS_BIGENDIAN) if(IS_BIGENDIAN) set(WORDS_BIGENDIAN 1) endif(IS_BIGENDIAN) # C99 isfinite support check_symbol_exists("isfinite" math.h HAVE_ISFINITE) # large file support check_function_exists("fseeko" HAVE_FSEEKO) if(HAVE_FSEEKO) execute_process( COMMAND getconf LFS_CFLAGS OUTPUT_VARIABLE LFS_CFLAGS ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE ) if(LFS_CFLAGS) add_definitions(${LFS_CFLAGS}) set(CMAKE_REQUIRED_FLAGS ${LFS_CFLAGS}) endif(LFS_CFLAGS) if(WIN32) add_definitions(-D_FILE_OFFSET_BITS=64) set(CMAKE_REQUIRED_FLAGS -D_FILE_OFFSET_BITS=64) endif(WIN32) check_type_size("off_t" SIZEOF_OFF_T) endif(HAVE_FSEEKO) # configuration file configure_file(config-cmake.h.in ${CMAKE_BINARY_DIR}/config.h) include_directories(${CMAKE_BINARY_DIR}) add_definitions(-DHAVE_CONFIG_H) # Visual C++ specific configuration if(MSVC) # use static library set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT") # C runtime deprecation in Visual C++ 2005 and later add_definitions(-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) endif(MSVC) # installation set(CPACK_PACKAGE_VENDOR "Marc Noirot ") set(CPACK_PACKAGE_VERSION ${FLVMETA_VERSION}) set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE.md) set(CPACK_RESOURCE_FILE_README ${CMAKE_SOURCE_DIR}/README.md) set(CPACK_SOURCE_IGNORE_FILES "/*build*;/CVS/;/\\\\.svn/;/\\\\.bzr/;/\\\\.hg/;/\\\\.git/;\\\\.swp$;\\\\.#;/#") set(CPACK_SOURCE_PACKAGE_FILE_NAME "flvmeta-${PACKAGE_VERSION}-src") if(WIN32) if (CMAKE_CL_64) set(EXECUTABLE_TYPE "64") else(CMAKE_CL_64) set(EXECUTABLE_TYPE "32") endif(CMAKE_CL_64) set(CPACK_PACKAGE_FILE_NAME "flvmeta-${PACKAGE_VERSION}-win${EXECUTABLE_TYPE}") set(CPACK_GENERATOR ZIP) install(FILES ${CMAKE_SOURCE_DIR}/README.md DESTINATION . RENAME Readme.txt) install(FILES ${CMAKE_SOURCE_DIR}/LICENSE.md DESTINATION . RENAME License.txt) install(FILES ${CMAKE_SOURCE_DIR}/CHANGELOG.md DESTINATION . RENAME Changelog.txt) else(WIN32) set(CPACK_PACKAGE_FILE_NAME "flvmeta-${PACKAGE_VERSION}") endif(WIN32) include(CPack) add_subdirectory(src) add_subdirectory(man) # check unit testing framework find_package(Check) # Enable unit testing if Check has been found if(CHECK_FOUND) set(CMAKE_REQUIRED_INCLUDES ${CHECK_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${CHECK_LIBRARIES}) set(CMAKE_REQUIRED_FLAGS -L${CHECK_LIBRARY_DIRS}) check_symbol_exists(ck_assert_double_eq check.h HAVE_CK_ASSERT_DOUBLE_EQ) if(HAVE_CK_ASSERT_DOUBLE_EQ) # tests enable_testing() add_subdirectory(tests) endif(HAVE_CK_ASSERT_DOUBLE_EQ) endif(CHECK_FOUND) flvmeta-1.2.2/INSTALL.md000066400000000000000000000177131346231570700146230ustar00rootroot00000000000000# How to Build FLVMeta with CMake Copyright (c) 2012-2016 Marc Noirot ## WHAT YOU NEED CMake version 2.6 or later installed on your system. ### HOW TO INSTALL: Linux distributions: shell> sudo apt-get install cmake The above works on Debian/Ubuntu based distributions. On others, the command line needs to be modified to e.g `yum install` on Fedora or `zypper install` on OpenSUSE. OpenSolaris: shell> pfexec pkgadd install SUNWcmake Windows and Mac OSX: Download and install the latest distribution from [http://www.cmake.org/cmake/resources/software.html](http://www.cmake.org/cmake/resources/software.html). On Windows, download installer exe file and run it. On MacOS, download the .dmg image and open it. Other Unixes: Precompiled packages for other Unix flavors (HPUX, AIX) are available from [http://www.cmake.org/cmake/resources/software.html](http://www.cmake.org/cmake/resources/software.html). Alternatively, you can build from source, source package is also available on CMake download page. ## Compiler Tools You will need a working compiler and make utility on your OS. On Windows, install Visual Studio (Express editions will work too). On Mac OSX, install Xcode tools. ## Build Ensure that compiler and cmake are in PATH. The following description assumes that current working directory is the source directory. Generic build on Unix, using "Unix Makefiles" generator: shell>cmake . shell>make Note: by default, cmake builds are less verbose than automake builds. Use `make VERBOSE=1` if you want to see add command lines for each compiled source. Windows, using "Visual Studio 9 2008" generator: shell>cmake . -G "Visual Studio 9 2008" shell>devenv flvmeta.sln /build /relwithdebinfo (alternatively, open flvmeta.sln and build using the IDE) Windows, using "NMake Makefiles" generator: shell>cmake . -G "NMake Makefiles" shell>nmake Mac OSX build with Xcode: shell>cmake . -G Xcode shell>xcodebuild -configuration Relwithdebinfo (alternatively, open flvmeta.xcodeproj and build using the IDE) Command line build with CMake 2.8: After creating project with `cmake -G` as above, issue cmake . --build this works with any CMake generator. For Visual Studio and Xcode you might want to add an extra configuration parameter, to avoid building all configurations. cmake . --build --config Relwithdebinfo ## Building "out-of-source" Building out-of-source provides additional benefits. For example it allows to build both Release and Debug configurations using the single source tree.Or build the same source with different version of the same compiler or with different compilers. Also you will prevent polluting the source tree with the objects and binaries produced during the make. Here is an example on how to do it (generic Unix), assuming the source tree is in directory named src and the current working directory is source root. shell>mkdir ../build # build directory is called build shell>cd ../build shell>cmake ../src Note: if a directory was used for in-source build, out-of-source will not work. To reenable out-of-source build, remove `/CMakeCache.txt` file. ## Configuration parameters The procedure above will build with default configuration. Let's say you want to change the configuration parameters and use the version of libyaml installed on your system instead of the one provided with FLVMeta. 1)You can provide parameters on the command line, like shell> cmake . -DFLVMETA_USE_SYSTEM_LIBYAML=1 This can be done during the initial configuration or any time later. Note, that parameters are "sticky", that is they are remembered in the CMake cache (CMakeCache.txt file in the build directory) 2) Configuration using cmake-gui (Windows, OSX, or Linux with cmake-gui installed) From the build directory, issue shell> cmake-gui . - Check the `FLVMETA_USE_SYSTEM_LIBYAML` checkbox - Click on "Configure" button - Click on "Generate" button - Close cmake-gui Then type: shell> make 3)Using ccmake (Unix) ccmake is curses-based GUI application that provides the same functionality as cmake-gui. It is less user-friendly compared to cmake-gui but works also on exotic Unixes like HPUX, AIX or Solaris. The most important parameter from a developer's point of view is probably `CMAKE_BUILD_TYPE` (this allows to build server with debug tracing library and with debug compile flags). After changing the configuration, recompile using shell> make ## Listing configuration parameters shell> cmake -L Gives a brief overview of important configuration parameters (dump to stdout) shell> cmake -LH Does the same but also provides a short help text for each parameter. shell> cmake -LAH Dumps all config parameters (including advanced) to the standard output. ## Packaging ### Binary distribution Packaging in form of tar.gz archives (or .zip on Windows) is also supported To create a tar.gz package, 1) If you're using "generic" Unix build with makefiles shell> make package this will create a tar.gz file in the top level build directory. 2) On Windows, using "NMake Makefiles" generator shell> nmake package 3) On Windows, using "Visual Studio" generator shell> devenv flvmeta.sln /build relwithdebinfo /project package Note On Windows, 7Zip or Winzip must be installed and 7z.exe, rsp, or winzip.exe need to be in the PATH. Another way to build packages is calling cpack executable directly like shell> cpack -G TGZ --config CPackConfig.cmake (-G TGZ is for tar.gz generator, there is also -GZIP) ### Source distribution `make package_source` target is provided. ## Additional Make targets: "make install" AND "make test" install target also provided for Makefile based generators. Installation directory can be controlled using configure-time parameter `CMAKE_INSTALL_PREFIX` (default is /usr/local. It is also possible to install to non-configured directory, using shell> make install DESTDIR="/some/absolute/path" "make test" runs unit tests (uses CTest for it) ## For Programmers: writing platform checks If you modify FLVMeta source and want to add a new platform check,please read [http://www.vtk.org/Wiki/CMake_HowToDoPlatformChecks](http://www.vtk.org/Wiki/CMake_HowToDoPlatformChecks) first. Bigger chunks of functionality, for example non-trivial macros are implemented in files /cmake subdirectory. For people with autotools background, it is important to remember CMake does not provide autoheader functionality. That is, when you add a check CHECK_FUNCTION_EXISTS(foo HAVE_FOO) to CMakeLists.txt, then you will also need to add #cmakedefine HAVE_FOO 1 to config-cmake.h.in. ## Troubleshooting platform checks If you suspect that a platform check returned wrong result, examine `/CMakeFiles/CMakeError.log` and `/CMakeFiles/CMakeOutput.log`. These files they contain compiler command line, and exact error messages. ## Troubleshooting CMake code While there are advanced flags for cmake like `-debug-trycompile` and `--trace`, a simple and efficient way to debug to add MESSAGE("interesting variable=${some_invariable}") to the interesting places in CMakeLists.txt Tips: - When using Makefile generator it is easy to examine which compiler flags are used to build. For example, compiler flags for flvmeta are in `/src/CMakeFiles/flvmeta.dir/flags.make` and the linker command line is in `/src/CMakeFiles/flvmeta.dir/link.txt` - CMake caches results of platform checks in CMakeCache.txt. It is a nice feature because tests do not rerun when reconfiguring (e.g when a new test was added).The downside of caching is that when a platform test was wrong and was later corrected, the cached result is still used. If you encounter this situation, which should be a rare occasion, you need either to remove the offending entry from CMakeCache.txt (if test was for `HAVE_FOO`, remove lines containing `HAVE_FOO` from CMakeCache.txt) or just remove the cache file. flvmeta-1.2.2/LICENSE.md000066400000000000000000000355061346231570700145770ustar00rootroot00000000000000The GNU General Public License, Version 2, June 1991 (GPLv2) ============================================================ > Copyright (C) 1989, 1991 Free Software Foundation, Inc. > 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble -------- The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. Terms And Conditions For Copying, Distribution And Modification --------------------------------------------------------------- **0.** This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. **1.** You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. **2.** You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: * **a)** You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. * **b)** You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. * **c)** If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. **3.** You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: * **a)** Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, * **b)** Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, * **c)** Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. **4.** You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. **5.** You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. **6.** Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. **7.** If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. **8.** If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. **9.** The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. **10.** If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. No Warranty ----------- **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.flvmeta-1.2.2/README.md000066400000000000000000000065151346231570700144500ustar00rootroot00000000000000# FLVMeta - FLV Metadata Editor [![Build Status](https://api.travis-ci.org/noirotm/flvmeta.svg?branch=master)](https://travis-ci.org/noirotm/flvmeta) [![Gitter chat](https://badges.gitter.im/noirotm/flvmeta.svg)](https://gitter.im/noirotm/flvmeta) ## About flvmeta is a command-line utility aimed at manipulating Adobe(tm) Flash Video files (FLV), through several commands, only one of which can be used for each invocation of the program. ## Features ### Metadata injection This program has the ability to compute and inject a variety of values in the `onMetaData` event tag, including keyframe indices used by most video players to allow random-access seeking, notably for HTTP pseudo-streamed files via a server-side module, by having the client send the file offset looked up for the nearest desired keyframe. Tools such as flvmeta must be used in the case the initial encoding process is unable to inject those metadata. It can also optionally inject the `onLastSecond` event, used to signal the end of playback, for example to revert the player software to a 'stopped' state. ### File information and metadata dumping flvmeta also has the ability to dump metadata and full file information to standard output, in a variety of textual output formats, including XML, YAML, and JSON. ### File validity checking Finally, the program can analyze FLV files to detect potential problems and errors, and generate a textual report in a raw format, or in XML. It has the ability to detect more than a hundred problems, going from harmless to potentially unplayable, using real world encountered issues. ## Performance flvmeta can operate on arbitrarily large files, and can handle FLV files using extended (32-bit) timestamps. It can guess video frame dimensions for all known video codecs supported by the official FLV specification. Its memory usage remains minimal, as it uses a two-pass reading algorithm which permits the computation of all necessary tags without loading anything more than the file's tags headers in memory. ## Installation See the [INSTALL.md](INSTALL.md) file for build and installation instructions. ## Authors ### Main developer - Marc Noirot ### Contributors I would like to thank the following contributors: - Neutron Soutmun - spelling fixes, patches and Debian package maintenance - Eric Priou - support and MacOSX builds - Zou Guangxian - VP60 related bug fix - nicmail777@yahoo.com - malformed metadata related bug fix - Robert M. Hall, II - sample files to implement extended timestamp support - podawan@gmail.com - extended timestamp related bug report - Anton Gorodchanin - command line and AVC bugfixes ## Acknowledgements The FLVMeta source package includes and uses the following software: - the libyaml YAML parser and emitter ([http://pyyaml.org/wiki/LibYAML](http://pyyaml.org/wiki/LibYAML "LibYAML")). ## License FLVMeta is provided "as is" with no warranty. The exact terms under which you may use and (re)distribute this program are detailed in the GNU General Public License, in the file [LICENSE.md](LICENSE.md). See the [CHANGELOG.md](CHANGELOG.md) file for a description of major changes in this release. See the file TODO for ideas on how you could help us improve FLVMeta. flvmeta-1.2.2/TODO000066400000000000000000000010341346231570700136500ustar00rootroot00000000000000Full AMF0 support. Metadata injection from xml file spec. i18n support. The program does not have much output, mainly errors, so it should be easy to translate it in several languages. If you want to contribute, send an email to flvmeta-discussion@googlegroups.com. Complete verbose mode. Fix onLastSecond insertion, which should be before the last second. Stream extraction. Complete test suites. Batch mode to handle multiple input files in one invocation. Scripting environment for custom manipulation of FLV files and metadata. flvmeta-1.2.2/cmake/000077500000000000000000000000001346231570700142425ustar00rootroot00000000000000flvmeta-1.2.2/cmake/modules/000077500000000000000000000000001346231570700157125ustar00rootroot00000000000000flvmeta-1.2.2/cmake/modules/FindCheck.cmake000066400000000000000000000045751346231570700205450ustar00rootroot00000000000000# - Try to find the CHECK libraries # Once done this will define # # CHECK_FOUND - system has check # CHECK_INCLUDE_DIR - the check include directory # CHECK_LIBRARIES - check library # # This configuration file for finding libcheck is originally from # the opensync project. The originally was downloaded from here: # opensync.org/browser/branches/3rd-party-cmake-modules/modules/FindCheck.cmake # # Copyright (c) 2007 Daniel Gollub # Copyright (c) 2007 Bjoern Ricks # # Redistribution and use is allowed according to the terms of the New # BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. INCLUDE( FindPkgConfig ) # Take care about check.pc settings PKG_SEARCH_MODULE( CHECK check ) # Look for CHECK include dir and libraries IF( NOT CHECK_FOUND ) IF ( CHECK_INSTALL_DIR ) MESSAGE ( STATUS "Using override CHECK_INSTALL_DIR to find check" ) SET ( CHECK_INCLUDE_DIR "${CHECK_INSTALL_DIR}/include" ) SET ( CHECK_INCLUDE_DIRS "${CHECK_INCLUDE_DIR}" ) FIND_LIBRARY( CHECK_LIBRARY NAMES check PATHS "${CHECK_INSTALL_DIR}/lib" ) FIND_LIBRARY( COMPAT_LIBRARY NAMES compat PATHS "${CHECK_INSTALL_DIR}/lib" ) SET ( CHECK_LIBRARIES "${CHECK_LIBRARY}" "${COMPAT_LIBRARY}" ) ELSE ( CHECK_INSTALL_DIR ) FIND_PATH( CHECK_INCLUDE_DIR check.h ) FIND_LIBRARY( CHECK_LIBRARIES NAMES check ) ENDIF ( CHECK_INSTALL_DIR ) IF ( CHECK_INCLUDE_DIR AND CHECK_LIBRARIES ) SET( CHECK_FOUND 1 ) IF ( NOT Check_FIND_QUIETLY ) MESSAGE ( STATUS "Found CHECK: ${CHECK_LIBRARIES}" ) ENDIF ( NOT Check_FIND_QUIETLY ) ELSE ( CHECK_INCLUDE_DIR AND CHECK_LIBRARIES ) IF ( Check_FIND_REQUIRED ) MESSAGE( FATAL_ERROR "Could NOT find CHECK" ) ELSE ( Check_FIND_REQUIRED ) IF ( NOT Check_FIND_QUIETLY ) MESSAGE( STATUS "Could NOT find CHECK" ) ENDIF ( NOT Check_FIND_QUIETLY ) ENDIF ( Check_FIND_REQUIRED ) ENDIF ( CHECK_INCLUDE_DIR AND CHECK_LIBRARIES ) ENDIF( NOT CHECK_FOUND ) # Hide advanced variables from CMake GUIs MARK_AS_ADVANCED( CHECK_INCLUDE_DIR CHECK_LIBRARIES ) flvmeta-1.2.2/cmake/modules/FindLibYAML.cmake000066400000000000000000000007541346231570700207140ustar00rootroot00000000000000# CMake module to search for the libyaml library # (library for parsing YAML files) # If it's found it sets LIBYAML_FOUND to TRUE # and following variables are set: # LIBYAML_INCLUDE_DIR # LIBYAML_LIBRARY FIND_PATH(LIBYAML_INCLUDE_DIR NAMES yaml.h) FIND_LIBRARY(LIBYAML_LIBRARIES NAMES yaml libyaml) INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Yaml DEFAULT_MSG LIBYAML_LIBRARIES LIBYAML_INCLUDE_DIR) MARK_AS_ADVANCED(LIBYAML_INCLUDE_DIR LIBYAML_LIBRARIES)flvmeta-1.2.2/config-cmake.h.in000066400000000000000000000070041346231570700162640ustar00rootroot00000000000000/* Name of package */ #define PACKAGE "@PACKAGE@" /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@" /* Define to the full name of this package. */ #define PACKAGE_NAME "@PACKAGE_NAME@" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "@PACKAGE_STRING@" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "@PACKAGE_NAME@" /* Define to the version of this package. */ #define PACKAGE_VERSION "@PACKAGE_VERSION@" /* The size of `double', as computed by sizeof. */ #define SIZEOF_DOUBLE @SIZEOF_DOUBLE@ /* The size of `float', as computed by sizeof. */ #define SIZEOF_FLOAT @SIZEOF_FLOAT@ /* The size of `long double', as computed by sizeof. */ #define SIZEOF_LONG_DOUBLE @SIZEOF_LONG_DOUBLE@ /* The size of `long', as computed by sizeof. */ #define SIZEOF_LONG @SIZEOF_LONG@ /* The size of `long long', as computed by sizeof. */ #define SIZEOF_LONG_LONG @SIZEOF_LONG_LONG@ /* Version number of package */ #define VERSION "@PACKAGE_VERSION@" /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_TYPES_H /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDINT_H /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDDEF_H /* Define to 1 if you have the header file. */ #cmakedefine HAVE_INTTYPES_H /* Define to 1 if isfinite exists and is declared. */ #cmakedefine HAVE_ISFINITE /* Define to 1 if fseeko (and presumably ftello) exists and is declared. */ #cmakedefine HAVE_FSEEKO /* The size of `off_t', as computed by sizeof. */ #ifdef HAVE_FSEEKO # define SIZEOF_OFF_T @SIZEOF_OFF_T@ #endif /* Define to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel and VAX). */ #cmakedefine WORDS_BIGENDIAN /* Define to empty if `const' does not conform to ANSI C. */ /* #undef const */ /* Define to rpl_malloc if the replacement function should be used. */ /* #undef malloc */ /* Define to `unsigned int' if does not define. */ /* #undef size_t */ /* Define to the type of an integer type of width exactly 16 bits if such a type exists and the standard includes do not define it. */ #cmakedefine int16_t short int /* Define to the type of an integer type of width exactly 32 bits if such a type exists and the standard includes do not define it. */ #cmakedefine int32_t int /* Define to the type of an integer type of width exactly 64 bits if such a type exists and the standard includes do not define it. */ #cmakedefine int64_t long long int /* Define to the type of an integer type of width exactly 8 bits if such a type exists and the standard includes do not define it. */ #cmakedefine int8_t char /* Define to the type of an unsigned integer type of width exactly 16 bits if such a type exists and the standard includes do not define it. */ #cmakedefine uint16_t unsigned short int /* Define to the type of an unsigned integer type of width exactly 32 bits if such a type exists and the standard includes do not define it. */ #cmakedefine uint32_t unsigned int /* Define to the type of an unsigned integer type of width exactly 64 bits if such a type exists and the standard includes do not define it. */ #cmakedefine uint64_t unsigned long long int /* Define to the type of an unsigned integer type of width exactly 8 bits if such a type exists and the standard includes do not define it. */ #cmakedefine uint8_t unsigned char flvmeta-1.2.2/man/000077500000000000000000000000001346231570700137355ustar00rootroot00000000000000flvmeta-1.2.2/man/CMakeLists.txt000066400000000000000000000021521346231570700164750ustar00rootroot00000000000000# test for pandoc availability if(NOT DEFINED PANDOC) message(STATUS "Looking for pandoc") find_program(PANDOC pandoc) if(PANDOC) message(STATUS "Looking for pandoc - found") else(PANDOC) message(STATUS "Looking for pandoc - not found") endif(PANDOC) mark_as_advanced(PANDOC) endif(NOT DEFINED PANDOC) if(PANDOC) set(MAN_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/flvmeta.1.md) set(MAN_OUTPUT_DIR ${CMAKE_BINARY_DIR}/man/man1) set(MAN_FILE ${MAN_OUTPUT_DIR}/flvmeta.1) # build man file from markdown source add_custom_command( OUTPUT ${MAN_FILE} COMMAND ${CMAKE_COMMAND} -E make_directory ${MAN_OUTPUT_DIR} COMMAND ${PANDOC} -s ${MAN_SOURCE} -o ${MAN_FILE} MAIN_DEPENDENCY ${MAN_SOURCE} ) # add custom target, built by default add_custom_target(man ALL DEPENDS ${MAN_FILE} SOURCES ${MAN_SOURCE} ) # install man file on systems having a manual directory include(GNUInstallDirs) if(CMAKE_INSTALL_MANDIR AND NOT WIN32) install(DIRECTORY ${MAN_OUTPUT_DIR} DESTINATION ${CMAKE_INSTALL_MANDIR} ) endif(CMAKE_INSTALL_MANDIR AND NOT WIN32) endif(PANDOC) flvmeta-1.2.2/man/flvmeta.1.md000066400000000000000000000253521346231570700160630ustar00rootroot00000000000000% flvmeta(1) flvmeta user manual % % January 2014 # NAME flvmeta - manipulate or extract metadata in Adobe Flash Video files # SYNOPSIS **flvmeta** *INPUT_FILE* **flvmeta** *INPUT_FILE* *OUTPUT_FILE* **flvmeta** `-D`|`--dump` [*options*] *INPUT_FILE* **flvmeta** `-F`|`--full-dump` [*options*] *INPUT_FILE* **flvmeta** `-C`|`--check` [*options*] *INPUT_FILE* **flvmeta** `-U`|`--update` [*options*] *INPUT_FILE* [*OUTPUT_FILE*] # DESCRIPTION **flvmeta** is a command-line utility aimed at manipulating Adobe(tm) Flash Video files (FLV), through several commands, only one of which can be used for each invocation of the program. It possesses the ability to compute and inject a variety of values in the _onMetaData_ event tag, including keyframe indices used by most video players to allow random-access seeking, notably for HTTP pseudo-streamed files via a server-side module, by having the client send the file offset looked up for the nearest desired keyframe. Tools such as **flvmeta** must be used in the case the initial encoding process is unable to inject those metadata. It can also optionnally inject the _onLastSecond_ event, used to signal the end of playback, for example to revert the player software to a 'stopped' state. **flvmeta** also has the ability to dump metadata and full file information to standard output, in a variety of textual output formats, including XML, YAML, and JSON. Finally, the program can analyze FLV files to detect potential problems and errors, and generate a textual report as a raw format, as JSON, or as XML. It has the ability to detect more than a hundred problems, going from harmless to potentially unplayable, using a few real world encountered issues. **flvmeta** can operate on arbitrarily large files, and can handle FLV files using extended (32-bit) timestamps. It can guess video frame dimensions for all known video codecs supported by the official FLV specification. Its memory usage remains minimal, as it uses a two-pass reading algorithm which permits the computation of all necessary tags without loading anything more than the file's tags headers in memory. # COMMANDS Only one command can be specified for an invocation of **flvmeta**. The chosen command determines the mode of execution of the program. By default, if no command is specified, **flvmeta** will implicitly choose the command to use according to the presence of *INPUT_FILE* and *OUTPUT_FILE*. If only *INPUT_FILE* is present, the **\--dump** command will be executed. If both *INPUT_FILE* and *OUTPUT_FILE* are present, the **\--update** command will be executed. Here is a list of the supported commands: ## -D, \--dump Dump a textual representation of the first _onMetaData_ tag found in *INPUT_FILE* to standard output. The default format is XML, unless specified otherwise. It is also possible to specify another event via the **\--event** option, such as _onLastSecond_. ## -F, \--full-dump Dump a textual representation of the whole contents of *INPUT_FILE* to standard output. The default format is XML, unless specified otherwise. ## -C, \--check Print a report to standard output listing warnings and errors detected in *INPUT_FILE*, as well as potential incompatibilities, and information about the codecs used in the file. The exit code will be set to a non-zero value if there is at least one error in the file. The output format can either be plain text, XML using the **\--xml** option, or JSON using the **\--json** option. It can also be disabled altogether using the **\--quiet** option if you are only interested in the exit status. Messages are divided into four specific levels of increasing importance: * **info**: informational messages that do not pertain to the file validity * **warning**: messages that inform of oddities to the flv format but that might not hamper file reading or playability, this is the default level * **error**: messages that inform of errors that might render the file impossible to play or stream correctly * **fatal**: messages that inform of errors that make further file reading impossible therefore ending parsing completely The **\--level** option allows **flvmeta** to limit the display of messages to a minimum level among those, for example if the user is only interested in error messages and above. Each message or message template presented to the user is identified by a specific code of the following format: `[level][topic][id]` * **level** is an upper-case letter that can be either I, W, E, F according to the aforementioned message levels * **topic** is a two-digit integer representing the general topic of the message * **id** is a unique three-digit identifier for the message, or message template if parameterized Messages can be related to the following topics : * **10** general flv file format * **11** file header * **12** previous tag size * **20** tag format * **30** tag types * **40** timestamps * **50** audio data * **51** audio codecs * **60** video data * **61** video codecs * **70** metadata * **80** AMF data * **81** keyframes * **82** cue points For example, represents a Warning in topic 51 with the id 050, which represents a warning message related to audio codecs, in that case to signal that an audio tag has an unknown codec. ## -U, \--update Update the given input file by inserting a computed _onMetaData_ tag. If *OUTPUT_FILE* is specified, it will be created or overwritten instead and the input file will not be modified. If the original file is to be updated, a temporary file will be created in the default temp directory of the platform, and it will be copied over the original file at the end of the operation. This is due to the fact that the output file is written while the original file is being read due to the two-pass method. The computed metadata contains among other data full keyframe information, in order to allow HTTP pseudo-streaming and random-access seeking in the file. By default, an _onLastSecond_ tag will be inserted, unless the **\--no-last-second** option is specified. Normally overwritten by the update process, the existing metadata found in the input file can be preserved by the **\--preserve** option. It is also possible to insert custom string values with the **\--add** option, which can be specified multiple times. By default, the update operation is performed without output, unless the **\--verbose** option is specified, or the **\--print-metadata** is used to print the newly written metadata to the standard output. # OPTIONS ## DUMP -d *FORMAT*, \--dump-format=*FORMAT* : specify dump format where *FORMAT* is 'xml' (default), 'json', 'raw', or 'yaml'. Also applicable for the **\--full-dump** command. -j, \--json : equivalent to **\--dump-format=json** -r, \--raw : equivalent to **\--dump-format=raw** -x, \--xml : equivalent to **\--dump-format=xml** -y, \--yaml : equivalent to **\--dump-format=yaml** -e *EVENT*, \--event=*EVENT* : specify the event to dump instead of _onMetaData_, for example _onLastSecond_ ## CHECK -l *LEVEL*, \--level=*LEVEL* : print only messages where level is at least *LEVEL*. The levels are, by ascending importance, 'info', 'warning' (default), 'error', or 'fatal'. -q, \--quiet : do not print messages, only return the status code -x, \--xml : generate an XML report instead of the default 'compiler-friendly' text -j, \--json : generate a JSON report instead of the default 'compiler-friendly' text ## UPDATE -m, \--print-metadata : print metadata to stdout after update using the format specified by the **\--format** option -a *NAME=VALUE*, \--add=*NAME=VALUE* : add a metadata string value to the output file. The name/value pair will be appended at the end of the _onMetaData_ tag. -s, \--no-lastsecond : do not create the *onLastSecond* tag -p, \--preserve : preserve input file existing *onMetadata* tags -f, \--fix : fix invalid tags from the input file -i, \--ignore : ignore invalid tags from the input file (the default behaviour is to stop the update process with an error) -t, \--reset-timestamps : reset timestamps so *OUTPUT_FILE* starts at zero. This has been added because some FLV files are produced by cutting bigger files, and the software doing the cutting does not resets the timestamps as required by the standard, which can cause playback issues. -k, --all-keyframes : index all keyframe tags, including duplicate timestamps ## GENERAL -v, \--verbose : display informative messages -V, \--version : print version information and exit -h, \--help : display help on the program usage and exit # FORMATS The various XML formats used by **flvmeta** are precisely described by the following XSD schemas: * http://schemas.flvmeta.org/flv.xsd: describes the general organization of FLV files * http://schemas.flvmeta.org/Amf0.xsd: describes an XML representation of the Adobe(TM) AMF0 serialization format * http://schemas.flvmeta.org/report.xsd: describes the XML output format of the **\--check** **\--xml** command # EXAMPLES **flvmeta example.flv** Prints the onMetadata tag contents of example.flv as XML output. **flvmeta example.flv out.flv** Creates a file named out.flv containing updated metadata and an onLastSecond tag from the exemple.flv file. **flvmeta \--check \--xml \--level=error example.flv** Checks the validity of the example.flv file and prints the error report to stdout in XML format, displaying only errors and fatal errors. **flvmeta \--full-dump \--yaml example.flv** Prints the full contents of example.flv as YAML format to stdout. **flvmeta \--update \--no-last-second \--show-metadata \--json example.flv** Performs an in-place update of example.flv by inserting computed onMetadata without an onLastSecond tag, and prints the newly inserted metadata on stdout as JSON. # EXIT STATUS * **0** flvmeta exited without error * **1** an error occurred when trying to open an input file * **2** the input file was not recognized as an FLV file * **3** an end-of-file condition was encountered unexpectedly * **4** a memory allocation error occurred during the run of the program * **5** an empty tag was encountered in an input file * **6** an error occurred when trying to open an output file * **7** an invalid tag was encountered in an input file * **8** an error was encountered while writing an output file * **9** the **\--check** command reported an invalid file (one or more errors) # BUGS **flvmeta** does not support encrypted FLV files yet. # AUTHOR Marc Noirot \ # COPYRIGHT Copyright 2007-2016 Marc Noirot This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # CONTACT Please report bugs to \ flvmeta-1.2.2/schemas/000077500000000000000000000000001346231570700146055ustar00rootroot00000000000000flvmeta-1.2.2/schemas/Amf0.xsd000066400000000000000000000063541346231570700161200ustar00rootroot00000000000000 flvmeta-1.2.2/schemas/TestAmf0.xml000066400000000000000000000017021346231570700167520ustar00rootroot00000000000000 flvmeta 1.0.8 flvmeta-1.2.2/schemas/TestFlv.xml000066400000000000000000000060211346231570700167150ustar00rootroot00000000000000 flvmeta 1.0.8 flvmeta-1.2.2/schemas/TestReport.xml000066400000000000000000000007101346231570700174400ustar00rootroot00000000000000 schmurk.flv 2010-03-05T14:22:52 flvmeta 1.2.0 invalid codec unknown file user flvmeta-1.2.2/schemas/flv.xsd000066400000000000000000000143141346231570700161170ustar00rootroot00000000000000 flvmeta-1.2.2/schemas/report.xsd000066400000000000000000000033741346231570700166470ustar00rootroot00000000000000 flvmeta-1.2.2/src/000077500000000000000000000000001346231570700137515ustar00rootroot00000000000000flvmeta-1.2.2/src/CMakeLists.txt000066400000000000000000000025271346231570700165170ustar00rootroot00000000000000set(flvmeta_src amf.c amf.h avc.c avc.h check.c check.h dump.c dump.h dump_json.c dump_json.h dump_raw.c dump_raw.h dump_xml.c dump_xml.h dump_yaml.c dump_yaml.h flv.c flv.h flvmeta.c flvmeta.h info.c info.h json.c json.h types.c types.h update.c update.h util.c util.h ${CMAKE_BINARY_DIR}/config.h ) # add support for getopt and gettext in windows if(WIN32) set(flvmeta_src ${flvmeta_src} compat/getopt1.c compat/getopt.c compat/getopt.h compat/gettext.h ) include_directories(compat) endif(WIN32) # static build if(WIN32) add_definitions(-DYAML_DECLARE_STATIC) endif(WIN32) add_executable(flvmeta ${flvmeta_src}) # libyaml if(FLVMETA_USE_SYSTEM_LIBYAML) # search for libyaml on the system, link with it find_package(LibYAML REQUIRED) target_include_directories(flvmeta PRIVATE ${LIBYAML_INCLUDE_DIR}) target_link_libraries(flvmeta ${LIBYAML_LIBRARIES}) else(FLVMETA_USE_SYSTEM_LIBYAML) # use bundled version of libyaml target_include_directories(flvmeta PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/libyaml) add_subdirectory(libyaml) target_link_libraries(flvmeta yaml) endif(FLVMETA_USE_SYSTEM_LIBYAML) if(WIN32) install( TARGETS flvmeta RUNTIME DESTINATION . ) else(WIN32) install( TARGETS flvmeta RUNTIME DESTINATION bin ) endif(WIN32) flvmeta-1.2.2/src/amf.c000066400000000000000000001077131346231570700146710ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "amf.h" /* function common to all array types */ static void amf_list_init(amf_list * list) { if (list != NULL) { list->size = 0; list->first_element = NULL; list->last_element = NULL; } } static amf_data * amf_list_push(amf_list * list, amf_data * data) { amf_node * node = (amf_node*)malloc(sizeof(amf_node)); if (node != NULL) { node->data = data; node->next = NULL; node->prev = NULL; if (list->size == 0) { list->first_element = node; list->last_element = node; } else { list->last_element->next = node; node->prev = list->last_element; list->last_element = node; } ++(list->size); return data; } return NULL; } static amf_data * amf_list_insert_before(amf_list * list, amf_node * node, amf_data * data) { if (node != NULL) { amf_node * new_node = (amf_node*)malloc(sizeof(amf_node)); if (new_node != NULL) { new_node->next = node; new_node->prev = node->prev; if (node->prev != NULL) { node->prev->next = new_node; node->prev = new_node; } else { list->first_element = new_node; } ++(list->size); new_node->data = data; return data; } } return NULL; } static amf_data * amf_list_insert_after(amf_list * list, amf_node * node, amf_data * data) { if (node != NULL) { amf_node * new_node = (amf_node*)malloc(sizeof(amf_node)); if (new_node != NULL) { new_node->next = node->next; new_node->prev = node; if (node->next != NULL) { node->next->prev = new_node; node->next = new_node; } else { list->last_element = new_node; } ++(list->size); new_node->data = data; return data; } } return NULL; } static amf_data * amf_list_delete(amf_list * list, amf_node * node) { amf_data * data = NULL; if (node != NULL) { if (node->next != NULL) { node->next->prev = node->prev; } if (node->prev != NULL) { node->prev->next = node->next; } if (node == list->first_element) { list->first_element = node->next; } if (node == list->last_element) { list->last_element = node->prev; } data = node->data; free(node); --(list->size); } return data; } static amf_data * amf_list_get_at(const amf_list * list, uint32 n) { if (n < list->size) { uint32 i; amf_node * node = list->first_element; for (i = 0; i < n; ++i) { node = node->next; } return node->data; } return NULL; } static amf_data * amf_list_pop(amf_list * list) { return amf_list_delete(list, list->last_element); } static amf_node * amf_list_first(const amf_list * list) { return list->first_element; } static amf_node * amf_list_last(const amf_list * list) { return list->last_element; } static void amf_list_clear(amf_list * list) { amf_node * tmp; amf_node * node = list->first_element; while (node != NULL) { amf_data_free(node->data); tmp = node; node = node->next; free(tmp); } list->size = 0; } static amf_list * amf_list_clone(const amf_list * list, amf_list * out_list) { amf_node * node; node = list->first_element; while (node != NULL) { amf_list_push(out_list, amf_data_clone(node->data)); node = node->next; } return out_list; } /* structure used to mimic a stream with a memory buffer */ typedef struct __buffer_context { byte * start_address; byte * current_address; size_t buffer_size; } buffer_context; /* callback function to mimic fread using a memory buffer */ static size_t buffer_read(void * out_buffer, size_t size, void * user_data) { buffer_context * ctxt = (buffer_context *)user_data; if (ctxt->current_address >= ctxt->start_address && ctxt->current_address + size <= ctxt->start_address + ctxt->buffer_size) { memcpy(out_buffer, ctxt->current_address, size); ctxt->current_address += size; return size; } else { return 0; } } /* callback function to mimic fwrite using a memory buffer */ static size_t buffer_write(const void * in_buffer, size_t size, void * user_data) { buffer_context * ctxt = (buffer_context *)user_data; if (ctxt->current_address >= ctxt->start_address && ctxt->current_address + size <= ctxt->start_address + ctxt->buffer_size) { memcpy(ctxt->current_address, in_buffer, size); ctxt->current_address += size; return size; } else { return 0; } } /* allocate an AMF data object */ amf_data * amf_data_new(byte type) { amf_data * data = (amf_data*)malloc(sizeof(amf_data)); if (data != NULL) { data->type = type; data->error_code = AMF_ERROR_OK; } return data; } /* read AMF data from buffer */ amf_data * amf_data_buffer_read(byte * buffer, size_t maxbytes) { buffer_context ctxt; ctxt.start_address = ctxt.current_address = buffer; ctxt.buffer_size = maxbytes; return amf_data_read(buffer_read, &ctxt); } /* write AMF data to buffer */ size_t amf_data_buffer_write(amf_data * data, byte * buffer, size_t maxbytes) { buffer_context ctxt; ctxt.start_address = ctxt.current_address = buffer; ctxt.buffer_size = maxbytes; return amf_data_write(data, buffer_write, &ctxt); } /* callback function to read data from a file stream */ static size_t file_read(void * out_buffer, size_t size, void * user_data) { return fread(out_buffer, sizeof(byte), size, (FILE *)user_data); } /* callback function to write data to a file stream */ static size_t file_write(const void * in_buffer, size_t size, void * user_data) { return fwrite(in_buffer, sizeof(byte), size, (FILE *)user_data); } /* load AMF data from a file stream */ amf_data * amf_data_file_read(FILE * stream) { return amf_data_read(file_read, stream); } /* write AMF data into a file stream */ size_t amf_data_file_write(const amf_data * data, FILE * stream) { return amf_data_write(data, file_write, stream); } /* read a number */ static amf_data * amf_number_read(amf_read_proc read_proc, void * user_data) { number64_be val; if (read_proc(&val, sizeof(number64_be), user_data) == sizeof(number64_be)) { return amf_number_new(swap_number64(val)); } else { return amf_data_error(AMF_ERROR_EOF); } } /* read a boolean */ static amf_data * amf_boolean_read(amf_read_proc read_proc, void * user_data) { uint8 val; if (read_proc(&val, sizeof(uint8), user_data) == sizeof(uint8)) { return amf_boolean_new(val); } else { return amf_data_error(AMF_ERROR_EOF); } } /* read a string */ static amf_data * amf_string_read(amf_read_proc read_proc, void * user_data) { uint16_be strsize; byte * buffer; if (read_proc(&strsize, sizeof(uint16_be), user_data) < sizeof(uint16_be)) { return amf_data_error(AMF_ERROR_EOF); } strsize = swap_uint16(strsize); if (strsize == 0) { return amf_string_new(NULL, 0); } buffer = (byte*)calloc(strsize, sizeof(byte)); if (buffer == NULL) { return NULL; } if (read_proc(buffer, strsize, user_data) == strsize) { amf_data * data = amf_string_new(buffer, strsize); free(buffer); return data; } else { free(buffer); return amf_data_error(AMF_ERROR_EOF); } } /* read an object */ static amf_data * amf_object_read(amf_read_proc read_proc, void * user_data) { amf_data * name; amf_data * element; byte error_code; amf_data * data; data = amf_object_new(); if (data == NULL) { return NULL; } while (1) { name = amf_string_read(read_proc, user_data); error_code = amf_data_get_error_code(name); if (error_code != AMF_ERROR_OK) { /* invalid name: error */ amf_data_free(name); amf_data_free(data); return amf_data_error(error_code); } element = amf_data_read(read_proc, user_data); error_code = amf_data_get_error_code(element); if (error_code == AMF_ERROR_END_TAG || error_code == AMF_ERROR_UNKNOWN_TYPE) { /* end tag or unknown element: end of data, exit loop */ amf_data_free(name); amf_data_free(element); break; } else if (error_code != AMF_ERROR_OK) { amf_data_free(name); amf_data_free(data); amf_data_free(element); return amf_data_error(error_code); } if (amf_object_add(data, (char *)amf_string_get_bytes(name), element) == NULL) { amf_data_free(name); amf_data_free(element); amf_data_free(data); return NULL; } else { amf_data_free(name); } } return data; } /* read an associative array */ static amf_data * amf_associative_array_read(amf_read_proc read_proc, void * user_data) { amf_data * name; amf_data * element; uint32_be size; byte error_code; amf_data * data; data = amf_associative_array_new(); if (data == NULL) { return NULL; } /* we ignore the 32 bits array size marker */ if (read_proc(&size, sizeof(uint32_be), user_data) < sizeof(uint32_be)) { amf_data_free(data); return amf_data_error(AMF_ERROR_EOF); } while(1) { name = amf_string_read(read_proc, user_data); error_code = amf_data_get_error_code(name); if (error_code != AMF_ERROR_OK) { /* invalid name: error */ amf_data_free(name); amf_data_free(data); return amf_data_error(error_code); } element = amf_data_read(read_proc, user_data); error_code = amf_data_get_error_code(element); if (amf_string_get_size(name) == 0 || error_code == AMF_ERROR_END_TAG || error_code == AMF_ERROR_UNKNOWN_TYPE) { /* end tag or unknown element: end of data, exit loop */ amf_data_free(name); amf_data_free(element); break; } else if (error_code != AMF_ERROR_OK) { amf_data_free(name); amf_data_free(data); amf_data_free(element); return amf_data_error(error_code); } if (amf_associative_array_add(data, (char *)amf_string_get_bytes(name), element) == NULL) { amf_data_free(name); amf_data_free(element); amf_data_free(data); return NULL; } else { amf_data_free(name); } } return data; } /* read an array */ static amf_data * amf_array_read(amf_read_proc read_proc, void * user_data) { size_t i; amf_data * element; byte error_code; amf_data * data; uint32 array_size; data = amf_array_new(); if (data == NULL) { return NULL; } if (read_proc(&array_size, sizeof(uint32), user_data) < sizeof(uint32)) { amf_data_free(data); return amf_data_error(AMF_ERROR_EOF); } array_size = swap_uint32(array_size); for (i = 0; i < array_size; ++i) { element = amf_data_read(read_proc, user_data); error_code = amf_data_get_error_code(element); if (error_code != AMF_ERROR_OK) { amf_data_free(element); amf_data_free(data); return amf_data_error(error_code); } if (amf_array_push(data, element) == NULL) { amf_data_free(element); amf_data_free(data); return NULL; } } return data; } /* read a date */ static amf_data * amf_date_read(amf_read_proc read_proc, void * user_data) { number64_be milliseconds; sint16_be timezone; if (read_proc(&milliseconds, sizeof(number64_be), user_data) == sizeof(number64_be) && read_proc(&timezone, sizeof(sint16_be), user_data) == sizeof(sint16_be)) { return amf_date_new(swap_number64(milliseconds), swap_sint16(timezone)); } else { return amf_data_error(AMF_ERROR_EOF); } } /* load AMF data from stream */ amf_data * amf_data_read(amf_read_proc read_proc, void * user_data) { byte type; if (read_proc(&type, sizeof(byte), user_data) < sizeof(byte)) { return amf_data_error(AMF_ERROR_EOF); } switch (type) { case AMF_TYPE_NUMBER: return amf_number_read(read_proc, user_data); case AMF_TYPE_BOOLEAN: return amf_boolean_read(read_proc, user_data); case AMF_TYPE_STRING: return amf_string_read(read_proc, user_data); case AMF_TYPE_OBJECT: return amf_object_read(read_proc, user_data); case AMF_TYPE_NULL: return amf_null_new(); case AMF_TYPE_UNDEFINED: return amf_undefined_new(); /*case AMF_TYPE_REFERENCE:*/ case AMF_TYPE_ASSOCIATIVE_ARRAY: return amf_associative_array_read(read_proc, user_data); case AMF_TYPE_ARRAY: return amf_array_read(read_proc, user_data); case AMF_TYPE_DATE: return amf_date_read(read_proc, user_data); /*case AMF_TYPE_SIMPLEOBJECT:*/ case AMF_TYPE_XML: case AMF_TYPE_CLASS: return amf_data_error(AMF_ERROR_UNSUPPORTED_TYPE); case AMF_TYPE_END: return amf_data_error(AMF_ERROR_END_TAG); /* end of composite object */ default: return amf_data_error(AMF_ERROR_UNKNOWN_TYPE); } } /* determines the size of the given AMF data */ size_t amf_data_size(const amf_data * data) { size_t s = 0; amf_node * node; if (data != NULL) { s += sizeof(byte); switch (data->type) { case AMF_TYPE_NUMBER: s += sizeof(number64_be); break; case AMF_TYPE_BOOLEAN: s += sizeof(uint8); break; case AMF_TYPE_STRING: s += sizeof(uint16) + (size_t)amf_string_get_size(data); break; case AMF_TYPE_OBJECT: node = amf_object_first(data); while (node != NULL) { s += sizeof(uint16) + (size_t)amf_string_get_size(amf_object_get_name(node)); s += (size_t)amf_data_size(amf_object_get_data(node)); node = amf_object_next(node); } s += sizeof(uint16) + sizeof(uint8); break; case AMF_TYPE_NULL: case AMF_TYPE_UNDEFINED: break; /*case AMF_TYPE_REFERENCE:*/ case AMF_TYPE_ASSOCIATIVE_ARRAY: s += sizeof(uint32); node = amf_associative_array_first(data); while (node != NULL) { s += sizeof(uint16) + (size_t)amf_string_get_size(amf_associative_array_get_name(node)); s += (size_t)amf_data_size(amf_associative_array_get_data(node)); node = amf_associative_array_next(node); } s += sizeof(uint16) + sizeof(uint8); break; case AMF_TYPE_ARRAY: s += sizeof(uint32); node = amf_array_first(data); while (node != NULL) { s += (size_t)amf_data_size(amf_array_get(node)); node = amf_array_next(node); } break; case AMF_TYPE_DATE: s += sizeof(number64) + sizeof(sint16); break; /*case AMF_TYPE_SIMPLEOBJECT:*/ case AMF_TYPE_XML: case AMF_TYPE_CLASS: case AMF_TYPE_END: break; /* end of composite object */ default: break; } } return s; } /* write a number */ static size_t amf_number_write(const amf_data * data, amf_write_proc write_proc, void * user_data) { number64 n = swap_number64(data->number_data); return write_proc(&n, sizeof(number64_be), user_data); } /* write a boolean */ static size_t amf_boolean_write(const amf_data * data, amf_write_proc write_proc, void * user_data) { return write_proc(&(data->boolean_data), sizeof(uint8), user_data); } /* write a string */ static size_t amf_string_write(const amf_data * data, amf_write_proc write_proc, void * user_data) { uint16 s; size_t w = 0; s = swap_uint16(data->string_data.size); w = write_proc(&s, sizeof(uint16_be), user_data); if (data->string_data.size > 0) { w += write_proc(data->string_data.mbstr, (size_t)(data->string_data.size), user_data); } return w; } /* write an object */ static size_t amf_object_write(const amf_data * data, amf_write_proc write_proc, void * user_data) { amf_node * node; size_t w = 0; uint16_be filler = swap_uint16(0); uint8 terminator = AMF_TYPE_END; node = amf_object_first(data); while (node != NULL) { w += amf_string_write(amf_object_get_name(node), write_proc, user_data); w += amf_data_write(amf_object_get_data(node), write_proc, user_data); node = amf_object_next(node); } /* empty string is the last element */ w += write_proc(&filler, sizeof(uint16_be), user_data); /* an object ends with 0x09 */ w += write_proc(&terminator, sizeof(uint8), user_data); return w; } /* write an associative array */ static size_t amf_associative_array_write(const amf_data * data, amf_write_proc write_proc, void * user_data) { amf_node * node; size_t w = 0; uint32_be s; uint16_be filler = swap_uint16(0); uint8 terminator = AMF_TYPE_END; s = swap_uint32(data->list_data.size) / 2; w += write_proc(&s, sizeof(uint32_be), user_data); node = amf_associative_array_first(data); while (node != NULL) { w += amf_string_write(amf_associative_array_get_name(node), write_proc, user_data); w += amf_data_write(amf_associative_array_get_data(node), write_proc, user_data); node = amf_associative_array_next(node); } /* empty string is the last element */ w += write_proc(&filler, sizeof(uint16_be), user_data); /* an object ends with 0x09 */ w += write_proc(&terminator, sizeof(uint8), user_data); return w; } /* write an array */ static size_t amf_array_write(const amf_data * data, amf_write_proc write_proc, void * user_data) { amf_node * node; size_t w = 0; uint32_be s; s = swap_uint32(data->list_data.size); w += write_proc(&s, sizeof(uint32_be), user_data); node = amf_array_first(data); while (node != NULL) { w += amf_data_write(amf_array_get(node), write_proc, user_data); node = amf_array_next(node); } return w; } /* write a date */ static size_t amf_date_write(const amf_data * data, amf_write_proc write_proc, void * user_data) { size_t w = 0; number64_be milli; sint16_be tz; milli = swap_number64(data->date_data.milliseconds); w += write_proc(&milli, sizeof(number64_be), user_data); tz = swap_sint16(data->date_data.timezone); w += write_proc(&tz, sizeof(sint16_be), user_data); return w; } /* write amf data to stream */ size_t amf_data_write(const amf_data * data, amf_write_proc write_proc, void * user_data) { size_t s = 0; if (data != NULL) { s += write_proc(&(data->type), sizeof(byte), user_data); switch (data->type) { case AMF_TYPE_NUMBER: s += amf_number_write(data, write_proc, user_data); break; case AMF_TYPE_BOOLEAN: s += amf_boolean_write(data, write_proc, user_data); break; case AMF_TYPE_STRING: s += amf_string_write(data, write_proc, user_data); break; case AMF_TYPE_OBJECT: s += amf_object_write(data, write_proc, user_data); break; case AMF_TYPE_NULL: case AMF_TYPE_UNDEFINED: break; /*case AMF_TYPE_REFERENCE:*/ case AMF_TYPE_ASSOCIATIVE_ARRAY: s += amf_associative_array_write(data, write_proc, user_data); break; case AMF_TYPE_ARRAY: s += amf_array_write(data, write_proc, user_data); break; case AMF_TYPE_DATE: s += amf_date_write(data, write_proc, user_data); break; /*case AMF_TYPE_SIMPLEOBJECT:*/ case AMF_TYPE_XML: case AMF_TYPE_CLASS: case AMF_TYPE_END: break; /* end of composite object */ default: break; } } return s; } /* data type */ byte amf_data_get_type(const amf_data * data) { return (data != NULL) ? data->type : AMF_TYPE_NULL; } /* error code */ byte amf_data_get_error_code(const amf_data * data) { return (data != NULL) ? data->error_code : AMF_ERROR_NULL_POINTER; } /* clone AMF data */ amf_data * amf_data_clone(const amf_data * data) { /* we copy data recursively */ if (data != NULL) { switch (data->type) { case AMF_TYPE_NUMBER: return amf_number_new(amf_number_get_value(data)); case AMF_TYPE_BOOLEAN: return amf_boolean_new(amf_boolean_get_value(data)); case AMF_TYPE_STRING: if (data->string_data.mbstr != NULL) { return amf_string_new((byte *)strdup((char *)amf_string_get_bytes(data)), amf_string_get_size(data)); } else { return amf_str(NULL); } case AMF_TYPE_NULL: return NULL; case AMF_TYPE_UNDEFINED: return NULL; /*case AMF_TYPE_REFERENCE:*/ case AMF_TYPE_OBJECT: case AMF_TYPE_ASSOCIATIVE_ARRAY: case AMF_TYPE_ARRAY: { amf_data * d = amf_data_new(data->type); if (d != NULL) { amf_list_init(&d->list_data); amf_list_clone(&data->list_data, &d->list_data); } return d; } case AMF_TYPE_DATE: return amf_date_new(amf_date_get_milliseconds(data), amf_date_get_timezone(data)); /*case AMF_TYPE_SIMPLEOBJECT:*/ case AMF_TYPE_XML: return NULL; case AMF_TYPE_CLASS: return NULL; } } return NULL; } /* free AMF data */ void amf_data_free(amf_data * data) { if (data != NULL) { switch (data->type) { case AMF_TYPE_NUMBER: break; case AMF_TYPE_BOOLEAN: break; case AMF_TYPE_STRING: if (data->string_data.mbstr != NULL) { free(data->string_data.mbstr); } break; case AMF_TYPE_NULL: break; case AMF_TYPE_UNDEFINED: break; /*case AMF_TYPE_REFERENCE:*/ case AMF_TYPE_OBJECT: case AMF_TYPE_ASSOCIATIVE_ARRAY: case AMF_TYPE_ARRAY: amf_list_clear(&data->list_data); break; case AMF_TYPE_DATE: break; /*case AMF_TYPE_SIMPLEOBJECT:*/ case AMF_TYPE_XML: break; case AMF_TYPE_CLASS: break; default: break; } free(data); } } /* dump AMF data into a stream as text */ void amf_data_dump(FILE * stream, const amf_data * data, int indent_level) { if (data != NULL) { amf_node * node; char datestr[128]; switch (data->type) { case AMF_TYPE_NUMBER: fprintf(stream, "%.12g", data->number_data); break; case AMF_TYPE_BOOLEAN: fprintf(stream, "%s", (data->boolean_data) ? "true" : "false"); break; case AMF_TYPE_STRING: fprintf(stream, "\'%.*s\'", data->string_data.size, data->string_data.mbstr); break; case AMF_TYPE_OBJECT: node = amf_object_first(data); fprintf(stream, "{\n"); while (node != NULL) { fprintf(stream, "%*s", (indent_level+1)*4, ""); amf_data_dump(stream, amf_object_get_name(node), indent_level+1); fprintf(stream, ": "); amf_data_dump(stream, amf_object_get_data(node), indent_level+1); node = amf_object_next(node); fprintf(stream, "\n"); } fprintf(stream, "%*s", indent_level*4 + 1, "}"); break; case AMF_TYPE_NULL: fprintf(stream, "null"); break; case AMF_TYPE_UNDEFINED: fprintf(stream, "undefined"); break; /*case AMF_TYPE_REFERENCE:*/ case AMF_TYPE_ASSOCIATIVE_ARRAY: node = amf_associative_array_first(data); fprintf(stream, "{\n"); while (node != NULL) { fprintf(stream, "%*s", (indent_level+1)*4, ""); amf_data_dump(stream, amf_associative_array_get_name(node), indent_level+1); fprintf(stream, " => "); amf_data_dump(stream, amf_associative_array_get_data(node), indent_level+1); node = amf_associative_array_next(node); fprintf(stream, "\n"); } fprintf(stream, "%*s", indent_level*4 + 1, "}"); break; case AMF_TYPE_ARRAY: node = amf_array_first(data); fprintf(stream, "[\n"); while (node != NULL) { fprintf(stream, "%*s", (indent_level+1)*4, ""); amf_data_dump(stream, node->data, indent_level+1); node = amf_array_next(node); fprintf(stream, "\n"); } fprintf(stream, "%*s", indent_level*4 + 1, "]"); break; case AMF_TYPE_DATE: amf_date_to_iso8601(data, datestr, sizeof(datestr)); fprintf(stream, "%s", datestr); break; /*case AMF_TYPE_SIMPLEOBJECT:*/ case AMF_TYPE_XML: break; case AMF_TYPE_CLASS: break; default: break; } } } /* return a null AMF object with the specified error code attached to it */ amf_data * amf_data_error(byte error_code) { amf_data * data = amf_null_new(); if (data != NULL) { data->error_code = error_code; } return data; } /* number functions */ amf_data * amf_number_new(number64 value) { amf_data * data = amf_data_new(AMF_TYPE_NUMBER); if (data != NULL) { data->number_data = value; } return data; } number64 amf_number_get_value(const amf_data * data) { return (data != NULL) ? data->number_data : 0; } void amf_number_set_value(amf_data * data, number64 value) { if (data != NULL) { data->number_data = value; } } /* boolean functions */ amf_data * amf_boolean_new(uint8 value) { amf_data * data = amf_data_new(AMF_TYPE_BOOLEAN); if (data != NULL) { data->boolean_data = value; } return data; } uint8 amf_boolean_get_value(const amf_data * data) { return (data != NULL) ? data->boolean_data : 0; } void amf_boolean_set_value(amf_data * data, uint8 value) { if (data != NULL) { data->boolean_data = value; } } /* string functions */ amf_data * amf_string_new(byte * str, uint16 size) { amf_data * data = amf_data_new(AMF_TYPE_STRING); if (data != NULL) { if (str == NULL) { data->string_data.size = 0; } else { data->string_data.size = size; } data->string_data.mbstr = (byte*)calloc(size+1, sizeof(byte)); if (data->string_data.mbstr != NULL) { if (data->string_data.size > 0) { memcpy(data->string_data.mbstr, str, data->string_data.size); } } else { amf_data_free(data); return NULL; } } return data; } amf_data * amf_str(const char * str) { return amf_string_new((byte *)str, (uint16)(str != NULL ? strlen(str) : 0)); } uint16 amf_string_get_size(const amf_data * data) { return (data != NULL) ? data->string_data.size : 0; } byte * amf_string_get_bytes(const amf_data * data) { return (data != NULL) ? data->string_data.mbstr : NULL; } /* object functions */ amf_data * amf_object_new(void) { amf_data * data = amf_data_new(AMF_TYPE_OBJECT); if (data != NULL) { amf_list_init(&data->list_data); } return data; } uint32 amf_object_size(const amf_data * data) { return (data != NULL) ? data->list_data.size / 2 : 0; } amf_data * amf_object_add(amf_data * data, const char * name, amf_data * element) { if (data != NULL) { if (amf_list_push(&data->list_data, amf_str(name)) != NULL) { if (amf_list_push(&data->list_data, element) != NULL) { return element; } else { amf_data_free(amf_list_pop(&data->list_data)); } } } return NULL; } amf_data * amf_object_get(const amf_data * data, const char * name) { if (data != NULL) { amf_node * node = amf_list_first(&(data->list_data)); while (node != NULL) { if (strncmp((char*)(node->data->string_data.mbstr), name, (size_t)(node->data->string_data.size)) == 0) { node = node->next; return (node != NULL) ? node->data : NULL; } /* we have to skip the element data to reach the next name */ node = node->next->next; } } return NULL; } amf_data * amf_object_set(amf_data * data, const char * name, amf_data * element) { if (data != NULL) { amf_node * node = amf_list_first(&(data->list_data)); while (node != NULL) { if (strncmp((char*)(node->data->string_data.mbstr), name, (size_t)(node->data->string_data.size)) == 0) { node = node->next; if (node != NULL && node->data != NULL) { amf_data_free(node->data); node->data = element; return element; } } /* we have to skip the element data to reach the next name */ node = node->next->next; } } return NULL; } amf_data * amf_object_delete(amf_data * data, const char * name) { if (data != NULL) { amf_node * node = amf_list_first(&data->list_data); while (node != NULL) { node = node->next; if (strncmp((char*)(node->data->string_data.mbstr), name, (size_t)(node->data->string_data.size)) == 0) { amf_node * data_node = node->next; amf_data_free(amf_list_delete(&data->list_data, node)); return amf_list_delete(&data->list_data, data_node); } else { node = node->next; } } } return NULL; } amf_node * amf_object_first(const amf_data * data) { return (data != NULL) ? amf_list_first(&data->list_data) : NULL; } amf_node * amf_object_last(const amf_data * data) { if (data != NULL) { amf_node * node = amf_list_last(&data->list_data); if (node != NULL) { return node->prev; } } return NULL; } amf_node * amf_object_next(amf_node * node) { if (node != NULL) { amf_node * next = node->next; if (next != NULL) { return next->next; } } return NULL; } amf_node * amf_object_prev(amf_node * node) { if (node != NULL) { amf_node * prev = node->prev; if (prev != NULL) { return prev->prev; } } return NULL; } amf_data * amf_object_get_name(amf_node * node) { return (node != NULL) ? node->data : NULL; } amf_data * amf_object_get_data(amf_node * node) { if (node != NULL) { amf_node * next = node->next; if (next != NULL) { return next->data; } } return NULL; } /* associative array functions */ amf_data * amf_associative_array_new(void) { amf_data * data = amf_data_new(AMF_TYPE_ASSOCIATIVE_ARRAY); if (data != NULL) { amf_list_init(&data->list_data); } return data; } /* array functions */ amf_data * amf_array_new(void) { amf_data * data = amf_data_new(AMF_TYPE_ARRAY); if (data != NULL) { amf_list_init(&data->list_data); } return data; } uint32 amf_array_size(const amf_data * data) { return (data != NULL) ? data->list_data.size : 0; } amf_data * amf_array_push(amf_data * data, amf_data * element) { return (data != NULL) ? amf_list_push(&data->list_data, element) : NULL; } amf_data * amf_array_pop(amf_data * data) { return (data != NULL) ? amf_list_pop(&data->list_data) : NULL; } amf_node * amf_array_first(const amf_data * data) { return (data != NULL) ? amf_list_first(&data->list_data) : NULL; } amf_node * amf_array_last(const amf_data * data) { return (data != NULL) ? amf_list_last(&data->list_data) : NULL; } amf_node * amf_array_next(amf_node * node) { return (node != NULL) ? node->next : NULL; } amf_node * amf_array_prev(amf_node * node) { return (node != NULL) ? node->prev : NULL; } amf_data * amf_array_get(amf_node * node) { return (node != NULL) ? node->data : NULL; } amf_data * amf_array_get_at(const amf_data * data, uint32 n) { return (data != NULL) ? amf_list_get_at(&data->list_data, n) : NULL; } amf_data * amf_array_delete(amf_data * data, amf_node * node) { return (data != NULL) ? amf_list_delete(&data->list_data, node) : NULL; } amf_data * amf_array_insert_before(amf_data * data, amf_node * node, amf_data * element) { return (data != NULL) ? amf_list_insert_before(&data->list_data, node, element) : NULL; } amf_data * amf_array_insert_after(amf_data * data, amf_node * node, amf_data * element) { return (data != NULL) ? amf_list_insert_after(&data->list_data, node, element) : NULL; } /* date functions */ amf_data * amf_date_new(number64 milliseconds, sint16 timezone) { amf_data * data = amf_data_new(AMF_TYPE_DATE); if (data != NULL) { data->date_data.milliseconds = milliseconds; data->date_data.timezone = timezone; } return data; } number64 amf_date_get_milliseconds(const amf_data * data) { return (data != NULL) ? data->date_data.milliseconds : 0.0; } sint16 amf_date_get_timezone(const amf_data * data) { return (data != NULL) ? data->date_data.timezone : 0; } time_t amf_date_to_time_t(const amf_data * data) { return (time_t)((data != NULL) ? data->date_data.milliseconds / 1000 : 0); } size_t amf_date_to_iso8601(const amf_data * data, char * buffer, size_t bufsize) { struct tm * t; time_t time; time = amf_date_to_time_t(data); tzset(); t = localtime(&time); if (t != NULL) { return strftime(buffer, bufsize, "%Y-%m-%dT%H:%M:%S", t); } else { /* if we couldn't parse the date, use a default value */ return snprintf(buffer, bufsize, "0000-00-00T00:00:00"); } } flvmeta-1.2.2/src/amf.h000066400000000000000000000200001346231570700146550ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __AMF_H__ #define __AMF_H__ #include #include #include #include "types.h" /* AMF data types */ #define AMF_TYPE_NUMBER ((byte)0x00) #define AMF_TYPE_BOOLEAN ((byte)0x01) #define AMF_TYPE_STRING ((byte)0x02) #define AMF_TYPE_OBJECT ((byte)0x03) #define AMF_TYPE_NULL ((byte)0x05) #define AMF_TYPE_UNDEFINED ((byte)0x06) /* #define AMF_TYPE_REFERENCE ((byte)0x07) */ #define AMF_TYPE_ASSOCIATIVE_ARRAY ((byte)0x08) #define AMF_TYPE_END ((byte)0x09) #define AMF_TYPE_ARRAY ((byte)0x0A) #define AMF_TYPE_DATE ((byte)0x0B) /* #define AMF_TYPE_SIMPLEOBJECT ((byte)0x0D) */ #define AMF_TYPE_XML ((byte)0x0F) #define AMF_TYPE_CLASS ((byte)0x10) /* AMF error codes */ #define AMF_ERROR_OK ((byte)0x00) #define AMF_ERROR_EOF ((byte)0x01) #define AMF_ERROR_UNKNOWN_TYPE ((byte)0x02) #define AMF_ERROR_END_TAG ((byte)0x03) #define AMF_ERROR_NULL_POINTER ((byte)0x04) #define AMF_ERROR_MEMORY ((byte)0x05) #define AMF_ERROR_UNSUPPORTED_TYPE ((byte)0x06) typedef struct __amf_node * p_amf_node; /* string type */ typedef struct __amf_string { uint16 size; byte * mbstr; } amf_string; /* array type */ typedef struct __amf_list { uint32 size; p_amf_node first_element; p_amf_node last_element; } amf_list; /* date type */ typedef struct __amf_date { number64 milliseconds; sint16 timezone; } amf_date; /* XML string type */ typedef struct __amf_xmlstring { uint32 size; byte * mbstr; } amf_xmlstring; /* class type */ typedef struct __amf_class { amf_string name; amf_list elements; } amf_class; /* structure encapsulating the various AMF objects */ typedef struct __amf_data { byte type; byte error_code; union { number64 number_data; uint8 boolean_data; amf_string string_data; amf_list list_data; amf_date date_data; amf_xmlstring xmlstring_data; amf_class class_data; }; } amf_data; /* node used in lists, relies on amf_data */ typedef struct __amf_node { amf_data * data; p_amf_node prev; p_amf_node next; } amf_node; #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* Pluggable backend support */ typedef size_t (*amf_read_proc)(void * out_buffer, size_t size, void * user_data); typedef size_t (*amf_write_proc)(const void * in_buffer, size_t size, void * user_data); /* read AMF data */ amf_data * amf_data_read(amf_read_proc read_proc, void * user_data); /* write AMF data */ size_t amf_data_write(const amf_data * data, amf_write_proc write_proc, void * user_data); /* generic functions */ /* allocate an AMF data object */ amf_data * amf_data_new(byte type); /* load AMF data from buffer */ amf_data * amf_data_buffer_read(byte * buffer, size_t maxbytes); /* load AMF data from stream */ amf_data * amf_data_file_read(FILE * stream); /* AMF data size */ size_t amf_data_size(const amf_data * data); /* write encoded AMF data into a buffer */ size_t amf_data_buffer_write(amf_data * data, byte * buffer, size_t maxbytes); /* write encoded AMF data into a stream */ size_t amf_data_file_write(const amf_data * data, FILE * stream); /* get the type of AMF data */ byte amf_data_get_type(const amf_data * data); /* get the error code of AMF data */ byte amf_data_get_error_code(const amf_data * data); /* return a new copy of AMF data */ amf_data * amf_data_clone(const amf_data * data); /* release the memory of AMF data */ void amf_data_free(amf_data * data); /* dump AMF data into a stream as text */ void amf_data_dump(FILE * stream, const amf_data * data, int indent_level); /* return a null AMF object with the specified error code attached to it */ amf_data * amf_data_error(byte error_code); /* number functions */ amf_data * amf_number_new(number64 value); number64 amf_number_get_value(const amf_data * data); void amf_number_set_value(amf_data * data, number64 value); /* boolean functions */ amf_data * amf_boolean_new(uint8 value); uint8 amf_boolean_get_value(const amf_data * data); void amf_boolean_set_value(amf_data * data, uint8 value); /* string functions */ amf_data * amf_string_new(byte * str, uint16 size); amf_data * amf_str(const char * str); uint16 amf_string_get_size(const amf_data * data); byte * amf_string_get_bytes(const amf_data * data); /* object functions */ amf_data * amf_object_new(void); uint32 amf_object_size(const amf_data * data); amf_data * amf_object_add(amf_data * data, const char * name, amf_data * element); amf_data * amf_object_get(const amf_data * data, const char * name); amf_data * amf_object_set(amf_data * data, const char * name, amf_data * element); amf_data * amf_object_delete(amf_data * data, const char * name); amf_node * amf_object_first(const amf_data * data); amf_node * amf_object_last(const amf_data * data); amf_node * amf_object_next(amf_node * node); amf_node * amf_object_prev(amf_node * node); amf_data * amf_object_get_name(amf_node * node); amf_data * amf_object_get_data(amf_node * node); /* null functions */ #define amf_null_new() amf_data_new(AMF_TYPE_NULL) /* undefined functions */ #define amf_undefined_new() amf_data_new(AMF_TYPE_UNDEFINED) /* associative array functions */ amf_data * amf_associative_array_new(void); #define amf_associative_array_size(d) amf_object_size(d) #define amf_associative_array_add(d, n, e) amf_object_add(d, n, e) #define amf_associative_array_get(d, n) amf_object_get(d, n) #define amf_associative_array_set(d, n, e) amf_object_set(d, n, e) #define amf_associative_array_delete(d, n) amf_object_delete(d, n) #define amf_associative_array_first(d) amf_object_first(d) #define amf_associative_array_last(d) amf_object_last(d) #define amf_associative_array_next(n) amf_object_next(n) #define amf_associative_array_prev(n) amf_object_prev(n) #define amf_associative_array_get_name(n) amf_object_get_name(n) #define amf_associative_array_get_data(n) amf_object_get_data(n) /* array functions */ amf_data * amf_array_new(void); uint32 amf_array_size(const amf_data * data); amf_data * amf_array_push(amf_data * data, amf_data * element); amf_data * amf_array_pop(amf_data * data); amf_node * amf_array_first(const amf_data * data); amf_node * amf_array_last(const amf_data * data); amf_node * amf_array_next(amf_node * node); amf_node * amf_array_prev(amf_node * node); amf_data * amf_array_get(amf_node * node); amf_data * amf_array_get_at(const amf_data * data, uint32 n); amf_data * amf_array_delete(amf_data * data, amf_node * node); amf_data * amf_array_insert_before(amf_data * data, amf_node * node, amf_data * element); amf_data * amf_array_insert_after(amf_data * data, amf_node * node, amf_data * element); /* date functions */ amf_data * amf_date_new(number64 milliseconds, sint16 timezone); number64 amf_date_get_milliseconds(const amf_data * data); sint16 amf_date_get_timezone(const amf_data * data); time_t amf_date_to_time_t(const amf_data * data); size_t amf_date_to_iso8601(const amf_data * data, char * buffer, size_t bufsize); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __AMF_H__ */ flvmeta-1.2.2/src/avc.c000066400000000000000000000227061346231570700146750ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "avc.h" /** bit buffer handling */ typedef struct __bit_buffer { byte * start; size_t size; byte * current; uint8 read_bits; } bit_buffer; static void skip_bits(bit_buffer * bb, size_t nbits) { bb->current = bb->current + (nbits + bb->read_bits) / 8; bb->read_bits = (uint8)((bb->read_bits + nbits) % 8); } static sint8 get_bit(bit_buffer * bb) { sint8 ret; if (bb->current - bb->start > (ptrdiff_t)(bb->size - 1)) { return -1; } ret = (*(bb->current) >> (7 - bb->read_bits)) & 0x1; if (bb->read_bits == 7) { bb->read_bits = 0; bb->current++; } else { bb->read_bits++; } return ret; } static uint8 get_bits(bit_buffer * bb, size_t nbits, uint32 * ret) { uint32 i; sint8 bit; if (nbits > sizeof(uint32) * 8) { nbits = sizeof(uint32) * 8; } *ret = 0; for (i = 0; i < nbits; i++) { bit = get_bit(bb); if (bit == -1) { return 0; } *ret = (*ret << 1) + bit; } return 1; } static uint32 exp_golomb_ue(bit_buffer * bb) { sint8 bit; uint8 significant_bits; uint32 bits; significant_bits = 0; do { bit = get_bit(bb); if (bit == -1) { return 0; } if (bit == 0) { significant_bits++; } } while (bit == 0); if (!get_bits(bb, significant_bits, &bits)) return 0; return (1 << significant_bits) + bits - 1; } static sint32 exp_golomb_se(bit_buffer * bb) { sint32 ret; ret = exp_golomb_ue(bb); if ((ret & 0x1) == 0) { return -(ret >> 1); } return (ret + 1) >> 1; } /* AVC type definitions */ #define AVC_SEQUENCE_HEADER 0 #define AVC_NALU 1 #define AVC_END_OF_SEQUENCE 2 typedef struct __AVCDecoderConfigurationRecord { uint8 configurationVersion; uint8 AVCProfileIndication; uint8 profile_compatibility; uint8 AVCLevelIndication; uint8 lengthSizeMinusOne; uint8 numOfSequenceParameterSets; } AVCDecoderConfigurationRecord; static int read_avc_decoder_configuration_record(flv_stream * f, AVCDecoderConfigurationRecord * adcr) { if (flv_read_tag_body(f, &adcr->configurationVersion, 1) == 1 && flv_read_tag_body(f, &adcr->AVCProfileIndication, 1) == 1 && flv_read_tag_body(f, &adcr->profile_compatibility, 1) == 1 && flv_read_tag_body(f, &adcr->AVCLevelIndication, 1) == 1 && flv_read_tag_body(f, &adcr->lengthSizeMinusOne, 1) == 1 && flv_read_tag_body(f, &adcr->numOfSequenceParameterSets, 1) == 1) { return FLV_OK; } return FLV_ERROR_EOF; } static void parse_scaling_list(uint32 size, bit_buffer * bb) { uint32 last_scale, next_scale, i; sint32 delta_scale; last_scale = 8; next_scale = 8; for (i = 0; i < size; i++) { if (next_scale != 0) { delta_scale = exp_golomb_se(bb); next_scale = (last_scale + delta_scale + 256) % 256; } if (next_scale != 0) { last_scale = next_scale; } } } /** Parses a SPS NALU to retrieve video width and height */ static void parse_sps(byte * sps, size_t sps_size, uint32 * width, uint32 * height) { bit_buffer bb; uint32 profile, pic_order_cnt_type, width_in_mbs, height_in_map_units; uint32 i, size, left, right, top, bottom; sint8 frame_mbs_only_flag; sint8 bit; bb.start = sps; bb.size = sps_size; bb.current = sps; bb.read_bits = 0; /* skip first byte, since we already know we're parsing a SPS */ skip_bits(&bb, 8); /* get profile */ if (!get_bits(&bb, 8, &profile)) { return; } /* skip 4 bits + 4 zeroed bits + 8 bits = 16 bits = 2 bytes */ skip_bits(&bb, 16); /* read sps id, first exp-golomb encoded value */ exp_golomb_ue(&bb); if (profile == 100 || profile == 110 || profile == 122 || profile == 144) { /* chroma format idx */ if (exp_golomb_ue(&bb) == 3) { skip_bits(&bb, 1); } /* bit depth luma minus8 */ exp_golomb_ue(&bb); /* bit depth chroma minus8 */ exp_golomb_ue(&bb); /* Qpprime Y Zero Transform Bypass flag */ skip_bits(&bb, 1); /* Seq Scaling Matrix Present Flag */ bit = get_bit(&bb); if (bit == -1) { return; } if (bit) { for (i = 0; i < 8; i++) { /* Seq Scaling List Present Flag */ bit = get_bit(&bb); if (bit == -1) { return; } if (bit) { parse_scaling_list(i < 6 ? 16 : 64, &bb); } } } } /* log2_max_frame_num_minus4 */ exp_golomb_ue(&bb); /* pic_order_cnt_type */ pic_order_cnt_type = exp_golomb_ue(&bb); if (pic_order_cnt_type == 0) { /* log2_max_pic_order_cnt_lsb_minus4 */ exp_golomb_ue(&bb); } else if (pic_order_cnt_type == 1) { /* delta_pic_order_always_zero_flag */ skip_bits(&bb, 1); /* offset_for_non_ref_pic */ exp_golomb_se(&bb); /* offset_for_top_to_bottom_field */ exp_golomb_se(&bb); size = exp_golomb_ue(&bb); for (i = 0; i < size; i++) { /* offset_for_ref_frame */ exp_golomb_se(&bb); } } /* num_ref_frames */ exp_golomb_ue(&bb); /* gaps_in_frame_num_value_allowed_flag */ skip_bits(&bb, 1); /* pic_width_in_mbs */ width_in_mbs = exp_golomb_ue(&bb) + 1; /* pic_height_in_map_units */ height_in_map_units = exp_golomb_ue(&bb) + 1; /* frame_mbs_only_flag */ frame_mbs_only_flag = get_bit(&bb); if (frame_mbs_only_flag == -1) { return; } if (!frame_mbs_only_flag) { /* mb_adaptive_frame_field */ skip_bits(&bb, 1); } /* direct_8x8_inference_flag */ skip_bits(&bb, 1); /* frame_cropping */ left = right = top = bottom = 0; bit = get_bit(&bb); if (bit == -1) { return; } if (bit) { left = exp_golomb_ue(&bb) * 2; right = exp_golomb_ue(&bb) * 2; top = exp_golomb_ue(&bb) * 2; bottom = exp_golomb_ue(&bb) * 2; if (!frame_mbs_only_flag) { top *= 2; bottom *= 2; } } /* width */ *width = width_in_mbs * 16 - (left + right); /* height */ *height = height_in_map_units * 16 - (top + bottom); if (!frame_mbs_only_flag) { *height *= 2; } } /** Tries to read the resolution of the current video packet. We assume to be at the first byte of the video data. */ int read_avc_resolution(flv_stream * f, uint32 body_length, uint32 * width, uint32 * height) { byte avc_packet_type; uint24 composition_time; AVCDecoderConfigurationRecord adcr; uint16 sps_size; byte * sps_buffer; /* make sure we have enough bytes to read in the current tag */ if (body_length < sizeof(byte) + sizeof(uint24) + sizeof(AVCDecoderConfigurationRecord)) { return FLV_OK; } /* determine whether we're reading an AVCDecoderConfigurationRecord */ if (flv_read_tag_body(f, &avc_packet_type, 1) < 1) { return FLV_ERROR_EOF; } if (avc_packet_type != AVC_SEQUENCE_HEADER) { return FLV_OK; } /* read the composition time */ if (flv_read_tag_body(f, &composition_time, sizeof(uint24)) < sizeof(uint24)) { return FLV_ERROR_EOF; } /* we need to read an AVCDecoderConfigurationRecord */ if (read_avc_decoder_configuration_record(f, &adcr) == FLV_ERROR_EOF) { return FLV_ERROR_EOF; } /* number of SequenceParameterSets */ if ((adcr.numOfSequenceParameterSets & 0x1F) == 0) { /* no SPS, return */ return FLV_OK; } /** read the first SequenceParameterSet found */ /* SPS size */ if (flv_read_tag_body(f, &sps_size, sizeof(uint16)) < sizeof(uint16)) { return FLV_ERROR_EOF; } sps_size = swap_uint16(sps_size); /* SPS size should not be zero or more than the remaining bytes in the tag body */ if (sps_size == 0 || sps_size > body_length - (sizeof(AVCDecoderConfigurationRecord) + 1 + sizeof(uint24) + sizeof(uint16))) { return FLV_ERROR_EOF; } /* read the SPS entirely */ sps_buffer = (byte *) malloc((size_t)sps_size); if (sps_buffer == NULL) { return FLV_ERROR_MEMORY; } if (flv_read_tag_body(f, sps_buffer, (size_t)sps_size) < (size_t)sps_size) { free(sps_buffer); return FLV_ERROR_EOF; } /* parse SPS to determine video resolution */ parse_sps(sps_buffer, (size_t)sps_size, width, height); free(sps_buffer); return FLV_OK; } flvmeta-1.2.2/src/avc.h000066400000000000000000000022211346231570700146700ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __AVC_H__ #define __AVC_H__ #include #include "types.h" #include "flv.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ int read_avc_resolution(flv_stream * f, uint32 body_length, uint32 * width, uint32 * height); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __AVC_H__ */ flvmeta-1.2.2/src/check.c000066400000000000000000002064401346231570700152000ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "check.h" #include "dump.h" #include "info.h" #include "json.h" #include "util.h" #include #include #include #include #include #include #include #define MAX_ACCEPTABLE_TAG_BODY_LENGTH 1000000 typedef struct { json_emitter je; } check_context; /* start the report */ static void report_start(const flvmeta_opts * opts, check_context * ctxt) { time_t now; struct tm * t; char datestr[128]; if (opts->quiet) return; now = time(NULL); tzset(); t = localtime(&now); strftime(datestr, sizeof(datestr), "%Y-%m-%dT%H:%M:%S", t); if (opts->check_report_format == FLVMETA_FORMAT_XML) { puts(""); puts(""); puts(" "); printf(" %s\n", opts->input_file); printf(" %s\n", datestr); printf(" %s\n", PACKAGE_STRING); puts(" "); puts(" "); } else if (opts->check_report_format == FLVMETA_FORMAT_JSON) { json_emit_init(&ctxt->je); json_emit_object_start(&ctxt->je); json_emit_object_key_z(&ctxt->je, "filename"); json_emit_string_z(&ctxt->je, opts->input_file); json_emit_object_key_z(&ctxt->je, "creation_date"); json_emit_string_z(&ctxt->je, datestr); json_emit_object_key_z(&ctxt->je, "generator"); json_emit_string_z(&ctxt->je, PACKAGE_STRING); json_emit_object_key_z(&ctxt->je, "messages"); json_emit_array_start(&ctxt->je); } } /* end the report */ static void report_end(const flvmeta_opts * opts, check_context * ctxt, uint32 errors, uint32 warnings) { if (opts->quiet) return; if (opts->check_report_format == FLVMETA_FORMAT_XML) { puts(" "); puts(""); } else if (opts->check_report_format == FLVMETA_FORMAT_JSON) { json_emit_array_end(&ctxt->je); json_emit_object_key_z(&ctxt->je, "errors"); json_emit_integer(&ctxt->je, errors); json_emit_object_key_z(&ctxt->je, "warnings"); json_emit_integer(&ctxt->je, warnings); json_emit_object_end(&ctxt->je); printf("\n"); } else { printf("%u error(s), %u warning(s)\n", errors, warnings); } } /* report an error to stdout according to the current format */ static void report_print_message( int level, const char * code, file_offset_t offset, const char * message, const flvmeta_opts * opts, check_context * ctxt ) { if (opts->quiet) return; if (level >= opts->check_level) { char * levelstr; switch (level) { case FLVMETA_CHECK_LEVEL_INFO: levelstr = "info"; break; case FLVMETA_CHECK_LEVEL_WARNING: levelstr = "warning"; break; case FLVMETA_CHECK_LEVEL_ERROR: levelstr = "error"; break; case FLVMETA_CHECK_LEVEL_FATAL: levelstr = "fatal"; break; default: levelstr = "unknown"; } if (opts->check_report_format == FLVMETA_FORMAT_XML) { /* XML report entry */ printf(" ", FILE_OFFSET_PRINTF_TYPE(offset)); printf("%s\n", message); } else if (opts->check_report_format == FLVMETA_FORMAT_JSON) { /* JSON report entry */ json_emit_object_start(&ctxt->je); json_emit_object_key_z(&ctxt->je, "level"); json_emit_string_z(&ctxt->je, levelstr); json_emit_object_key_z(&ctxt->je, "code"); json_emit_string_z(&ctxt->je, code); json_emit_object_key_z(&ctxt->je, "offset"); json_emit_file_offset(&ctxt->je, offset); json_emit_object_key_z(&ctxt->je, "message"); json_emit_string_z(&ctxt->je, message); json_emit_object_end(&ctxt->je); } else { /* raw report entry */ /*printf("%s:", opts->input_file);*/ printf("0x%.8" FILE_OFFSET_PRINTF_FORMAT "x: ", FILE_OFFSET_PRINTF_TYPE(offset)); printf("%s %s: %s\n", levelstr, code, message); } } } /* timestamp distance */ #define timestamp_distance(t1, t2) ((t1)>(t2)?(t1)-(t2):(t2)-(t1)) /* convenience macros */ #define print_info(code, offset, message) \ report_print_message(FLVMETA_CHECK_LEVEL_INFO, code, offset, message, opts, &ctxt) #define print_warning(code, offset, message) \ if (opts->check_level <= FLVMETA_CHECK_LEVEL_WARNING) ++warnings; \ report_print_message(FLVMETA_CHECK_LEVEL_WARNING, code, offset, message, opts, &ctxt) #define print_error(code, offset, message) \ if (opts->check_level <= FLVMETA_CHECK_LEVEL_ERROR) ++errors; \ report_print_message(FLVMETA_CHECK_LEVEL_ERROR, code, offset, message, opts, &ctxt) #define print_fatal(code, offset, message) \ if (opts->check_level <= FLVMETA_CHECK_LEVEL_FATAL) ++errors; \ report_print_message(FLVMETA_CHECK_LEVEL_FATAL, code, offset, message, opts, &ctxt) /* get string representing given AMF type */ static const char * get_amf_type_string(byte type) { switch (type) { case AMF_TYPE_NUMBER: return "Number"; case AMF_TYPE_BOOLEAN: return "Boolean"; case AMF_TYPE_STRING: return "String"; case AMF_TYPE_NULL: return "Null"; case AMF_TYPE_UNDEFINED: return "Undefined"; /*case AMF_TYPE_REFERENCE:*/ case AMF_TYPE_OBJECT: return "Object"; case AMF_TYPE_ASSOCIATIVE_ARRAY: return "Associative array"; case AMF_TYPE_ARRAY: return "Array"; case AMF_TYPE_DATE: return "Date"; /*case AMF_TYPE_SIMPLEOBJECT:*/ case AMF_TYPE_XML: return "XML"; case AMF_TYPE_CLASS: return "Class"; default: return "Unknown type"; } } /* check FLV file validity */ int check_flv_file(const flvmeta_opts * opts) { flv_stream * flv_in; flv_header header; check_context ctxt; uint32 errors, warnings; int result; char message[256]; uint32 prev_tag_size, tag_number; uint32 last_timestamp, last_video_timestamp, last_audio_timestamp; file_offset_t filesize; int have_audio, have_video; flvmeta_opts opts_loc; flv_info info; int have_desync; int have_on_metadata; file_offset_t on_metadata_offset; amf_data * on_metadata, * on_metadata_name; int have_on_last_second; uint32 on_last_second_timestamp; int have_prev_audio_tag; flv_audio_tag prev_audio_tag; int have_prev_video_tag; flv_video_tag prev_video_tag; int consecutive_unknown_tags; int video_frames_number, keyframes_number; prev_audio_tag = 0; prev_video_tag = 0; have_audio = have_video = 0; tag_number = 0; last_timestamp = last_video_timestamp = last_audio_timestamp = 0; have_desync = 0; have_prev_audio_tag = have_prev_video_tag = 0; video_frames_number = keyframes_number = 0; have_on_metadata = 0; on_metadata_offset = 0; on_metadata = on_metadata_name = NULL; have_on_last_second = 0; on_last_second_timestamp = 0; consecutive_unknown_tags = 0; /* file stats */ if (flvmeta_filesize(opts->input_file, &filesize) == 0) { return ERROR_OPEN_READ; } /* open file for reading */ flv_in = flv_open(opts->input_file); if (flv_in == NULL) { return ERROR_OPEN_READ; } errors = warnings = 0; report_start(opts, &ctxt); /** check header **/ /* check signature */ result = flv_read_header(flv_in, &header); if (result == FLV_ERROR_EOF) { print_fatal(FATAL_HEADER_EOF, 0, "unexpected end of file in header"); goto end; } else if (result == FLV_ERROR_NO_FLV) { print_fatal(FATAL_HEADER_NO_SIGNATURE, 0, "FLV signature not found in header"); goto end; } /* version */ if (header.version != FLV_VERSION) { sprintf(message, "header version should be 1, %u found instead", header.version); print_error(ERROR_HEADER_BAD_VERSION, 3, message); } /* video and audio flags */ if (!flv_header_has_audio(header) && !flv_header_has_video(header)) { print_error(ERROR_HEADER_NO_STREAMS, 4, "header signals the file does not contain video tags or audio tags"); } else if (!flv_header_has_audio(header)) { print_info(INFO_HEADER_NO_AUDIO, 4, "header signals the file does not contain audio tags"); } else if (!flv_header_has_video(header)) { print_warning(WARNING_HEADER_NO_VIDEO, 4, "header signals the file does not contain video tags"); } /* reserved flags */ if (header.flags & 0xFA) { print_error(ERROR_HEADER_BAD_RESERVED_FLAGS, 4, "header reserved flags are not zero"); } /* offset */ if (flv_header_get_offset(header) != 9) { sprintf(message, "header offset should be 9, %u found instead", flv_header_get_offset(header)); print_error(ERROR_HEADER_BAD_OFFSET, 5, message); } /** check first previous tag size **/ result = flv_read_prev_tag_size(flv_in, &prev_tag_size); if (result == FLV_ERROR_EOF) { print_fatal(FATAL_PREV_TAG_SIZE_EOF, 9, "unexpected end of file in previous tag size"); goto end; } else if (prev_tag_size != 0) { sprintf(message, "first previous tag size should be 0, %u found instead", prev_tag_size); print_error(ERROR_PREV_TAG_SIZE_BAD_FIRST, 9, message); } /* we reached the end of file: no tags in file */ if (flv_get_offset(flv_in) == filesize) { print_fatal(FATAL_GENERAL_NO_TAG, 13, "file does not contain tags"); goto end; } /** read tags **/ while (flv_get_offset(flv_in) < filesize) { flv_tag tag; file_offset_t offset; uint32 body_length, timestamp, stream_id; int decr_timestamp_signaled; result = flv_read_tag(flv_in, &tag); if (result != FLV_OK) { print_fatal(FATAL_TAG_EOF, flv_get_offset(flv_in), "unexpected end of file in tag"); goto end; } ++tag_number; offset = flv_get_current_tag_offset(flv_in); body_length = flv_tag_get_body_length(tag); timestamp = flv_tag_get_timestamp(tag); stream_id = flv_tag_get_stream_id(tag); /* check tag type */ if (tag.type != FLV_TAG_TYPE_AUDIO && tag.type != FLV_TAG_TYPE_VIDEO && tag.type != FLV_TAG_TYPE_META ) { sprintf(message, "unknown tag type %" PRI_BYTE "d", tag.type); print_error(ERROR_TAG_TYPE_UNKNOWN, offset, message); ++consecutive_unknown_tags; if (consecutive_unknown_tags >= 2) { print_fatal(FATAL_CONSECUTIVE_UNKNOWN_TAGS, offset, "consecutive tags with unknown type found, aborting"); goto end; } } else { consecutive_unknown_tags = 0; } /* check consistency with global header */ if (!have_video && tag.type == FLV_TAG_TYPE_VIDEO) { if (!flv_header_has_video(header)) { print_warning(WARNING_HEADER_UNEXPECTED_VIDEO, offset, "video tag found despite header signaling the file contains no video"); } have_video = 1; } if (!have_audio && tag.type == FLV_TAG_TYPE_AUDIO) { if (!flv_header_has_audio(header)) { print_warning(WARNING_HEADER_UNEXPECTED_AUDIO, offset, "audio tag found despite header signaling the file contains no audio"); } have_audio = 1; } /* check body length */ if (body_length > (filesize - flv_get_offset(flv_in))) { sprintf(message, "tag body length (%u bytes) exceeds file size", body_length); print_fatal(FATAL_TAG_BODY_LENGTH_OVERFLOW, offset + 1, message); goto end; } else if (body_length > MAX_ACCEPTABLE_TAG_BODY_LENGTH) { sprintf(message, "tag body length (%u bytes) is abnormally large", body_length); print_warning(WARNING_TAG_BODY_LENGTH_LARGE, offset + 1, message); } else if (body_length == 0) { print_warning(WARNING_TAG_BODY_LENGTH_ZERO, offset + 1, "tag body length is zero"); } /** check timestamp **/ decr_timestamp_signaled = 0; /* check whether first timestamp is zero */ if (tag_number == 1 && timestamp != 0) { sprintf(message, "first timestamp should be zero, %u found instead", timestamp); print_error(ERROR_TIMESTAMP_FIRST_NON_ZERO, offset + 4, message); } /* check whether timestamps decrease in a given stream */ if (tag.type == FLV_TAG_TYPE_AUDIO) { if (last_audio_timestamp > timestamp) { sprintf(message, "audio tag timestamps are decreasing from %u to %u", last_audio_timestamp, timestamp); print_error(ERROR_TIMESTAMP_AUDIO_DECREASE, offset + 4, message); } last_audio_timestamp = timestamp; decr_timestamp_signaled = 1; } if (tag.type == FLV_TAG_TYPE_VIDEO) { if (last_video_timestamp > timestamp) { sprintf(message, "video tag timestamps are decreasing from %u to %u", last_video_timestamp, timestamp); print_error(ERROR_TIMESTAMP_VIDEO_DECREASE, offset + 4, message); } last_video_timestamp = timestamp; decr_timestamp_signaled = 1; } /* check for overflow error */ if (last_timestamp > timestamp && last_timestamp - timestamp > 0xF00000) { print_error(ERROR_TIMESTAMP_OVERFLOW, offset + 4, "extended bits not used after timestamp overflow"); } /* check whether timestamps decrease globally */ else if (!decr_timestamp_signaled && last_timestamp > timestamp && last_timestamp - timestamp >= 1000) { sprintf(message, "timestamps are decreasing from %u to %u", last_timestamp, timestamp); print_error(ERROR_TIMESTAMP_DECREASE, offset + 4, message); } last_timestamp = timestamp; /* check for desyncs between audio and video: one second or more is suspicious */ if (have_video && have_audio && !have_desync && timestamp_distance(last_video_timestamp, last_audio_timestamp) >= 1000) { sprintf(message, "audio and video streams are desynchronized by %d ms", timestamp_distance(last_video_timestamp, last_audio_timestamp)); print_warning(WARNING_TIMESTAMP_DESYNC, offset + 4, message); have_desync = 1; /* do not repeat */ } /** stream id must be zero **/ if (stream_id != 0) { sprintf(message, "tag stream id must be zero, %u found instead", stream_id); print_error(ERROR_TAG_STREAM_ID_NON_ZERO, offset + 8, message); } /* check tag body contents only if not empty */ if (body_length > 0) { /** check audio info **/ if (tag.type == FLV_TAG_TYPE_AUDIO) { flv_audio_tag at; uint8_bitmask audio_format; result = flv_read_audio_tag(flv_in, &at); if (result == FLV_ERROR_EOF) { print_fatal(FATAL_TAG_EOF, offset + 11, "unexpected end of file in tag"); goto end; } /* check whether the format varies between tags */ if (have_prev_audio_tag && prev_audio_tag != at) { print_warning(WARNING_AUDIO_FORMAT_CHANGED, offset + 11, "audio format changed since last tag"); } /* check format */ audio_format = flv_audio_tag_sound_format(at); if (audio_format == 12 || audio_format == 13) { sprintf(message, "unknown audio format %u", audio_format); print_warning(WARNING_AUDIO_CODEC_UNKNOWN, offset + 11, message); } else if (audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_G711_A || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_G711_MU || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_RESERVED || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_MP3_8 || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_DEVICE_SPECIFIC ) { sprintf(message, "audio format %u is reserved for internal use", audio_format); print_warning(WARNING_AUDIO_CODEC_RESERVED, offset + 11, message); } /* check consistency, see flash video spec */ if (flv_audio_tag_sound_rate(at) != FLV_AUDIO_TAG_SOUND_RATE_44 && audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_AAC ) { print_warning(WARNING_AUDIO_CODEC_AAC_BAD, offset + 11, "audio data in AAC format should have a 44KHz rate, field will be ignored"); } if (flv_audio_tag_sound_type(at) == FLV_AUDIO_TAG_SOUND_TYPE_STEREO && (audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER_16_MONO || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER_8_MONO) ) { print_warning(WARNING_AUDIO_CODEC_NELLYMOSER_BAD, offset + 11, "audio data in Nellymoser format cannot be stereo, field will be ignored"); } else if (flv_audio_tag_sound_type(at) == FLV_AUDIO_TAG_SOUND_TYPE_MONO && audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_AAC ) { print_warning(WARNING_AUDIO_CODEC_AAC_MONO, offset + 11, "audio data in AAC format should be stereo, field will be ignored"); } else if (audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_LINEAR_PCM) { print_warning(WARNING_AUDIO_CODEC_LINEAR_PCM, offset + 11, "audio data in Linear PCM, platform endian format should not be used because of non-portability"); } prev_audio_tag = at; have_prev_audio_tag = 1; } /** check video info **/ else if (tag.type == FLV_TAG_TYPE_VIDEO) { flv_video_tag vt; uint8_bitmask video_frame_type, video_codec; video_frames_number++; result = flv_read_video_tag(flv_in, &vt); if (result == FLV_ERROR_EOF) { print_fatal(FATAL_TAG_EOF, offset + 11, "unexpected end of file in tag"); goto end; } /* check whether the format varies between tags */ if (have_prev_video_tag && flv_video_tag_codec_id(prev_video_tag) != flv_video_tag_codec_id(vt)) { print_warning(WARNING_VIDEO_FORMAT_CHANGED, offset + 11, "video format changed since last tag"); } /* check video frame type */ video_frame_type = flv_video_tag_frame_type(vt); if (video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME && video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_INTERFRAME && video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_DISPOSABLE_INTERFRAME && video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_GENERATED_KEYFRAME && video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_COMMAND_FRAME ) { sprintf(message, "unknown video frame type %u", video_frame_type); print_error(ERROR_VIDEO_FRAME_TYPE_UNKNOWN, offset + 11, message); } if (video_frame_type == FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME) { keyframes_number++; } /* check whether first frame is a keyframe */ if (!have_prev_video_tag && video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME) { print_warning(WARNING_VIDEO_NO_FIRST_KEYFRAME, offset + 11, "first video frame is not a keyframe, playback will suffer"); } /* check video codec */ video_codec = flv_video_tag_codec_id(vt); if (video_codec != FLV_VIDEO_TAG_CODEC_JPEG && video_codec != FLV_VIDEO_TAG_CODEC_SORENSEN_H263 && video_codec != FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO && video_codec != FLV_VIDEO_TAG_CODEC_ON2_VP6 && video_codec != FLV_VIDEO_TAG_CODEC_ON2_VP6_ALPHA && video_codec != FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO_V2 && video_codec != FLV_VIDEO_TAG_CODEC_AVC ) { sprintf(message, "unknown video codec id %u", video_codec); print_error(ERROR_VIDEO_CODEC_UNKNOWN, offset + 11, message); } /* according to spec, JPEG codec is not currently used */ if (video_codec == FLV_VIDEO_TAG_CODEC_JPEG) { print_warning(WARNING_VIDEO_CODEC_JPEG, offset + 11, "JPEG codec not currently used"); } prev_video_tag = vt; have_prev_video_tag = 1; } /** check script data info **/ else if (tag.type == FLV_TAG_TYPE_META) { amf_data * name; amf_data * data; name = NULL; data = NULL; result = flv_read_metadata(flv_in, &name, &data); if (result == FLV_ERROR_EOF) { print_fatal(FATAL_TAG_EOF, offset + 11, "unexpected end of file in tag"); amf_data_free(name); amf_data_free(data); goto end; } else if (result == FLV_ERROR_EMPTY_TAG) { print_warning(WARNING_METADATA_EMPTY, offset + 11, "empty metadata tag"); } else if (result == FLV_ERROR_INVALID_METADATA_NAME) { print_error(ERROR_METADATA_NAME_INVALID, offset + 11, "invalid metadata name"); } else if (result == FLV_ERROR_INVALID_METADATA) { print_error(ERROR_METADATA_DATA_INVALID, offset + 11, "invalid metadata"); } else if (amf_data_get_type(name) != AMF_TYPE_STRING) { /* name type checking */ sprintf(message, "invalid metadata name type: %u, should be a string (2)", amf_data_get_type(name)); print_error(ERROR_METADATA_NAME_INVALID_TYPE, offset, message); } else { /* empty name checking */ if (amf_string_get_size(name) == 0) { print_warning(WARNING_METADATA_NAME_EMPTY, offset, "empty metadata name"); } /* check whether all body size has been read */ if (flv_in->current_tag_body_length > 0) { sprintf(message, "%u bytes not read in tag body after metadata end", body_length - flv_in->current_tag_body_length); print_warning(WARNING_METADATA_DATA_REMAINING, flv_get_offset(flv_in), message); } else if (flv_in->current_tag_body_overflow > 0) { sprintf(message, "%u bytes missing from tag body after metadata end", flv_in->current_tag_body_overflow); print_warning(WARNING_METADATA_DATA_MISSING, flv_get_offset(flv_in), message); } /* onLastSecond checking */ if (!strcmp((char*)amf_string_get_bytes(name), "onLastSecond")) { if (have_on_last_second == 0) { have_on_last_second = 1; on_last_second_timestamp = timestamp; } else { print_warning(WARNING_METADATA_LAST_SECOND_DUP, offset, "duplicate onLastSecond event"); } } /* onMetaData checking */ if (!strcmp((char*)amf_string_get_bytes(name), "onMetaData")) { if (have_on_metadata == 0) { have_on_metadata = 1; on_metadata_offset = offset; on_metadata = amf_data_clone(data); on_metadata_name = amf_data_clone(name); /* check onMetadata type */ if (amf_data_get_type(on_metadata) != AMF_TYPE_ASSOCIATIVE_ARRAY) { sprintf(message, "invalid onMetaData data type: %u, should be an associative array (8)", amf_data_get_type(on_metadata)); print_error(ERROR_METADATA_DATA_INVALID_TYPE, offset, message); } /* onMetaData must be the first tag at 0 timestamp */ if (tag_number != 1) { print_warning(WARNING_METADATA_BAD_TAG, offset, "onMetadata event found after the first tag"); } if (timestamp != 0) { print_warning(WARNING_METADATA_BAD_TIMESTAMP, offset, "onMetadata event found after timestamp zero"); } } else { print_warning(WARNING_METADATA_DUPLICATE, offset, "duplicate onMetaData event"); } } /* unknown metadata name */ if (strcmp((char*)amf_string_get_bytes(name), "onMetaData") && strcmp((char*)amf_string_get_bytes(name), "onCuePoint") && strcmp((char*)amf_string_get_bytes(name), "onLastSecond")) { sprintf(message, "unknown metadata event name: '%s'", (char*)amf_string_get_bytes(name)); print_info(INFO_METADATA_NAME_UNKNOWN, flv_get_offset(flv_in), message); } } amf_data_free(name); amf_data_free(data); } } /* check body length against previous tag size */ result = flv_read_prev_tag_size(flv_in, &prev_tag_size); if (result != FLV_OK) { print_fatal(FATAL_PREV_TAG_SIZE_EOF, flv_get_offset(flv_in), "unexpected end of file in previous tag size"); goto end; } if (prev_tag_size != FLV_TAG_SIZE + body_length) { sprintf(message, "previous tag size should be %u, %u found instead", FLV_TAG_SIZE + body_length, prev_tag_size); print_error(ERROR_PREV_TAG_SIZE_BAD, flv_get_offset(flv_in), message); } } /** final checks */ /* check consistency with global header */ if (!have_video && flv_header_has_video(header)) { print_warning(WARNING_HEADER_VIDEO_NOT_FOUND, 4, "no video tag found despite header signaling the file contains video"); } if (!have_audio && flv_header_has_audio(header)) { print_warning(WARNING_HEADER_AUDIO_NOT_FOUND, 4, "no audio tag found despite header signaling the file contains audio"); } /* check last timestamps */ if (have_video && have_audio && timestamp_distance(last_audio_timestamp, last_video_timestamp) >= 1000) { if (last_audio_timestamp > last_video_timestamp) { sprintf(message, "video stops %u ms before audio", last_audio_timestamp - last_video_timestamp); print_warning(WARNING_TIMESTAMP_VIDEO_ENDS_FIRST, filesize, message); } else { sprintf(message, "audio stops %u ms before video", last_video_timestamp - last_audio_timestamp); print_warning(WARNING_TIMESTAMP_AUDIO_ENDS_FIRST, filesize, message); } } /* check video keyframes */ if (have_video && keyframes_number == 0) { print_warning(WARNING_VIDEO_NO_KEYFRAME, filesize, "no keyframe detected, file is probably broken or incomplete"); } if (have_video && keyframes_number == video_frames_number) { print_warning(WARNING_VIDEO_ONLY_KEYFRAMES, filesize, "only keyframes detected, probably inefficient compression scheme used"); } /* only keyframes + onLastSecond bug */ if (have_video && have_on_last_second && keyframes_number == video_frames_number) { print_warning(WARNING_VIDEO_ONLY_KF_LAST_SEC, filesize, "only keyframes detected and onLastSecond event present, file is probably not playable"); } /* check onLastSecond timestamp */ if (have_on_last_second && (last_timestamp - on_last_second_timestamp) >= 2000) { sprintf(message, "onLastSecond event located %u ms before the last tag", last_timestamp - on_last_second_timestamp); print_warning(WARNING_METADATA_LAST_SECOND_BAD, filesize, message); } /* check onMetaData presence */ if (!have_on_metadata) { print_warning(WARNING_METADATA_NOT_PRESENT, filesize, "onMetaData event not found, file might not be playable"); } else { amf_node * n; int have_width, have_height; have_width = 0; have_height = 0; /* compute metadata, with a sensible set of unobstrusive options */ opts_loc.verbose = 0; opts_loc.reset_timestamps = 0; opts_loc.preserve_metadata = 0; opts_loc.all_keyframes = 0; opts_loc.error_handling = FLVMETA_IGNORE_ERRORS; opts_loc.insert_onlastsecond = 0; flv_reset(flv_in); if (get_flv_info(flv_in, &info, &opts_loc) != OK) { print_fatal(FATAL_INFO_COMPUTATION_ERROR, 0, "unable to compute file information"); goto end; } /* delete useless info data */ amf_data_free(info.original_on_metadata); /* more metadata checks */ for (n = amf_associative_array_first(on_metadata); n != NULL; n = amf_associative_array_next(n)) { byte * name; amf_data * data; byte type; number64 duration; if (info.have_audio) { duration = (info.last_timestamp - info.first_timestamp + info.audio_frame_duration) / 1000.0; } else { duration = (info.last_timestamp - info.first_timestamp + info.video_frame_duration) / 1000.0; } name = amf_string_get_bytes(amf_associative_array_get_name(n)); data = amf_associative_array_get_data(n); type = amf_data_get_type(data); /* TODO: check UTF-8 strings, in key, and value if string type */ /* hasMetadata (bool): true */ if (!strcmp((char*)name, "hasMetadata")) { if (type == AMF_TYPE_BOOLEAN) { if (amf_boolean_get_value(data) == 0) { print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, "hasMetadata should be set to true"); } } else { sprintf(message, "invalid type for hasMetadata: expected %s, got %s", get_amf_type_string(AMF_TYPE_BOOLEAN), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* hasVideo (bool) */ if (!strcmp((char*)name, "hasVideo")) { if (type == AMF_TYPE_BOOLEAN) { if (amf_boolean_get_value(data) != info.have_video) { sprintf(message, "hasVideo should be set to %s", info.have_video ? "true" : "false"); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { sprintf(message, "invalid type for hasVideo: expected %s, got %s", get_amf_type_string(AMF_TYPE_BOOLEAN), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* hasAudio (bool) */ if (!strcmp((char*)name, "hasAudio")) { if (type == AMF_TYPE_BOOLEAN) { if (amf_boolean_get_value(data) != info.have_audio) { sprintf(message, "hasAudio should be set to %s", info.have_audio ? "true" : "false"); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { sprintf(message, "invalid type for hasAudio: expected %s, got %s", get_amf_type_string(AMF_TYPE_BOOLEAN), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* duration (number) */ if (!strcmp((char*)name, "duration")) { if (type == AMF_TYPE_NUMBER) { number64 file_duration; file_duration = amf_number_get_value(data); if (fabs(file_duration - duration) >= 1.0) { sprintf(message, "duration should be %.12g, got %.12g", duration, file_duration); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { sprintf(message, "invalid type for duration: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* lasttimestamp: (number) */ if (!strcmp((char*)name, "lasttimestamp")) { if (type == AMF_TYPE_NUMBER) { number64 lasttimestamp, file_lasttimestamp; lasttimestamp = info.last_timestamp / 1000.0; file_lasttimestamp = amf_number_get_value(data); if (fabs(file_lasttimestamp - lasttimestamp) >= 1.0) { sprintf(message, "lasttimestamp should be %.12g, got %.12g", lasttimestamp, file_lasttimestamp); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { sprintf(message, "invalid type for lasttimestamp: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* lastkeyframetimestamp: (number) */ if (!strcmp((char*)name, "lastkeyframetimestamp")) { if (type == AMF_TYPE_NUMBER) { number64 lastkeyframetimestamp, file_lastkeyframetimestamp; lastkeyframetimestamp = info.last_keyframe_timestamp / 1000.0; file_lastkeyframetimestamp = amf_number_get_value(data); if (fabs(file_lastkeyframetimestamp - lastkeyframetimestamp) >= 1.0) { sprintf(message, "lastkeyframetimestamp should be %.12g, got %.12g", lastkeyframetimestamp, file_lastkeyframetimestamp); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { sprintf(message, "invalid type for lastkeyframetimestamp: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* width: (number) */ if (!strcmp((char*)name, "width")) { if (type == AMF_TYPE_NUMBER) { if (info.have_video) { number64 width, file_width; width = info.video_width; file_width = amf_number_get_value(data); if (fabs(file_width - width) >= 1.0 && width != 0) { sprintf(message, "width should be %.12g, got %.12g", width, file_width); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } have_width = 1; } else { print_warning(WARNING_AMF_DATA_VIDEO_NEEDED, on_metadata_offset, "width metadata present without video data"); } } else { sprintf(message, "invalid type for width: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* height: (number) */ if (!strcmp((char*)name, "height")) { if (type == AMF_TYPE_NUMBER) { if (info.have_video) { number64 height, file_height; height = info.video_height; file_height = amf_number_get_value(data); if (fabs(file_height - height) >= 1.0 && height != 0) { sprintf(message, "height should be %.12g, got %.12g", height, file_height); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } have_height = 1; } else { print_warning(WARNING_AMF_DATA_VIDEO_NEEDED, on_metadata_offset, "height metadata present without video data"); } } else { sprintf(message, "invalid type for height: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* videodatarate: (number)*/ if (!strcmp((char*)name, "videodatarate")) { if (type == AMF_TYPE_NUMBER) { if (info.have_video) { number64 videodatarate, file_videodatarate; videodatarate = ((info.real_video_data_size / 1024.0) * 8.0) / duration; file_videodatarate = amf_number_get_value(data); if (fabs(file_videodatarate - videodatarate) >= 1.0) { sprintf(message, "videodatarate should be %.12g, got %.12g", videodatarate, file_videodatarate); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_VIDEO_NEEDED, on_metadata_offset, "videodatarate metadata present without video data"); } } else { sprintf(message, "invalid type for videodatarate: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* framerate: (number) */ if (!strcmp((char*)name, "framerate")) { if (type == AMF_TYPE_NUMBER) { if (info.have_video) { number64 framerate, file_framerate; framerate = info.video_frames_number / duration; file_framerate = amf_number_get_value(data); if (fabs(file_framerate - framerate) >= 1.0) { sprintf(message, "framerate should be %.12g, got %.12g", framerate, file_framerate); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_VIDEO_NEEDED, on_metadata_offset, "framerate metadata present without video data"); } } else { sprintf(message, "invalid type for framerate: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* audiodatarate: (number) */ if (!strcmp((char*)name, "audiodatarate")) { if (type == AMF_TYPE_NUMBER) { if (info.have_audio) { number64 audiodatarate, file_audiodatarate; audiodatarate = ((info.real_audio_data_size / 1024.0) * 8.0) / duration; file_audiodatarate = amf_number_get_value(data); if (fabs(file_audiodatarate - audiodatarate) >= 1.0) { sprintf(message, "audiodatarate should be %.12g, got %.12g", audiodatarate, file_audiodatarate); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_AUDIO_NEEDED, on_metadata_offset, "audiodatarate metadata present without audio data"); } } else { sprintf(message, "invalid type for audiodatarate: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* audiosamplerate: (number) */ if (!strcmp((char*)name, "audiosamplerate")) { if (type == AMF_TYPE_NUMBER) { if (info.have_audio) { number64 audiosamplerate, file_audiosamplerate; audiosamplerate = 0.0; switch (info.audio_rate) { case FLV_AUDIO_TAG_SOUND_RATE_5_5: audiosamplerate = 5500.0; break; case FLV_AUDIO_TAG_SOUND_RATE_11: audiosamplerate = 11000.0; break; case FLV_AUDIO_TAG_SOUND_RATE_22: audiosamplerate = 22050.0; break; case FLV_AUDIO_TAG_SOUND_RATE_44: audiosamplerate = 44100.0; break; } file_audiosamplerate = amf_number_get_value(data); /* 100 tolerance, since 44000 is sometimes used instead of 44100 */ if (fabs(file_audiosamplerate - audiosamplerate) > 100.0) { sprintf(message, "audiosamplerate should be %.12g, got %.12g", audiosamplerate, file_audiosamplerate); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_AUDIO_NEEDED, on_metadata_offset, "audiosamplerate metadata present without audio data"); } } else { sprintf(message, "invalid type for audiosamplerate: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* audiosamplesize: (number) */ if (!strcmp((char*)name, "audiosamplesize")) { if (type == AMF_TYPE_NUMBER) { if (info.have_audio) { number64 audiosamplesize, file_audiosamplesize; audiosamplesize = 0.0; switch (info.audio_size) { case FLV_AUDIO_TAG_SOUND_SIZE_8: audiosamplesize = 8.0; break; case FLV_AUDIO_TAG_SOUND_SIZE_16: audiosamplesize = 16.0; break; } file_audiosamplesize = amf_number_get_value(data); if (fabs(file_audiosamplesize - audiosamplesize) >= 1.0) { sprintf(message, "audiosamplesize should be %.12g, got %.12g", audiosamplesize, file_audiosamplesize); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_AUDIO_NEEDED, on_metadata_offset, "audiosamplesize metadata present without audio data"); } } else { sprintf(message, "invalid type for audiosamplesize: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* stereo: (boolean) */ if (!strcmp((char*)name, "stereo")) { if (type == AMF_TYPE_BOOLEAN) { if (info.have_audio) { uint8 stereo, file_stereo; stereo = (info.audio_stereo == FLV_AUDIO_TAG_SOUND_TYPE_STEREO); file_stereo = amf_boolean_get_value(data); if (file_stereo != stereo) { sprintf(message, "stereo should be %s", stereo ? "true" : "false"); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_AUDIO_NEEDED, on_metadata_offset, "stereo metadata present without audio data"); } } else { sprintf(message, "invalid type for stereo: expected %s, got %s", get_amf_type_string(AMF_TYPE_BOOLEAN), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* filesize: (number) */ if (!strcmp((char*)name, "filesize")) { if (type == AMF_TYPE_NUMBER) { number64 real_filesize, file_filesize; real_filesize = (number64)(filesize); file_filesize = amf_number_get_value(data); if (fabs(file_filesize - real_filesize) >= 1.0) { sprintf(message, "filesize should be %.12g, got %.12g", real_filesize, file_filesize); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { sprintf(message, "invalid type for filesize: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* videosize: (number) */ if (!strcmp((char*)name, "videosize")) { if (type == AMF_TYPE_NUMBER) { if (info.have_video) { number64 videosize, file_videosize; videosize = (number64)(info.video_data_size); file_videosize = amf_number_get_value(data); if (fabs(file_videosize - videosize) >= 1.0) { sprintf(message, "videosize should be %.12g, got %.12g", videosize, file_videosize); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_VIDEO_NEEDED, on_metadata_offset, "videosize metadata present without video data"); } } else { sprintf(message, "invalid type for videosize: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* audiosize: (number) */ if (!strcmp((char*)name, "audiosize")) { if (type == AMF_TYPE_NUMBER) { if (info.have_audio) { number64 audiosize, file_audiosize; audiosize = (number64)(info.audio_data_size); file_audiosize = amf_number_get_value(data); if (fabs(file_audiosize - audiosize) >= 1.0) { sprintf(message, "audiosize should be %.12g, got %.12g", audiosize, file_audiosize); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_AUDIO_NEEDED, on_metadata_offset, "audiosize metadata present without audio data"); } } else { sprintf(message, "invalid type for audiosize: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* datasize: (number) */ if (!strcmp((char*)name, "datasize")) { if (type == AMF_TYPE_NUMBER) { number64 datasize, file_datasize; uint32 on_metadata_size; on_metadata_size = FLV_TAG_SIZE + (uint32)(amf_data_size(on_metadata_name) + amf_data_size(on_metadata)); datasize = (number64)(info.meta_data_size + on_metadata_size); file_datasize = amf_number_get_value(data); if (fabs(file_datasize - datasize) >= 1.0) { sprintf(message, "datasize should be %.12g, got %.12g", datasize, file_datasize); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { sprintf(message, "invalid type for datasize: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* audiocodecid: (number) */ if (!strcmp((char*)name, "audiocodecid")) { if (type == AMF_TYPE_NUMBER) { if (info.have_audio) { number64 audiocodecid, file_audiocodecid; audiocodecid = (number64)info.audio_codec; file_audiocodecid = amf_number_get_value(data); if (fabs(file_audiocodecid - audiocodecid) >= 1.0) { sprintf(message, "audiocodecid should be %.12g, got %.12g", audiocodecid, file_audiocodecid); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_AUDIO_NEEDED, on_metadata_offset, "audiocodecid metadata present without audio data"); } } else { sprintf(message, "invalid type for audiocodecid: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* videocodecid: (number) */ if (!strcmp((char*)name, "videocodecid")) { if (type == AMF_TYPE_NUMBER) { if (info.have_video) { number64 videocodecid, file_videocodecid; videocodecid = (number64)info.video_codec; file_videocodecid = amf_number_get_value(data); if (fabs(file_videocodecid - videocodecid) >= 1.0) { sprintf(message, "videocodecid should be %.12g, got %.12g", videocodecid, file_videocodecid); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_VIDEO_NEEDED, on_metadata_offset, "videocodecid metadata present without video data"); } } else { sprintf(message, "invalid type for videocodecid: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* audiodelay: (number) */ if (!strcmp((char*)name, "audiodelay")) { if (type == AMF_TYPE_NUMBER) { if (info.have_audio && info.have_video) { number64 audiodelay, file_audiodelay; audiodelay = ((sint32)info.audio_first_timestamp - (sint32)info.video_first_timestamp) / 1000.0; file_audiodelay = amf_number_get_value(data); if (fabs(file_audiodelay - audiodelay) >= 1.0) { sprintf(message, "audiodelay should be %.12g, got %.12g", audiodelay, file_audiodelay); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { print_warning(WARNING_AMF_DATA_AUDIO_VIDEO_NEEDED, on_metadata_offset, "audiodelay metadata present without audio and video data"); } } else { sprintf(message, "invalid type for audiodelay: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* canSeekToEnd: (boolean) */ if (!strcmp((char*)name, "canSeekToEnd")) { if (type == AMF_TYPE_BOOLEAN) { if (amf_boolean_get_value(data) != info.can_seek_to_end) { sprintf(message, "canSeekToEnd should be set to %s", info.can_seek_to_end ? "true" : "false"); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { sprintf(message, "invalid type for canSeekToEnd: expected %s, got %s", get_amf_type_string(AMF_TYPE_BOOLEAN), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* hasKeyframes: (boolean) */ if (!strcmp((char*)name, "hasKeyframes")) { if (type == AMF_TYPE_BOOLEAN) { if (amf_boolean_get_value(data) != info.have_keyframes) { sprintf(message, "hasKeyframes should be set to %s", info.have_keyframes ? "true" : "false"); print_warning(WARNING_AMF_DATA_INVALID_VALUE, on_metadata_offset, message); } } else { sprintf(message, "invalid type for hasKeyframes: expected %s, got %s", get_amf_type_string(AMF_TYPE_BOOLEAN), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } /* keyframes: (object) */ if (!strcmp((char*)name, "keyframes")) { if (type == AMF_TYPE_OBJECT) { amf_data * file_times, * file_filepositions; file_times = amf_object_get(data, "times"); file_filepositions = amf_object_get(data, "filepositions"); /* check sub-arrays' presence */ if (file_times == NULL) { print_warning(WARNING_KEYFRAMES_TIMES_MISSING, on_metadata_offset, "Missing times metadata"); } if (file_filepositions == NULL) { print_warning(WARNING_KEYFRAMES_FILEPOS_MISSING, on_metadata_offset, "Missing filepositions metadata"); } if (file_times != NULL && file_filepositions != NULL) { /* check types */ uint8 times_type, fp_type; times_type = amf_data_get_type(file_times); if (times_type != AMF_TYPE_ARRAY) { sprintf(message, "invalid type for times: expected %s, got %s", get_amf_type_string(AMF_TYPE_ARRAY), get_amf_type_string(times_type)); print_warning(WARNING_KEYFRAMES_TIMES_TYPE_BAD, on_metadata_offset, message); } fp_type = amf_data_get_type(file_filepositions); if (fp_type != AMF_TYPE_ARRAY) { sprintf(message, "invalid type for filepositions: expected %s, got %s", get_amf_type_string(AMF_TYPE_ARRAY), get_amf_type_string(fp_type)); print_warning(WARNING_KEYFRAMES_FILEPOS_TYPE_BAD, on_metadata_offset, message); } if (times_type == AMF_TYPE_ARRAY && fp_type == AMF_TYPE_ARRAY) { /* check array sizes */ if (amf_array_size(info.times) != amf_array_size(file_times) || amf_array_size(info.filepositions) != amf_array_size(file_filepositions) || amf_array_size(file_filepositions) != amf_array_size(file_times)) { print_warning(WARNING_KEYFRAMES_ARRAY_LENGTH_BAD, on_metadata_offset, "invalid keyframes arrays length"); } else { number64 last_file_time; int have_last_time; amf_node * ff_node, * ft_node, * f_node, * t_node; /* iterate in parallel, report diffs */ last_file_time = 0; have_last_time = 0; t_node = amf_array_first(info.times); f_node = amf_array_first(info.filepositions); ft_node = amf_array_first(file_times); ff_node = amf_array_first(file_filepositions); while (t_node != NULL && f_node != NULL && ft_node != NULL && ff_node != NULL) { number64 time, f_time, position, f_position; time = amf_number_get_value(amf_array_get(t_node)); position = amf_number_get_value(amf_array_get(f_node)); /* time */ if (amf_data_get_type(amf_array_get(ft_node)) != AMF_TYPE_NUMBER) { sprintf(message, "invalid type for time: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_KEYFRAMES_TIME_TYPE_BAD, on_metadata_offset, message); } else { f_time = amf_number_get_value(amf_array_get(ft_node)); if (fabs(time - f_time) >= 1.0) { sprintf(message, "invalid keyframe time: expected %.12g, got %.12g", time, f_time); print_warning(WARNING_KEYFRAMES_TIME_BAD, on_metadata_offset, message); } /* check for duplicate time, can happen in H.264 files */ if (have_last_time && last_file_time == f_time) { sprintf(message, "Duplicate keyframe time: %.12g", f_time); print_warning(WARNING_KEYFRAMES_TIME_DUPLICATE, on_metadata_offset, message); } have_last_time = 1; last_file_time = f_time; } /* position */ if (amf_data_get_type(amf_array_get(ff_node)) != AMF_TYPE_NUMBER) { sprintf(message, "invalid type for file position: expected %s, got %s", get_amf_type_string(AMF_TYPE_NUMBER), get_amf_type_string(type)); print_warning(WARNING_KEYFRAMES_POS_TYPE_BAD, on_metadata_offset, message); } else { f_position = amf_number_get_value(amf_array_get(ff_node)); if (fabs(position - f_position) >= 1.0) { sprintf(message, "invalid keyframe file position: expected %.12g, got %.12g", position, f_position); print_warning(WARNING_KEYFRAMES_POS_BAD, on_metadata_offset, message); } } /* next entry */ t_node = amf_array_next(t_node); f_node = amf_array_next(f_node); ft_node = amf_array_next(ft_node); ff_node = amf_array_next(ff_node); } } } } } else { sprintf(message, "invalid type for keyframes: expected %s, got %s", get_amf_type_string(AMF_TYPE_BOOLEAN), get_amf_type_string(type)); print_warning(WARNING_AMF_DATA_INVALID_TYPE, on_metadata_offset, message); } } } /* we need to release the info.keyframes pointer, because as opposed to update.c, these amf data do not get added into another object, therefore keep memory ownership */ amf_data_free(info.keyframes); /* missing width or height can cause size problem in various players */ if (info.have_video) { if (!have_width) { print_error(ERROR_VIDEO_WIDTH_MISSING, on_metadata_offset, "width information not found in metadata, problems might occur in some players"); } if (!have_height) { print_error(ERROR_VIDEO_HEIGHT_MISSING, on_metadata_offset, "height information not found in metadata, problems might occur in some players"); } } } /* could we compute video resolution ? */ if (info.video_width == 0 && info.video_height == 0) { print_warning(WARNING_VIDEO_SIZE_ERROR, filesize, "unable to determine video resolution"); } /* global info */ if (info.have_video) { /* video codec */ sprintf(message, "video codec is %s", dump_string_get_video_codec(prev_video_tag)); print_info(INFO_VIDEO_CODEC, 0, message); } if (info.have_audio) { /* audio info */ sprintf(message, "audio format is %s (%s, %s-bit, %s kHz)", dump_string_get_sound_format(prev_audio_tag), dump_string_get_sound_type(prev_audio_tag), dump_string_get_sound_size(prev_audio_tag), dump_string_get_sound_rate(prev_audio_tag) ); print_info(INFO_AUDIO_FORMAT, 0, message); } /* does the file use extended timestamps ? */ if (last_timestamp > 0x00FFFFFF) { print_info(INFO_TIMESTAMP_USE_EXTENDED, 0, "extended timestamps used in the file"); } /* is the file larger than 4GB ? */ if (filesize > 0xFFFFFFFFULL) { print_info(INFO_GENERAL_LARGE_FILE, 0, "file is larger than 4 GB"); } end: report_end(opts, &ctxt, errors, warnings); amf_data_free(on_metadata); amf_data_free(on_metadata_name); flv_close(flv_in); return (errors > 0) ? ERROR_INVALID_FLV_FILE : OK; } flvmeta-1.2.2/src/check.h000066400000000000000000000223261346231570700152040ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __CHECK_H__ #define __CHECK_H__ #include "flvmeta.h" /* message level codes */ #define LEVEL_FATAL "F" #define LEVEL_ERROR "E" #define LEVEL_WARNING "W" #define LEVEL_INFO "I" /* message topic codes */ #define TOPIC_GENERAL_FORMAT "10" #define TOPIC_HEADER "11" #define TOPIC_PREV_TAG_SIZE "12" #define TOPIC_TAG_FORMAT "20" #define TOPIC_TAG_TYPES "30" #define TOPIC_TIMESTAMPS "40" #define TOPIC_AUDIO_DATA "50" #define TOPIC_AUDIO_CODECS "51" #define TOPIC_VIDEO_DATA "60" #define TOPIC_VIDEO_CODECS "61" #define TOPIC_METADATA "70" #define TOPIC_AMF_DATA "80" #define TOPIC_KEYFRAMES "81" #define TOPIC_CUE_POINTS "82" /* info, warning, error codes, sorted by unique identifier */ #define FATAL_HEADER_EOF LEVEL_FATAL TOPIC_HEADER "001" #define FATAL_HEADER_NO_SIGNATURE LEVEL_FATAL TOPIC_HEADER "002" #define ERROR_HEADER_BAD_VERSION LEVEL_ERROR TOPIC_HEADER "003" #define ERROR_HEADER_NO_STREAMS LEVEL_ERROR TOPIC_HEADER "004" #define INFO_HEADER_NO_AUDIO LEVEL_INFO TOPIC_HEADER "005" #define WARNING_HEADER_NO_VIDEO LEVEL_WARNING TOPIC_HEADER "006" #define ERROR_HEADER_BAD_RESERVED_FLAGS LEVEL_ERROR TOPIC_HEADER "007" #define ERROR_HEADER_BAD_OFFSET LEVEL_ERROR TOPIC_HEADER "008" #define FATAL_PREV_TAG_SIZE_EOF LEVEL_FATAL TOPIC_PREV_TAG_SIZE "009" #define ERROR_PREV_TAG_SIZE_BAD_FIRST LEVEL_ERROR TOPIC_PREV_TAG_SIZE "010" #define FATAL_GENERAL_NO_TAG LEVEL_FATAL TOPIC_GENERAL_FORMAT "011" #define FATAL_TAG_EOF LEVEL_FATAL TOPIC_TAG_FORMAT "012" #define ERROR_TAG_TYPE_UNKNOWN LEVEL_ERROR TOPIC_TAG_TYPES "013" #define WARNING_HEADER_UNEXPECTED_VIDEO LEVEL_WARNING TOPIC_HEADER "014" #define WARNING_HEADER_UNEXPECTED_AUDIO LEVEL_WARNING TOPIC_HEADER "015" #define FATAL_TAG_BODY_LENGTH_OVERFLOW LEVEL_FATAL TOPIC_TAG_FORMAT "016" #define WARNING_TAG_BODY_LENGTH_LARGE LEVEL_WARNING TOPIC_TAG_FORMAT "017" #define WARNING_TAG_BODY_LENGTH_ZERO LEVEL_WARNING TOPIC_TAG_FORMAT "018" #define ERROR_TIMESTAMP_FIRST_NON_ZERO LEVEL_ERROR TOPIC_TIMESTAMPS "019" #define ERROR_TIMESTAMP_AUDIO_DECREASE LEVEL_ERROR TOPIC_TIMESTAMPS "020" #define ERROR_TIMESTAMP_VIDEO_DECREASE LEVEL_ERROR TOPIC_TIMESTAMPS "021" #define ERROR_TIMESTAMP_OVERFLOW LEVEL_ERROR TOPIC_TIMESTAMPS "022" #define ERROR_TIMESTAMP_DECREASE LEVEL_ERROR TOPIC_TIMESTAMPS "023" #define WARNING_TIMESTAMP_DESYNC LEVEL_WARNING TOPIC_TIMESTAMPS "024" #define ERROR_TAG_STREAM_ID_NON_ZERO LEVEL_ERROR TOPIC_TAG_FORMAT "025" #define WARNING_AUDIO_FORMAT_CHANGED LEVEL_WARNING TOPIC_AUDIO_DATA "026" #define WARNING_AUDIO_CODEC_UNKNOWN LEVEL_WARNING TOPIC_AUDIO_CODECS "027" #define WARNING_AUDIO_CODEC_RESERVED LEVEL_WARNING TOPIC_AUDIO_CODECS "028" #define WARNING_AUDIO_CODEC_AAC_BAD LEVEL_WARNING TOPIC_AUDIO_CODECS "029" #define WARNING_AUDIO_CODEC_NELLYMOSER_BAD LEVEL_WARNING TOPIC_AUDIO_CODECS "030" #define WARNING_AUDIO_CODEC_AAC_MONO LEVEL_WARNING TOPIC_AUDIO_CODECS "031" #define WARNING_AUDIO_CODEC_LINEAR_PCM LEVEL_WARNING TOPIC_AUDIO_CODECS "032" #define WARNING_VIDEO_FORMAT_CHANGED LEVEL_WARNING TOPIC_VIDEO_DATA "033" #define ERROR_VIDEO_FRAME_TYPE_UNKNOWN LEVEL_ERROR TOPIC_VIDEO_DATA "034" #define WARNING_VIDEO_NO_FIRST_KEYFRAME LEVEL_WARNING TOPIC_VIDEO_DATA "035" #define ERROR_VIDEO_CODEC_UNKNOWN LEVEL_ERROR TOPIC_VIDEO_CODECS "036" #define WARNING_VIDEO_CODEC_JPEG LEVEL_WARNING TOPIC_VIDEO_CODECS "037" #define WARNING_METADATA_EMPTY LEVEL_WARNING TOPIC_METADATA "038" #define ERROR_METADATA_NAME_INVALID LEVEL_ERROR TOPIC_METADATA "039" #define ERROR_METADATA_DATA_INVALID LEVEL_ERROR TOPIC_METADATA "040" #define ERROR_METADATA_NAME_INVALID_TYPE LEVEL_ERROR TOPIC_METADATA "041" #define WARNING_METADATA_NAME_EMPTY LEVEL_WARNING TOPIC_METADATA "042" #define WARNING_METADATA_DATA_REMAINING LEVEL_WARNING TOPIC_METADATA "043" #define WARNING_METADATA_DATA_MISSING LEVEL_WARNING TOPIC_METADATA "044" #define WARNING_METADATA_LAST_SECOND_DUP LEVEL_WARNING TOPIC_METADATA "045" #define ERROR_METADATA_DATA_INVALID_TYPE LEVEL_ERROR TOPIC_METADATA "046" #define WARNING_METADATA_BAD_TAG LEVEL_WARNING TOPIC_METADATA "047" #define WARNING_METADATA_BAD_TIMESTAMP LEVEL_WARNING TOPIC_METADATA "048" #define WARNING_METADATA_DUPLICATE LEVEL_WARNING TOPIC_METADATA "049" #define INFO_METADATA_NAME_UNKNOWN LEVEL_INFO TOPIC_METADATA "050" #define ERROR_PREV_TAG_SIZE_BAD LEVEL_ERROR TOPIC_PREV_TAG_SIZE "051" #define WARNING_HEADER_VIDEO_NOT_FOUND LEVEL_WARNING TOPIC_HEADER "052" #define WARNING_HEADER_AUDIO_NOT_FOUND LEVEL_WARNING TOPIC_HEADER "053" #define WARNING_TIMESTAMP_VIDEO_ENDS_FIRST LEVEL_WARNING TOPIC_TIMESTAMPS "054" #define WARNING_TIMESTAMP_AUDIO_ENDS_FIRST LEVEL_WARNING TOPIC_TIMESTAMPS "055" #define WARNING_VIDEO_NO_KEYFRAME LEVEL_WARNING TOPIC_VIDEO_DATA "056" #define WARNING_VIDEO_ONLY_KEYFRAMES LEVEL_WARNING TOPIC_VIDEO_DATA "057" #define WARNING_VIDEO_ONLY_KF_LAST_SEC LEVEL_WARNING TOPIC_VIDEO_DATA "058" #define WARNING_METADATA_LAST_SECOND_BAD LEVEL_WARNING TOPIC_METADATA "059" #define WARNING_METADATA_NOT_PRESENT LEVEL_WARNING TOPIC_METADATA "060" #define FATAL_INFO_COMPUTATION_ERROR LEVEL_FATAL TOPIC_GENERAL_FORMAT "061" #define WARNING_AMF_DATA_INVALID_VALUE LEVEL_WARNING TOPIC_AMF_DATA "062" #define WARNING_AMF_DATA_INVALID_TYPE LEVEL_WARNING TOPIC_AMF_DATA "063" #define WARNING_AMF_DATA_VIDEO_NEEDED LEVEL_WARNING TOPIC_AMF_DATA "064" #define WARNING_AMF_DATA_AUDIO_NEEDED LEVEL_WARNING TOPIC_AMF_DATA "065" #define WARNING_AMF_DATA_AUDIO_VIDEO_NEEDED LEVEL_WARNING TOPIC_AMF_DATA "066" #define WARNING_KEYFRAMES_TIMES_MISSING LEVEL_WARNING TOPIC_KEYFRAMES "067" #define WARNING_KEYFRAMES_FILEPOS_MISSING LEVEL_WARNING TOPIC_KEYFRAMES "068" #define WARNING_KEYFRAMES_TIMES_TYPE_BAD LEVEL_WARNING TOPIC_KEYFRAMES "069" #define WARNING_KEYFRAMES_FILEPOS_TYPE_BAD LEVEL_WARNING TOPIC_KEYFRAMES "070" #define WARNING_KEYFRAMES_ARRAY_LENGTH_BAD LEVEL_WARNING TOPIC_KEYFRAMES "071" #define WARNING_KEYFRAMES_TIME_TYPE_BAD LEVEL_WARNING TOPIC_KEYFRAMES "072" #define WARNING_KEYFRAMES_POS_TYPE_BAD LEVEL_WARNING TOPIC_KEYFRAMES "073" #define WARNING_KEYFRAMES_TIME_BAD LEVEL_WARNING TOPIC_KEYFRAMES "074" #define WARNING_KEYFRAMES_POS_BAD LEVEL_WARNING TOPIC_KEYFRAMES "075" #define WARNING_KEYFRAMES_TIME_DUPLICATE LEVEL_WARNING TOPIC_KEYFRAMES "076" #define ERROR_VIDEO_WIDTH_MISSING LEVEL_ERROR TOPIC_VIDEO_DATA "077" #define ERROR_VIDEO_HEIGHT_MISSING LEVEL_ERROR TOPIC_VIDEO_DATA "078" #define WARNING_VIDEO_SIZE_ERROR LEVEL_WARNING TOPIC_VIDEO_DATA "079" #define INFO_VIDEO_CODEC LEVEL_INFO TOPIC_VIDEO_CODECS "080" #define INFO_AUDIO_FORMAT LEVEL_INFO TOPIC_AUDIO_CODECS "081" #define INFO_TIMESTAMP_USE_EXTENDED LEVEL_INFO TOPIC_TIMESTAMPS "082" #define INFO_GENERAL_LARGE_FILE LEVEL_INFO TOPIC_GENERAL_FORMAT "083" #define FATAL_CONSECUTIVE_UNKNOWN_TAGS LEVEL_FATAL TOPIC_TAG_TYPES "084" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* check FLV file validity */ int check_flv_file(const flvmeta_opts * opts); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __CHECK_H__ */ flvmeta-1.2.2/src/compat/000077500000000000000000000000001346231570700152345ustar00rootroot00000000000000flvmeta-1.2.2/src/compat/getopt.c000066400000000000000000001000751346231570700167050ustar00rootroot00000000000000/* Getopt for GNU. NOTE: getopt is now part of the C library, so if you don't know what "Keep this file name-space clean" means, talk to drepper@gnu.org before changing it! Copyright (C) 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc. This file is part of the GNU C Library. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* This tells Alpha OSF/1 not to define a getopt prototype in . Ditto for AIX 3.2 and . */ #ifndef _NO_PROTO # define _NO_PROTO #endif #ifdef HAVE_CONFIG_H # include #endif #include #include /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 # include # if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION # define ELIDE_CODE # endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ # include # include #endif /* GNU C library. */ #include #ifdef VMS # include #endif #ifdef _LIBC # include #else /* This is for other GNU distributions with internationalized messages. */ # include "gettext.h" #endif #define _(msgid) gettext (msgid) #if defined _LIBC && defined USE_IN_LIBIO # include #endif #ifndef attribute_hidden # define attribute_hidden #endif /* This version of `getopt' appears to the caller like standard Unix `getopt' but it behaves differently for the user, since it allows the user to intersperse the options with the other arguments. As `getopt' works, it permutes the elements of ARGV so that, when it is done, all the options precede everything else. Thus all application programs are extended to handle flexible argument order. Setting the environment variable POSIXLY_CORRECT disables permutation. Then the behavior is completely standard. GNU application programs can use a third alternative mode in which they can distinguish the relative order of options and other arguments. */ #include "getopt.h" /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* 1003.2 says this must be 1 before any call. */ int optind = 1; /* Formerly, initialization of getopt depended on optind==0, which causes problems with re-calling getopt as programs generally don't know that. */ int __getopt_initialized attribute_hidden; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ int optopt = '?'; /* Describe how to deal with options that follow non-option ARGV-elements. If the caller did not specify anything, the default is REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise. REQUIRE_ORDER means don't recognize them as options; stop option processing when the first non-option is seen. This is what Unix does. This mode of operation is selected by either setting the environment variable POSIXLY_CORRECT, or using `+' as the first character of the list of option characters. PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. RETURN_IN_ORDER is an option available to programs that were written to expect options and other ARGV-elements in any order and that care about the ordering of the two. We describe each non-option ARGV-element as if it were the argument of an option with character code 1. Using `-' as the first character of the list of option characters selects this mode of operation. The special argument `--' forces an end of option-scanning regardless of the value of `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return -1 with `optind' != ARGC. */ static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; /* Value of POSIXLY_CORRECT environment variable. */ static char *posixly_correct; #ifndef __GNU_LIBRARY__ /* Avoid depending on library functions or files whose names are inconsistent. */ //#ifndef getenv //extern char *getenv (); //#endif #endif /* not __GNU_LIBRARY__ */ /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; #ifdef _LIBC /* Stored original parameters. XXX This is no good solution. We should rather copy the args so that we can compare them later. But we must not use malloc(3). */ extern int __libc_argc; extern char **__libc_argv; /* Bash 2.0 gives us an environment variable containing flags indicating ARGV elements that should not be considered arguments. */ # ifdef USE_NONOPTION_FLAGS /* Defined in getopt_init.c */ extern char *__getopt_nonoption_flags; static int nonoption_flags_max_len; static int nonoption_flags_len; # endif # ifdef USE_NONOPTION_FLAGS # define SWAP_FLAGS(ch1, ch2) \ if (nonoption_flags_len > 0) \ { \ char __tmp = __getopt_nonoption_flags[ch1]; \ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ __getopt_nonoption_flags[ch2] = __tmp; \ } # else # define SWAP_FLAGS(ch1, ch2) # endif #else /* !_LIBC */ # define SWAP_FLAGS(ch1, ch2) #endif /* _LIBC */ /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The other is elements [last_nonopt,optind), which contains all the options processed since those non-options were skipped. `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. */ static void exchange (char **argv) { int bottom = first_nonopt; int middle = last_nonopt; int top = optind; char *tem; /* Exchange the shorter segment with the far end of the longer segment. That puts the shorter segment into the right place. It leaves the longer segment in the right place overall, but it consists of two parts that need to be swapped next. */ #if defined _LIBC && defined USE_NONOPTION_FLAGS /* First make sure the handling of the `__getopt_nonoption_flags' string can work normally. Our top argument must be in the range of the string. */ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { /* We must extend the array. The user plays games with us and presents new arguments. */ char *new_str = malloc (top + 1); if (new_str == NULL) nonoption_flags_len = nonoption_flags_max_len = 0; else { memset (__mempcpy (new_str, __getopt_nonoption_flags, nonoption_flags_max_len), '\0', top + 1 - nonoption_flags_max_len); nonoption_flags_max_len = top + 1; __getopt_nonoption_flags = new_str; } } #endif while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { /* Bottom segment is the short one. */ int len = middle - bottom; register int i; /* Swap it with the top part of the top segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); } /* Exclude the moved bottom segment from further swapping. */ top -= len; } else { /* Top segment is the short one. */ int len = top - middle; register int i; /* Swap it with the bottom part of the bottom segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; SWAP_FLAGS (bottom + i, middle + i); } /* Exclude the moved top segment from further swapping. */ bottom += len; } } /* Update records for the slots the non-options now occupy. */ first_nonopt += (optind - last_nonopt); last_nonopt = optind; } /* Initialize the internal data when the first call is made. */ static const char * _getopt_initialize (int argc, char *const *argv, const char *optstring) { /* Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ first_nonopt = last_nonopt = optind; nextchar = NULL; posixly_correct = getenv ("POSIXLY_CORRECT"); /* Determine how to handle the ordering of options and nonoptions. */ if (optstring[0] == '-') { ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; } else if (posixly_correct != NULL) ordering = REQUIRE_ORDER; else ordering = PERMUTE; #if defined _LIBC && defined USE_NONOPTION_FLAGS if (posixly_correct == NULL && argc == __libc_argc && argv == __libc_argv) { if (nonoption_flags_max_len == 0) { if (__getopt_nonoption_flags == NULL || __getopt_nonoption_flags[0] == '\0') nonoption_flags_max_len = -1; else { const char *orig_str = __getopt_nonoption_flags; int len = nonoption_flags_max_len = strlen (orig_str); if (nonoption_flags_max_len < argc) nonoption_flags_max_len = argc; __getopt_nonoption_flags = (char *) malloc (nonoption_flags_max_len); if (__getopt_nonoption_flags == NULL) nonoption_flags_max_len = -1; else memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), '\0', nonoption_flags_max_len - len); } } nonoption_flags_len = nonoption_flags_max_len; } else nonoption_flags_len = 0; #endif return optstring; } /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns -1. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal (int argc, char *const *argv, const char *optstring, const struct option *longopts, int *longind, int long_only) { int print_errors = opterr; if (optstring[0] == ':') print_errors = 0; if (argc < 1) return -1; optarg = NULL; if (optind == 0 || !__getopt_initialized) { if (optind == 0) optind = 1; /* Don't scan ARGV[0], the program name. */ optstring = _getopt_initialize (argc, argv, optstring); __getopt_initialized = 1; } /* Test whether ARGV[optind] points to a non-option argument. Either it does not have option syntax, or there is an environment flag from the shell indicating it is not an option. The later information is only used when the used in the GNU libc. */ #if defined _LIBC && defined USE_NONOPTION_FLAGS # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ || (optind < nonoption_flags_len \ && __getopt_nonoption_flags[optind] == '1')) #else # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif if (nextchar == NULL || *nextchar == '\0') { /* Advance to the next ARGV-element. */ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been moved back by the user (who may also have changed the arguments). */ if (last_nonopt > optind) last_nonopt = optind; if (first_nonopt > optind) first_nonopt = optind; if (ordering == PERMUTE) { /* If we have just processed some options following some non-options, exchange them so that the options come first. */ if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (last_nonopt != optind) first_nonopt = optind; /* Skip any additional non-options and extend the range of non-options previously skipped. */ while (optind < argc && NONOPTION_P) optind++; last_nonopt = optind; } /* The special ARGV-element `--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (optind != argc && !strcmp (argv[optind], "--")) { optind++; if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (first_nonopt == last_nonopt) first_nonopt = optind; last_nonopt = argc; optind = argc; } /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return -1; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if (NONOPTION_P) { if (ordering == REQUIRE_ORDER) return -1; optarg = argv[optind++]; return 1; } /* We have found another option-ARGV-element. Skip the initial punctuation. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } /* Decode the current option-ARGV-element. */ /* Check whether the ARGV-element is a long option. If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't consider it an abbreviated form of a long option that starts with f. Otherwise there would be no way to give the -f short option. On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg "u". This distinction seems to be the most useful approach. */ if (longopts != NULL && (argv[optind][1] == '-' || (long_only && (argv[optind][2] || !strchr (optstring, argv[optind][1]))))) { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = -1; int option_index; for (nameend = nextchar; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == (unsigned int) strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]); #endif } nextchar += strlen (nextchar); optind++; optopt = 0; return '?'; } if (pfound != NULL) { option_index = indfound; optind++; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; int n; #endif if (argv[optind - 1][1] == '-') { /* --option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("\ %s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); #else fprintf (stderr, _("\ %s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); #endif } else { /* +option or -option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("\ %s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); #else fprintf (stderr, _("\ %s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); #endif } #if defined _LIBC && defined USE_IN_LIBIO if (n >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #endif } nextchar += strlen (nextchar); optopt = pfound->val; return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); #endif } nextchar += strlen (nextchar); optopt = pfound->val; return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' || strchr (optstring, *nextchar) == NULL) { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; int n; #endif if (argv[optind][1] == '-') { /* --option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); #else fprintf (stderr, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); #endif } else { /* +option or -option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); #else fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); #endif } #if defined _LIBC && defined USE_IN_LIBIO if (n >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #endif } nextchar = (char *) ""; optind++; optopt = 0; return '?'; } } /* Look at and handle the next short option-character. */ { char c = *nextchar++; char *temp = strchr (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') ++optind; if (temp == NULL || c == ':') { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; int n; #endif if (posixly_correct) { /* 1003.2 specifies the format of this message. */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: illegal option -- %c\n"), argv[0], c); #else fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c); #endif } else { #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: invalid option -- %c\n"), argv[0], c); #else fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c); #endif } #if defined _LIBC && defined USE_IN_LIBIO if (n >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #endif } optopt = c; return '?'; } /* Convenience. Treat POSIX -W foo same as long option --foo */ if (temp[0] == 'W' && temp[1] == ';') { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (print_errors) { /* 1003.2 specifies the format of this message. */ #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("%s: option requires an argument -- %c\n"), argv[0], c) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); #endif } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; return c; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; /* optarg is now the argument, see if it's in the table of longopts. */ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]); #endif } nextchar += strlen (nextchar); optind++; return '?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name); #endif } nextchar += strlen (nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); #endif } nextchar += strlen (nextchar); return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } nextchar = NULL; return 'W'; /* Let the application handle it. */ } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = NULL; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (print_errors) { /* 1003.2 specifies the format of this message. */ #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option requires an argument -- %c\n"), argv[0], c) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); #endif } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } int getopt (int argc, char *const *argv, const char *optstring) { return _getopt_internal (argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); } #endif /* Not ELIDE_CODE. */ #ifdef TEST /* Compile with -DTEST to make an executable for use in testing the above definition of `getopt'. */ int main (int argc, char **argv) { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; c = getopt (argc, argv, "abc:d:0123456789"); if (c == -1) break; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */ flvmeta-1.2.2/src/compat/getopt.h000066400000000000000000000137411346231570700167150ustar00rootroot00000000000000/* Declarations for getopt. Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2001, 2003 Free Software Foundation, Inc. This file is part of the GNU C Library. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef _GETOPT_H #ifndef __need_getopt # define _GETOPT_H 1 #endif /* If __GNU_LIBRARY__ is not already defined, either we are being used standalone, or this is the first header included in the source file. If we are being used with glibc, we need to include , but that does not exist if we are standalone. So: if __GNU_LIBRARY__ is not defined, include , which will pull in for us if it's from glibc. (Why ctype.h? It's guaranteed to exist and it doesn't flood the namespace with stuff the way some other headers do.) */ #if !defined __GNU_LIBRARY__ # include #endif #ifdef __cplusplus extern "C" { #endif /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ extern char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ extern int optind; /* Callers store zero here to inhibit the error message `getopt' prints for unrecognized options. */ extern int opterr; /* Set to an option character which was unrecognized. */ extern int optopt; #ifndef __need_getopt /* Describe the long-named options requested by the application. The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector of `struct option' terminated by an element containing a name which is zero. The field `has_arg' is: no_argument (or 0) if the option does not take an argument, required_argument (or 1) if the option requires an argument, optional_argument (or 2) if the option takes an optional argument. If the field `flag' is not NULL, it points to a variable that is set to the value given in the field `val' when the option is found, but left unchanged if the option is not found. To have a long-named option do something other than set an `int' to a compiled-in constant, such as set a value from `optarg', set the option's `flag' field to zero and its `val' field to a nonzero value (the equivalent single-letter option character, if there is one). For long options that have a zero `flag' field, `getopt' returns the contents of the `val' field. */ struct option { const char *name; /* has_arg can't be an enum because some compilers complain about type mismatches in all the code that assumes it is an int. */ int has_arg; int *flag; int val; }; /* Names for the values of the `has_arg' field of `struct option'. */ # define no_argument 0 # define required_argument 1 # define optional_argument 2 #endif /* need getopt */ /* Get definitions and prototypes for functions to process the arguments in ARGV (ARGC of them, minus the program name) for options given in OPTS. Return the option character from OPTS just read. Return -1 when there are no more options. For unrecognized options, or options missing arguments, `optopt' is set to the option letter, and '?' is returned. The OPTS string is a list of characters which are recognized option letters, optionally followed by colons, specifying that that letter takes an argument, to be placed in `optarg'. If a letter in OPTS is followed by two colons, its argument is optional. This behavior is specific to the GNU `getopt'. The argument `--' causes premature termination of argument scanning, explicitly telling `getopt' that there are no more options. If OPTS begins with `--', then non-option arguments are treated as arguments to the option '\0'. This behavior is specific to the GNU `getopt'. */ #ifdef __GNU_LIBRARY__ /* Many other libraries have conflicting prototypes for getopt, with differences in the consts, in stdlib.h. To avoid compilation errors, only prototype getopt for the GNU C library. */ extern int getopt (int ___argc, char *const *___argv, const char *__shortopts); #else /* not __GNU_LIBRARY__ */ extern int getopt (); #endif /* __GNU_LIBRARY__ */ #ifndef __need_getopt extern int getopt_long (int ___argc, char *const *___argv, const char *__shortopts, const struct option *__longopts, int *__longind); extern int getopt_long_only (int ___argc, char *const *___argv, const char *__shortopts, const struct option *__longopts, int *__longind); /* Internal only. Users should not call this directly. */ extern int _getopt_internal (int ___argc, char *const *___argv, const char *__shortopts, const struct option *__longopts, int *__longind, int __long_only); #endif #ifdef __cplusplus } #endif /* Make sure we later can get all the definitions and declarations. */ #undef __need_getopt #endif /* getopt.h */ flvmeta-1.2.2/src/compat/getopt1.c000066400000000000000000000103731346231570700167670ustar00rootroot00000000000000/* getopt_long and getopt_long_only entry points for GNU getopt. Copyright (C) 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998, 2003 Free Software Foundation, Inc. This file is part of the GNU C Library. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H #include #endif #ifdef _LIBC # include #else # include "getopt.h" #endif #include /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 #include #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ #include #endif #ifndef NULL #define NULL 0 #endif int getopt_long (int argc, char *const *argv, const char *options, const struct option *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } /* Like getopt_long, but '-' as well as '--' can indicate a long option. If an option that starts with '-' (not '--') doesn't match a long option, but does match a short option, it is parsed as a short option instead. */ int getopt_long_only (int argc, char *const *argv, const char *options, const struct option *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 1); } # ifdef _LIBC libc_hidden_def (getopt_long) libc_hidden_def (getopt_long_only) # endif #endif /* Not ELIDE_CODE. */ #ifdef TEST #include int main (int argc, char **argv) { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; int option_index = 0; static struct option long_options[] = { {"add", 1, 0, 0}, {"append", 0, 0, 0}, {"delete", 1, 0, 0}, {"verbose", 0, 0, 0}, {"create", 0, 0, 0}, {"file", 1, 0, 0}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "abc:d:0123456789", long_options, &option_index); if (c == -1) break; switch (c) { case 0: printf ("option %s", long_options[option_index].name); if (optarg) printf (" with arg %s", optarg); printf ("\n"); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case 'd': printf ("option d with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */ flvmeta-1.2.2/src/compat/gettext.h000066400000000000000000000057211346231570700170760ustar00rootroot00000000000000/* Convenience header for conditional use of GNU . Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef _LIBGETTEXT_H #define _LIBGETTEXT_H 1 /* NLS can be disabled through the configure --disable-nls option. */ #if ENABLE_NLS /* Get declarations of GNU message catalog functions. */ # include #else /* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which chokes if dcgettext is defined as a macro. So include it now, to make later inclusions of a NOP. We don't include as well because people using "gettext.h" will not include , and also including would fail on SunOS 4, whereas is OK. */ #if defined(__sun) # include #endif /* Disabled NLS. The casts to 'const char *' serve the purpose of producing warnings for invalid uses of the value returned from these functions. On pre-ANSI systems without 'const', the config.h file is supposed to contain "#define const". */ # define gettext(Msgid) ((const char *) (Msgid)) # define dgettext(Domainname, Msgid) ((const char *) (Msgid)) # define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) # define ngettext(Msgid1, Msgid2, N) \ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) # define dngettext(Domainname, Msgid1, Msgid2, N) \ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) # define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) # define textdomain(Domainname) ((const char *) (Domainname)) # define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) # define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset)) #endif /* A pseudo function call that serves as a marker for the automated extraction of messages, but does not call gettext(). The run-time translation is done at a different place in the code. The argument, String, should be a literal string. Concatenated strings and other string expressions won't work. The macro's expansion is not parenthesized, so that it is suitable as initializer for static 'char[]' or 'const char[]' variables. */ #define gettext_noop(String) String #endif /* _LIBGETTEXT_H */ flvmeta-1.2.2/src/dump.c000066400000000000000000000164641346231570700150750ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "flvmeta.h" #include "dump_json.h" #include "dump_raw.h" #include "dump_xml.h" #include "dump_yaml.h" #include const char * dump_string_get_tag_type(flv_tag * tag) { switch (tag->type) { case FLV_TAG_TYPE_AUDIO: return "audio"; case FLV_TAG_TYPE_VIDEO: return "video"; case FLV_TAG_TYPE_META: return "scriptData"; default: return "Unknown"; } } const char * dump_string_get_video_codec(flv_video_tag tag) { switch (flv_video_tag_codec_id(tag)) { case FLV_VIDEO_TAG_CODEC_JPEG: return "JPEG"; case FLV_VIDEO_TAG_CODEC_SORENSEN_H263: return "Sorenson H.263"; case FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO: return "Screen video"; case FLV_VIDEO_TAG_CODEC_ON2_VP6: return "On2 VP6"; case FLV_VIDEO_TAG_CODEC_ON2_VP6_ALPHA: return "On2 VP6 with alpha channel"; case FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO_V2: return "Screen video version 2"; case FLV_VIDEO_TAG_CODEC_AVC: return "AVC"; default: return "Unknown"; } } const char * dump_string_get_video_frame_type(flv_video_tag tag) { switch (flv_video_tag_frame_type(tag)) { case FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME: if (flv_video_tag_codec_id(tag) == FLV_VIDEO_TAG_CODEC_AVC) { return "seekable frame"; } else { return "keyframe"; } case FLV_VIDEO_TAG_FRAME_TYPE_INTERFRAME: if (flv_video_tag_codec_id(tag) == FLV_VIDEO_TAG_CODEC_AVC) { return "non-seekable frame"; } else { return "inter frame"; } case FLV_VIDEO_TAG_FRAME_TYPE_DISPOSABLE_INTERFRAME: return "disposable inter frame"; case FLV_VIDEO_TAG_FRAME_TYPE_GENERATED_KEYFRAME: return "generated keyframe"; case FLV_VIDEO_TAG_FRAME_TYPE_COMMAND_FRAME: return "video info/command frame"; default: return "Unknown"; } } const char * dump_string_get_avc_packet_type(flv_avc_packet_type type) { switch (type) { case FLV_AVC_PACKET_TYPE_SEQUENCE_HEADER: return "AVC sequence header"; case FLV_AVC_PACKET_TYPE_NALU: return "AVC NALU"; case FLV_AVC_PACKET_TYPE_SEQUENCE_END: return "AVC sequence end"; default: return "Unknown"; } } const char * dump_string_get_sound_type(flv_audio_tag tag) { switch (flv_audio_tag_sound_type(tag)) { case FLV_AUDIO_TAG_SOUND_TYPE_MONO: return "mono"; case FLV_AUDIO_TAG_SOUND_TYPE_STEREO: return "stereo"; default: return "Unknown"; } } const char * dump_string_get_sound_size(flv_audio_tag tag) { switch (flv_audio_tag_sound_size(tag)) { case FLV_AUDIO_TAG_SOUND_SIZE_8: return "8"; case FLV_AUDIO_TAG_SOUND_SIZE_16: return "16"; default: return "Unknown"; } } const char * dump_string_get_sound_rate(flv_audio_tag tag) { switch (flv_audio_tag_sound_rate(tag)) { case FLV_AUDIO_TAG_SOUND_RATE_5_5: return "5.5"; case FLV_AUDIO_TAG_SOUND_RATE_11: return "11"; case FLV_AUDIO_TAG_SOUND_RATE_22: return "22"; case FLV_AUDIO_TAG_SOUND_RATE_44: return "44"; default: return "Unknown"; } } const char * dump_string_get_sound_format(flv_audio_tag tag) { switch (flv_audio_tag_sound_format(tag)) { case FLV_AUDIO_TAG_SOUND_FORMAT_LINEAR_PCM: return "Linear PCM, platform endian"; case FLV_AUDIO_TAG_SOUND_FORMAT_ADPCM: return "ADPCM"; case FLV_AUDIO_TAG_SOUND_FORMAT_MP3: return "MP3"; case FLV_AUDIO_TAG_SOUND_FORMAT_LINEAR_PCM_LE: return "Linear PCM, little-endian"; case FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER_16_MONO: return "Nellymoser 16-kHz mono"; case FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER_8_MONO: return "Nellymoser 8-kHz mono"; case FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER: return "Nellymoser"; case FLV_AUDIO_TAG_SOUND_FORMAT_G711_A: return "G.711 A-law logarithmic PCM"; case FLV_AUDIO_TAG_SOUND_FORMAT_G711_MU: return "G.711 mu-law logarithmic PCM"; case FLV_AUDIO_TAG_SOUND_FORMAT_RESERVED: return "reserved"; case FLV_AUDIO_TAG_SOUND_FORMAT_AAC: return "AAC"; case FLV_AUDIO_TAG_SOUND_FORMAT_SPEEX: return "Speex"; case FLV_AUDIO_TAG_SOUND_FORMAT_MP3_8: return "MP3 8-Khz"; case FLV_AUDIO_TAG_SOUND_FORMAT_DEVICE_SPECIFIC: return "Device-specific sound"; default: return "Unknown"; } } const char * dump_string_get_aac_packet_type(flv_aac_packet_type type) { switch (type) { case FLV_AAC_PACKET_TYPE_SEQUENCE_HEADER: return "AAC sequence header"; case FLV_AAC_PACKET_TYPE_RAW: return "AAC raw"; default: return "Unknown"; } } /* dump metadata from a FLV file */ int dump_metadata(const flvmeta_opts * options) { int retval; flv_parser parser; memset(&parser, 0, sizeof(flv_parser)); parser.user_data = (void*)options; switch (options->dump_format) { case FLVMETA_FORMAT_JSON: dump_json_setup_metadata_dump(&parser); break; case FLVMETA_FORMAT_RAW: dump_raw_setup_metadata_dump(&parser); break; case FLVMETA_FORMAT_XML: dump_xml_setup_metadata_dump(&parser); break; case FLVMETA_FORMAT_YAML: dump_yaml_setup_metadata_dump(&parser); break; } retval = flv_parse(options->input_file, &parser); if (retval == FLVMETA_DUMP_STOP_OK) { retval = FLV_OK; } return retval; } /* dump the full contents of an FLV file */ int dump_flv_file(const flvmeta_opts * options) { flv_parser parser; memset(&parser, 0, sizeof(flv_parser)); switch (options->dump_format) { case FLVMETA_FORMAT_JSON: return dump_json_file(&parser, options); case FLVMETA_FORMAT_RAW: return dump_raw_file(&parser, options); case FLVMETA_FORMAT_XML: return dump_xml_file(&parser, options); case FLVMETA_FORMAT_YAML: return dump_yaml_file(&parser, options); default: return OK; } } /* dump AMF data directly */ int dump_amf_data(const amf_data * data, const flvmeta_opts * options) { switch (options->dump_format) { case FLVMETA_FORMAT_JSON: return dump_json_amf_data(data); case FLVMETA_FORMAT_RAW: return dump_raw_amf_data(data); case FLVMETA_FORMAT_XML: return dump_xml_amf_data(data); case FLVMETA_FORMAT_YAML: return dump_yaml_amf_data(data); default: return OK; } } flvmeta-1.2.2/src/dump.h000066400000000000000000000035751346231570700151010ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __DUMP_H__ #define __DUMP_H__ #include "flvmeta.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* common FLV strings */ const char * dump_string_get_tag_type(flv_tag * tag); const char * dump_string_get_video_codec(flv_video_tag tag); const char * dump_string_get_video_frame_type(flv_video_tag tag); const char * dump_string_get_avc_packet_type(flv_avc_packet_type type); const char * dump_string_get_sound_type(flv_audio_tag tag); const char * dump_string_get_sound_size(flv_audio_tag tag); const char * dump_string_get_sound_rate(flv_audio_tag tag); const char * dump_string_get_sound_format(flv_audio_tag tag); const char * dump_string_get_aac_packet_type(flv_aac_packet_type type); /* dump metadata from a FLV file */ int dump_metadata(const flvmeta_opts * options); /* dump the full contents of an FLV file */ int dump_flv_file(const flvmeta_opts * options); /* dump AMF data directly */ int dump_amf_data(const amf_data * data, const flvmeta_opts * options); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __DUMP_H__ */ flvmeta-1.2.2/src/dump_json.c000066400000000000000000000232451346231570700161210ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "dump.h" #include "dump_json.h" #include "json.h" #include #include /* JSON metadata dumping */ static void json_amf_data_dump(const amf_data * data, json_emitter * je) { if (data != NULL) { amf_node * node; char str[128]; switch (data->type) { case AMF_TYPE_NUMBER: json_emit_number(je, data->number_data); break; case AMF_TYPE_BOOLEAN: json_emit_boolean(je, data->boolean_data); break; case AMF_TYPE_STRING: json_emit_string(je, (char *)amf_string_get_bytes(data), amf_string_get_size(data)); break; case AMF_TYPE_OBJECT: json_emit_object_start(je); node = amf_object_first(data); while (node != NULL) { json_emit_object_key(je, (char *)amf_string_get_bytes(amf_object_get_name(node)), amf_string_get_size(amf_object_get_name(node)) ); json_amf_data_dump(amf_object_get_data(node), je); node = amf_object_next(node); } json_emit_object_end(je); break; case AMF_TYPE_NULL: case AMF_TYPE_UNDEFINED: json_emit_null(je); break; case AMF_TYPE_ASSOCIATIVE_ARRAY: json_emit_object_start(je); node = amf_associative_array_first(data); while (node != NULL) { json_emit_object_key(je, (char *)amf_string_get_bytes(amf_associative_array_get_name(node)), amf_string_get_size(amf_associative_array_get_name(node)) ); json_amf_data_dump(amf_object_get_data(node), je); node = amf_associative_array_next(node); } json_emit_object_end(je); break; case AMF_TYPE_ARRAY: json_emit_array_start(je); node = amf_array_first(data); while (node != NULL) { json_amf_data_dump(amf_array_get(node), je); node = amf_array_next(node); } json_emit_array_end(je); break; case AMF_TYPE_DATE: amf_date_to_iso8601(data, str, sizeof(str)); json_emit_string(je, str, strlen(str)); break; case AMF_TYPE_XML: break; case AMF_TYPE_CLASS: break; default: break; } } } /* JSON FLV file full dump callbacks */ static int json_on_header(flv_header * header, flv_parser * parser) { json_emitter * je; je = (json_emitter*)parser->user_data; json_emit_object_start(je); json_emit_object_key_z(je, "magic"); json_emit_string(je, (char*)header->signature, 3); json_emit_object_key_z(je, "hasVideo"); json_emit_boolean(je, flv_header_has_video(*header)); json_emit_object_key_z(je, "hasAudio"); json_emit_boolean(je, flv_header_has_audio(*header)); json_emit_object_key_z(je, "version"); json_emit_integer(je, header->version); json_emit_object_key_z(je, "tags"); json_emit_array_start(je); return OK; } static int json_on_tag(flv_tag * tag, flv_parser * parser) { json_emitter * je; je = (json_emitter*)parser->user_data; json_emit_object_start(je); json_emit_object_key_z(je, "type"); json_emit_string_z(je, dump_string_get_tag_type(tag)); json_emit_object_key_z(je, "timestamp"); json_emit_integer(je, flv_tag_get_timestamp(*tag)); json_emit_object_key_z(je, "dataSize"); json_emit_integer(je, flv_tag_get_body_length(*tag)); json_emit_object_key_z(je, "offset"); json_emit_file_offset(je, parser->stream->current_tag_offset); return OK; } static int json_on_video_tag(flv_tag * tag, flv_video_tag vt, flv_parser * parser) { json_emitter * je; je = (json_emitter*)parser->user_data; json_emit_object_key_z(je, "videoData"); json_emit_object_start(je); json_emit_object_key_z(je, "codecID"); json_emit_string_z(je, dump_string_get_video_codec(vt)); json_emit_object_key_z(je, "frameType"); json_emit_string_z(je, dump_string_get_video_frame_type(vt)); /* if AVC, detect frame type and composition time */ if (flv_video_tag_codec_id(vt) == FLV_VIDEO_TAG_CODEC_AVC) { flv_avc_packet_type type; /* packet type */ if (flv_read_tag_body(parser->stream, &type, sizeof(flv_avc_packet_type)) < sizeof(flv_avc_packet_type)) { return ERROR_INVALID_TAG; } json_emit_object_key_z(je, "AVCData"); json_emit_object_start(je); json_emit_object_key_z(je, "packetType"); json_emit_string_z(je, dump_string_get_avc_packet_type(type)); /* composition time */ if (type == FLV_AVC_PACKET_TYPE_NALU) { uint24_be composition_time; if (flv_read_tag_body(parser->stream, &composition_time, sizeof(uint24_be)) < sizeof(uint24_be)) { return ERROR_INVALID_TAG; } json_emit_object_key_z(je, "compositionTimeOffset"); json_emit_integer(je, uint24_be_to_uint32(composition_time)); } json_emit_object_end(je); } json_emit_object_end(je); return OK; } static int json_on_audio_tag(flv_tag * tag, flv_audio_tag at, flv_parser * parser) { json_emitter * je; je = (json_emitter*)parser->user_data; json_emit_object_key_z(je, "audioData"); json_emit_object_start(je); json_emit_object_key_z(je, "type"); json_emit_string_z(je, dump_string_get_sound_type(at)); json_emit_object_key_z(je, "size"); json_emit_string_z(je, dump_string_get_sound_size(at)); json_emit_object_key_z(je, "rate"); json_emit_string_z(je, dump_string_get_sound_rate(at)); json_emit_object_key_z(je, "format"); json_emit_string_z(je, dump_string_get_sound_format(at)); /* if AAC, detect packet type */ if (flv_audio_tag_sound_format(at) == FLV_AUDIO_TAG_SOUND_FORMAT_AAC) { flv_aac_packet_type type; /* packet type */ if (flv_read_tag_body(parser->stream, &type, sizeof(flv_aac_packet_type)) < sizeof(flv_aac_packet_type)) { return ERROR_INVALID_TAG; } json_emit_object_key_z(je, "AACData"); json_emit_object_start(je); json_emit_object_key_z(je, "packetType"); json_emit_string_z(je, dump_string_get_aac_packet_type(type)); json_emit_object_end(je); } json_emit_object_end(je); return OK; } static int json_on_metadata_tag(flv_tag * tag, char * name, amf_data * data, flv_parser * parser) { json_emitter * je; je = (json_emitter*)parser->user_data; json_emit_object_key_z(je, "scriptDataObject"); json_emit_object_start(je); json_emit_object_key_z(je, "name"); json_emit_string_z(je, name); json_emit_object_key_z(je, "metadata"); json_amf_data_dump(data, je); json_emit_object_end(je); return OK; } static int json_on_prev_tag_size(uint32 size, flv_parser * parser) { json_emitter * je; je = (json_emitter*)parser->user_data; json_emit_object_end(je); return OK; } static int json_on_stream_end(flv_parser * parser) { json_emitter * je; je = (json_emitter*)parser->user_data; json_emit_array_end(je); json_emit_object_end(je); return OK; } /* JSON FLV file metadata dump callback */ static int json_on_metadata_tag_only(flv_tag * tag, char * name, amf_data * data, flv_parser * parser) { flvmeta_opts * options = (flvmeta_opts*) parser->user_data; if (options->metadata_event == NULL) { if (!strcmp(name, "onMetaData")) { dump_json_amf_data(data); return FLVMETA_DUMP_STOP_OK; } } else { if (!strcmp(name, options->metadata_event)) { dump_json_amf_data(data); } } return OK; } /* setup dumping */ void dump_json_setup_metadata_dump(flv_parser * parser) { if (parser != NULL) { parser->on_metadata_tag = json_on_metadata_tag_only; } } int dump_json_file(flv_parser * parser, const flvmeta_opts * options) { json_emitter je; parser->on_header = json_on_header; parser->on_tag = json_on_tag; parser->on_audio_tag = json_on_audio_tag; parser->on_video_tag = json_on_video_tag; parser->on_metadata_tag = json_on_metadata_tag; parser->on_prev_tag_size = json_on_prev_tag_size; parser->on_stream_end = json_on_stream_end; json_emit_init(&je); parser->user_data = &je; return flv_parse(options->input_file, parser); } int dump_json_amf_data(const amf_data * data) { json_emitter je; json_emit_init(&je); /* dump AMF into JSON */ json_amf_data_dump(data, &je); printf("\n"); return OK; } flvmeta-1.2.2/src/dump_json.h000066400000000000000000000023561346231570700161260ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __DUMP_JSON_H__ #define __DUMP_JSON_H__ #include "flvmeta.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* JSON dumping functions */ void dump_json_setup_metadata_dump(flv_parser * parser); int dump_json_file(flv_parser * parser, const flvmeta_opts * options); int dump_json_amf_data(const amf_data * data); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __DUMP_JSON_H__ */ flvmeta-1.2.2/src/dump_raw.c000066400000000000000000000131761346231570700157430ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "dump.h" #include "dump_raw.h" #include #include /* raw FLV file full dump callbacks */ static int raw_on_header(flv_header * header, flv_parser * parser) { int * n; n = (int*) malloc(sizeof(uint32)); if (n == NULL) { return ERROR_MEMORY; } parser->user_data = n; *n = 0; printf("Magic: %.3s\n", header->signature); printf("Version: %" PRI_BYTE "u\n", header->version); printf("Has audio: %s\n", flv_header_has_audio(*header) ? "yes" : "no"); printf("Has video: %s\n", flv_header_has_video(*header) ? "yes" : "no"); printf("Offset: %u\n", swap_uint32(header->offset)); return OK; } static int raw_on_tag(flv_tag * tag, flv_parser * parser) { int * n; /* increment current tag number */ n = ((int*)parser->user_data); ++(*n); printf("--- Tag #%u at 0x%" FILE_OFFSET_PRINTF_FORMAT "X", *n, FILE_OFFSET_PRINTF_TYPE(parser->stream->current_tag_offset)); printf(" (%" FILE_OFFSET_PRINTF_FORMAT "u) ---\n", FILE_OFFSET_PRINTF_TYPE(parser->stream->current_tag_offset)); printf("Tag type: %s\n", dump_string_get_tag_type(tag)); printf("Body length: %u\n", flv_tag_get_body_length(*tag)); printf("Timestamp: %u\n", flv_tag_get_timestamp(*tag)); return OK; } static int raw_on_video_tag(flv_tag * tag, flv_video_tag vt, flv_parser * parser) { printf("* Video codec: %s\n", dump_string_get_video_codec(vt)); printf("* Video frame type: %s\n", dump_string_get_video_frame_type(vt)); /* if AVC, detect frame type and composition time */ if (flv_video_tag_codec_id(vt) == FLV_VIDEO_TAG_CODEC_AVC) { flv_avc_packet_type type; /* packet type */ if (flv_read_tag_body(parser->stream, &type, sizeof(flv_avc_packet_type)) < sizeof(flv_avc_packet_type)) { return ERROR_INVALID_TAG; } printf("* AVC packet type: %s\n", dump_string_get_avc_packet_type(type)); /* composition time */ if (type == FLV_AVC_PACKET_TYPE_NALU) { uint24_be composition_time; if (flv_read_tag_body(parser->stream, &composition_time, sizeof(uint24_be)) < sizeof(uint24_be)) { return ERROR_INVALID_TAG; } printf("* Composition time offset: %i\n", uint24_be_to_uint32(composition_time)); } } return OK; } static int raw_on_audio_tag(flv_tag * tag, flv_audio_tag at, flv_parser * parser) { printf("* Sound type: %s\n", dump_string_get_sound_type(at)); printf("* Sound size: %s\n", dump_string_get_sound_size(at)); printf("* Sound rate: %s\n", dump_string_get_sound_rate(at)); printf("* Sound format: %s\n", dump_string_get_sound_format(at)); /* if AAC, detect packet type */ if (flv_audio_tag_sound_format(at) == FLV_AUDIO_TAG_SOUND_FORMAT_AAC) { flv_aac_packet_type type; /* packet type */ if (flv_read_tag_body(parser->stream, &type, sizeof(flv_aac_packet_type)) < sizeof(flv_aac_packet_type)) { return ERROR_INVALID_TAG; } printf("* AAC packet type: %s\n", dump_string_get_aac_packet_type(type)); } return OK; } static int raw_on_metadata_tag(flv_tag * tag, char * name, amf_data * data, flv_parser * parser) { printf("* Metadata event name: %s\n", name); printf("* Metadata contents: "); amf_data_dump(stdout, data, 0); printf("\n"); return OK; } static int raw_on_prev_tag_size(uint32 size, flv_parser * parser) { printf("Previous tag size: %u\n", size); return OK; } static int raw_on_stream_end(flv_parser * parser) { free(parser->user_data); return OK; } /* raw FLV file metadata dump callback */ static int raw_on_metadata_tag_only(flv_tag * tag, char * name, amf_data * data, flv_parser * parser) { flvmeta_opts * options = (flvmeta_opts*) parser->user_data; if (options->metadata_event == NULL) { if (!strcmp(name, "onMetaData")) { dump_raw_amf_data(data); return FLVMETA_DUMP_STOP_OK; } } else { if (!strcmp(name, options->metadata_event)) { dump_raw_amf_data(data); } } return OK; } /* setup dumping */ void dump_raw_setup_metadata_dump(flv_parser * parser) { if (parser != NULL) { parser->on_metadata_tag = raw_on_metadata_tag_only; } } int dump_raw_file(flv_parser * parser, const flvmeta_opts * options) { parser->on_header = raw_on_header; parser->on_tag = raw_on_tag; parser->on_audio_tag = raw_on_audio_tag; parser->on_video_tag = raw_on_video_tag; parser->on_metadata_tag = raw_on_metadata_tag; parser->on_prev_tag_size = raw_on_prev_tag_size; parser->on_stream_end = raw_on_stream_end; return flv_parse(options->input_file, parser); } int dump_raw_amf_data(const amf_data * data) { amf_data_dump(stdout, data, 0); printf("\n"); return OK; } flvmeta-1.2.2/src/dump_raw.h000066400000000000000000000023471346231570700157460ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __DUMP_RAW_H__ #define __DUMP_RAW_H__ #include "flvmeta.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* raw dumping functions */ void dump_raw_setup_metadata_dump(flv_parser * parser); int dump_raw_file(flv_parser * parser, const flvmeta_opts * options); int dump_raw_amf_data(const amf_data * data); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __DUMP_RAW_H__ */ flvmeta-1.2.2/src/dump_xml.c000066400000000000000000000262141346231570700157470ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "dump.h" #include "dump_xml.h" #include #include /* does the given string have XML tag markers ? */ static int has_xml_markers(const char * str, int len) { int i; for (i = 0; i < len; i++) { if (str[i] == '<' || str[i] == '>') { return 1; } } return 0; } /* XML metadata dumping */ static void xml_amf_data_dump(const amf_data * data, int qualified, int indent_level) { if (data != NULL) { amf_node * node; char datestr[128]; int markers; char * ns; char ns_decl[50]; /* namespace to use whether we're using qualified mode */ ns = (qualified == 1) ? "amf:" : ""; /* if indent_level is zero, that means we're at the root of the xml document therefore we need to insert the namespace definition */ if (indent_level == 0) { sprintf(ns_decl, " xmlns%s=\"http://schemas.flvmeta.org/AMF0/1.0/\"", ns); } else { strcpy(ns_decl, ""); } /* print indentation spaces */ printf("%*s", indent_level * 2, ""); switch (data->type) { case AMF_TYPE_NUMBER: printf("<%snumber%s value=\"%.12g\"/>\n", ns, ns_decl, data->number_data); break; case AMF_TYPE_BOOLEAN: printf("<%sboolean%s value=\"%s\"/>\n", ns, ns_decl, (data->boolean_data) ? "true" : "false"); break; case AMF_TYPE_STRING: if (amf_string_get_size(data) > 0) { printf("<%sstring%s>", ns, ns_decl); /* check whether the string contains xml characters, if so, CDATA it */ markers = has_xml_markers((char*)amf_string_get_bytes(data), amf_string_get_size(data)); if (markers) { printf(""); } printf("\n", ns); } else { /* simplify empty xml element into a more compact form */ printf("<%sstring%s/>\n", ns, ns_decl); } break; case AMF_TYPE_OBJECT: if (amf_object_size(data) > 0) { printf("<%sobject%s>\n", ns, ns_decl); node = amf_object_first(data); while (node != NULL) { printf("%*s<%sentry name=\"%s\">\n", (indent_level + 1) * 2, "", ns, amf_string_get_bytes(amf_object_get_name(node))); xml_amf_data_dump(amf_object_get_data(node), qualified, indent_level + 2); node = amf_object_next(node); printf("%*s\n", (indent_level + 1) * 2, "", ns); } printf("%*s\n", indent_level * 2, "", ns); } else { /* simplify empty xml element into a more compact form */ printf("<%sobject%s/>\n", ns, ns_decl); } break; case AMF_TYPE_NULL: printf("<%snull%s/>\n", ns, ns_decl); break; case AMF_TYPE_UNDEFINED: printf("<%sundefined%s/>\n", ns, ns_decl); break; case AMF_TYPE_ASSOCIATIVE_ARRAY: if (amf_associative_array_size(data) > 0) { printf("<%sassociativeArray%s>\n", ns, ns_decl); node = amf_associative_array_first(data); while (node != NULL) { printf("%*s<%sentry name=\"%s\">\n", (indent_level + 1) * 2, "", ns, amf_string_get_bytes(amf_associative_array_get_name(node))); xml_amf_data_dump(amf_associative_array_get_data(node), qualified, indent_level + 2); node = amf_associative_array_next(node); printf("%*s\n", (indent_level + 1) * 2, "", ns); } printf("%*s\n", indent_level * 2, "", ns); } else { /* simplify empty xml element into a more compact form */ printf("<%sassociativeArray%s/>\n", ns, ns_decl); } break; case AMF_TYPE_ARRAY: if (amf_array_size(data) > 0) { printf("<%sarray%s>\n", ns, ns_decl); node = amf_array_first(data); while (node != NULL) { xml_amf_data_dump(amf_array_get(node), qualified, indent_level + 1); node = amf_array_next(node); } printf("%*s\n", indent_level * 2, "", ns); } else { /* simplify empty xml element into a more compact form */ printf("<%sarray%s/>\n", ns, ns_decl); } break; case AMF_TYPE_DATE: amf_date_to_iso8601(data, datestr, sizeof(datestr)); printf("<%sdate%s value=\"%s\"/>\n", ns, ns_decl, datestr); break; case AMF_TYPE_XML: break; case AMF_TYPE_CLASS: break; default: break; } } } /* XML FLV file full dump callbacks */ static int xml_on_header(flv_header * header, flv_parser * parser) { puts(""); printf("\n", flv_header_has_video(*header) ? "true" : "false", flv_header_has_audio(*header) ? "true" : "false", header->version); return OK; } static int xml_on_tag(flv_tag * tag, flv_parser * parser) { printf(" \n", FILE_OFFSET_PRINTF_TYPE(parser->stream->current_tag_offset)); return OK; } static int xml_on_video_tag(flv_tag * tag, flv_video_tag vt, flv_parser * parser) { printf(" \n"); /* packet type */ if (flv_read_tag_body(parser->stream, &type, sizeof(flv_avc_packet_type)) < sizeof(flv_avc_packet_type)) { return ERROR_INVALID_TAG; } printf(" stream, &composition_time, sizeof(uint24_be)) < sizeof(uint24_be)) { return ERROR_INVALID_TAG; } printf(" compositionTimeOffset=\"%i\"", uint24_be_to_uint32(composition_time)); } printf("/>\n"); printf(" \n"); } else { printf("/>\n"); } return OK; } static int xml_on_audio_tag(flv_tag * tag, flv_audio_tag at, flv_parser * parser) { printf(" \n"); /* packet type */ if (flv_read_tag_body(parser->stream, &type, sizeof(flv_aac_packet_type)) < sizeof(flv_aac_packet_type)) { return ERROR_INVALID_TAG; } printf(" \n", dump_string_get_aac_packet_type(type)); printf(" \n"); } else { printf("/>\n"); } return OK; } static int xml_on_metadata_tag(flv_tag * tag, char * name, amf_data * data, flv_parser * parser) { printf(" \n", name); /* dump AMF data as XML, we start from level 3, meaning 6 indentations characters */ xml_amf_data_dump(data, 1, 3); puts(" "); return OK; } static int xml_on_prev_tag_size(uint32 size, flv_parser * parser) { puts(" "); return OK; } static int xml_on_stream_end(flv_parser * parser) { puts(""); return OK; } /* XML FLV file metadata dump callbacks */ static int xml_on_metadata_tag_only(flv_tag * tag, char * name, amf_data * data, flv_parser * parser) { flvmeta_opts * options = (flvmeta_opts*) parser->user_data; if (options->metadata_event == NULL) { if (!strcmp(name, "onMetaData")) { dump_xml_amf_data(data); return FLVMETA_DUMP_STOP_OK; } } else { if (!strcmp(name, options->metadata_event)) { dump_xml_amf_data(data); } } return OK; } /* dumping functions */ void dump_xml_setup_metadata_dump(flv_parser * parser) { if (parser != NULL) { parser->on_metadata_tag = xml_on_metadata_tag_only; } } int dump_xml_file(flv_parser * parser, const flvmeta_opts * options) { parser->on_header = xml_on_header; parser->on_tag = xml_on_tag; parser->on_audio_tag = xml_on_audio_tag; parser->on_video_tag = xml_on_video_tag; parser->on_metadata_tag = xml_on_metadata_tag; parser->on_prev_tag_size = xml_on_prev_tag_size; parser->on_stream_end = xml_on_stream_end; return flv_parse(options->input_file, parser); } int dump_xml_amf_data(const amf_data * data) { puts(""); xml_amf_data_dump(data, 0, 0); return OK; } flvmeta-1.2.2/src/dump_xml.h000066400000000000000000000023471346231570700157550ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __DUMP_XML_H__ #define __DUMP_XML_H__ #include "flvmeta.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* XML dumping functions */ void dump_xml_setup_metadata_dump(flv_parser * parser); int dump_xml_file(flv_parser * parser, const flvmeta_opts * options); int dump_xml_amf_data(const amf_data * data); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __DUMP_XML_H__ */ flvmeta-1.2.2/src/dump_yaml.c000066400000000000000000000455221346231570700161140ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "dump.h" #include "dump_yaml.h" #include "yaml.h" #include #include /* YAML metadata dumping */ static void amf_data_yaml_dump(const amf_data * data, yaml_emitter_t * emitter) { if (data != NULL) { amf_node * node; yaml_event_t event; char str[128]; switch (data->type) { case AMF_TYPE_NUMBER: sprintf(str, "%.12g", data->number_data); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); break; case AMF_TYPE_BOOLEAN: sprintf(str, (data->boolean_data) ? "true" : "false"); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); break; case AMF_TYPE_STRING: yaml_scalar_event_initialize(&event, NULL, NULL, amf_string_get_bytes(data), (int)amf_string_get_size(data), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); break; case AMF_TYPE_OBJECT: yaml_mapping_start_event_initialize(&event, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE); yaml_emitter_emit(emitter, &event); node = amf_object_first(data); while (node != NULL) { amf_data * name; name = amf_object_get_name(node); /* if this fails, we skip the current entry */ if (yaml_scalar_event_initialize(&event, NULL, NULL, amf_string_get_bytes(name), amf_string_get_size(name), 1, 1, YAML_ANY_SCALAR_STYLE)) { yaml_emitter_emit(emitter, &event); amf_data_yaml_dump(amf_object_get_data(node), emitter); } node = amf_object_next(node); } yaml_mapping_end_event_initialize(&event); yaml_emitter_emit(emitter, &event); break; case AMF_TYPE_NULL: case AMF_TYPE_UNDEFINED: yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"null", 4, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); break; case AMF_TYPE_ASSOCIATIVE_ARRAY: yaml_mapping_start_event_initialize(&event, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE); yaml_emitter_emit(emitter, &event); node = amf_associative_array_first(data); while (node != NULL) { amf_data * name; name = amf_associative_array_get_name(node); /* if this fails, we skip the current entry */ if (yaml_scalar_event_initialize(&event, NULL, NULL, amf_string_get_bytes(name), amf_string_get_size(name), 1, 1, YAML_ANY_SCALAR_STYLE)) { yaml_emitter_emit(emitter, &event); amf_data_yaml_dump(amf_associative_array_get_data(node), emitter); } node = amf_associative_array_next(node); } yaml_mapping_end_event_initialize(&event); yaml_emitter_emit(emitter, &event); break; case AMF_TYPE_ARRAY: yaml_sequence_start_event_initialize(&event, NULL, NULL, 1, YAML_ANY_SEQUENCE_STYLE); yaml_emitter_emit(emitter, &event); node = amf_array_first(data); while (node != NULL) { amf_data_yaml_dump(amf_array_get(node), emitter); node = amf_array_next(node); } yaml_sequence_end_event_initialize(&event); yaml_emitter_emit(emitter, &event); break; case AMF_TYPE_DATE: amf_date_to_iso8601(data, str, sizeof(str)); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); break; case AMF_TYPE_XML: break; case AMF_TYPE_CLASS: break; default: break; } } } /* YAML FLV file full dump callbacks */ static int yaml_on_header(flv_header * header, flv_parser * parser) { yaml_emitter_t * emitter; yaml_event_t event; char buffer[20]; emitter = (yaml_emitter_t *)parser->user_data; yaml_mapping_start_event_initialize(&event, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE); yaml_emitter_emit(emitter, &event); /* magic */ yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"magic", 5, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); sprintf(buffer, "%.3s", header->signature); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)buffer, (int)strlen(buffer), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); /* hasVideo */ yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"hasVideo", 8, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); sprintf(buffer, "%s", flv_header_has_video(*header) ? "true" : "false"); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)buffer, (int)strlen(buffer), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); /* hasAudio */ yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"hasAudio", 8, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); sprintf(buffer, "%s", flv_header_has_audio(*header) ? "true" : "false"); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)buffer, (int)strlen(buffer), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); /* version */ yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"version", 7, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); sprintf(buffer, "%i", header->version); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)buffer, (int)strlen(buffer), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); /* start of tags array */ yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"tags", 4, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_sequence_start_event_initialize(&event, NULL, NULL, 1, YAML_ANY_SEQUENCE_STYLE); yaml_emitter_emit(emitter, &event); return OK; } static int yaml_on_tag(flv_tag * tag, flv_parser * parser) { const char * str; yaml_emitter_t * emitter; yaml_event_t event; char buffer[20]; emitter = (yaml_emitter_t *)parser->user_data; str = dump_string_get_tag_type(tag); yaml_mapping_start_event_initialize(&event, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE); yaml_emitter_emit(emitter, &event); /* type */ yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"type", 4, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); /* timestamp */ yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"timestamp", 9, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); sprintf(buffer, "%i", flv_tag_get_timestamp(*tag)); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)buffer, (int)strlen(buffer), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); /* data size */ yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"dataSize", 8, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); sprintf(buffer, "%i", flv_tag_get_body_length(*tag)); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)buffer, (int)strlen(buffer), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); /* offset */ yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"offset", 6, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); sprintf(buffer, "%" FILE_OFFSET_PRINTF_FORMAT "u", FILE_OFFSET_PRINTF_TYPE(parser->stream->current_tag_offset)); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)buffer, (int)strlen(buffer), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); return OK; } static int yaml_on_video_tag(flv_tag * tag, flv_video_tag vt, flv_parser * parser) { const char * str; yaml_emitter_t * emitter; yaml_event_t event; char buffer[20]; emitter = (yaml_emitter_t *)parser->user_data; yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"videoData", 9, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_mapping_start_event_initialize(&event, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE); yaml_emitter_emit(emitter, &event); str = dump_string_get_video_codec(vt); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"codecID", 7, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); str = dump_string_get_video_frame_type(vt); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"frameType", 9, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); /* if AVC, detect frame type and composition time */ if (flv_video_tag_codec_id(vt) == FLV_VIDEO_TAG_CODEC_AVC) { flv_avc_packet_type type; /* packet type */ if (flv_read_tag_body(parser->stream, &type, sizeof(flv_avc_packet_type)) < sizeof(flv_avc_packet_type)) { return ERROR_INVALID_TAG; } yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"AVCData", 7, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_mapping_start_event_initialize(&event, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE); yaml_emitter_emit(emitter, &event); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"packetType", 10, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); str = dump_string_get_avc_packet_type(type); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); /* composition time */ if (type == FLV_AVC_PACKET_TYPE_NALU) { uint24_be composition_time; if (flv_read_tag_body(parser->stream, &composition_time, sizeof(uint24_be)) < sizeof(uint24_be)) { return ERROR_INVALID_TAG; } yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"compositionTimeOffset", 21, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); sprintf(buffer, "%i", uint24_be_to_uint32(composition_time)); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)buffer, (int)strlen(buffer), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); } yaml_mapping_end_event_initialize(&event); yaml_emitter_emit(emitter, &event); } yaml_mapping_end_event_initialize(&event); yaml_emitter_emit(emitter, &event); return OK; } static int yaml_on_audio_tag(flv_tag * tag, flv_audio_tag at, flv_parser * parser) { const char * str; yaml_emitter_t * emitter; yaml_event_t event; emitter = (yaml_emitter_t *)parser->user_data; yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"audioData", 9, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_mapping_start_event_initialize(&event, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE); yaml_emitter_emit(emitter, &event); str = dump_string_get_sound_type(at); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"type", 4, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); str = dump_string_get_sound_size(at); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"size", 4, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); str = dump_string_get_sound_rate(at); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"rate", 4, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); str = dump_string_get_sound_format(at); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"format", 6, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); /* if AAC, detect packet type */ if (flv_audio_tag_sound_format(at) == FLV_AUDIO_TAG_SOUND_FORMAT_AAC) { flv_aac_packet_type type; /* packet type */ if (flv_read_tag_body(parser->stream, &type, sizeof(flv_aac_packet_type)) < sizeof(flv_aac_packet_type)) { return ERROR_INVALID_TAG; } yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"AACData", 7, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_mapping_start_event_initialize(&event, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE); yaml_emitter_emit(emitter, &event); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"packetType", 10, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); str = dump_string_get_aac_packet_type(type); yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)str, (int)strlen(str), 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); yaml_mapping_end_event_initialize(&event); yaml_emitter_emit(emitter, &event); } yaml_mapping_end_event_initialize(&event); yaml_emitter_emit(emitter, &event); return OK; } static int yaml_on_metadata_tag(flv_tag * tag, char * name, amf_data * data, flv_parser * parser) { yaml_emitter_t * emitter; yaml_event_t event; emitter = (yaml_emitter_t *)parser->user_data; yaml_scalar_event_initialize(&event, NULL, NULL, (yaml_char_t*)"scriptDataObject", 16, 1, 1, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &event); amf_data_yaml_dump(data, emitter); return OK; } static int yaml_on_prev_tag_size(uint32 size, flv_parser * parser) { yaml_emitter_t * emitter; yaml_event_t event; emitter = (yaml_emitter_t *)parser->user_data; yaml_mapping_end_event_initialize(&event); yaml_emitter_emit(emitter, &event); return OK; } static int yaml_on_stream_end(flv_parser * parser) { yaml_emitter_t * emitter; yaml_event_t event; emitter = (yaml_emitter_t *)parser->user_data; yaml_sequence_end_event_initialize(&event); yaml_emitter_emit(emitter, &event); yaml_mapping_end_event_initialize(&event); yaml_emitter_emit(emitter, &event); return OK; } /* YAML FLV file metadata dump callbacks */ static int yaml_on_metadata_tag_only(flv_tag * tag, char * name, amf_data * data, flv_parser * parser) { flvmeta_opts * options = (flvmeta_opts*) parser->user_data; if (options->metadata_event == NULL) { if (!strcmp(name, "onMetaData")) { dump_yaml_amf_data(data); return FLVMETA_DUMP_STOP_OK; } } else { if (!strcmp(name, options->metadata_event)) { dump_yaml_amf_data(data); } } return OK; } /* dumping functions */ void dump_yaml_setup_metadata_dump(flv_parser * parser) { if (parser != NULL) { parser->on_metadata_tag = yaml_on_metadata_tag_only; } } int dump_yaml_file(flv_parser * parser, const flvmeta_opts * options) { yaml_emitter_t emitter; yaml_event_t event; int ret; parser->on_header = yaml_on_header; parser->on_tag = yaml_on_tag; parser->on_audio_tag = yaml_on_audio_tag; parser->on_video_tag = yaml_on_video_tag; parser->on_metadata_tag = yaml_on_metadata_tag; parser->on_prev_tag_size = yaml_on_prev_tag_size; parser->on_stream_end = yaml_on_stream_end; yaml_emitter_initialize(&emitter); yaml_emitter_set_output_file(&emitter, stdout); yaml_emitter_open(&emitter); yaml_document_start_event_initialize(&event, NULL, NULL, NULL, 0); yaml_emitter_emit(&emitter, &event); parser->user_data = &emitter; ret = flv_parse(options->input_file, parser); yaml_document_end_event_initialize(&event, 1); yaml_emitter_emit(&emitter, &event); yaml_emitter_flush(&emitter); yaml_emitter_close(&emitter); yaml_emitter_delete(&emitter); return ret; } int dump_yaml_amf_data(const amf_data * data) { yaml_emitter_t emitter; yaml_event_t event; yaml_emitter_initialize(&emitter); yaml_emitter_set_output_file(&emitter, stdout); yaml_emitter_open(&emitter); yaml_document_start_event_initialize(&event, NULL, NULL, NULL, 0); yaml_emitter_emit(&emitter, &event); /* dump AMF into YAML */ amf_data_yaml_dump(data, &emitter); yaml_document_end_event_initialize(&event, 1); yaml_emitter_emit(&emitter, &event); yaml_emitter_flush(&emitter); yaml_emitter_close(&emitter); yaml_emitter_delete(&emitter); return OK; } flvmeta-1.2.2/src/dump_yaml.h000066400000000000000000000023561346231570700161170ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __DUMP_YAML_H__ #define __DUMP_YAML_H__ #include "flvmeta.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* YAML dumping functions */ void dump_yaml_setup_metadata_dump(flv_parser * parser); int dump_yaml_file(flv_parser * parser, const flvmeta_opts * options); int dump_yaml_amf_data(const amf_data * data); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __DUMP_YAML_H__ */ flvmeta-1.2.2/src/flv.c000066400000000000000000000420471346231570700147130ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "flv.h" #include void flv_tag_set_timestamp(flv_tag * tag, uint32 timestamp) { tag->timestamp = uint32_to_uint24_be(timestamp); tag->timestamp_extended = (uint8)((timestamp & 0xFF000000) >> 24); } /* FLV stream functions */ flv_stream * flv_open(const char * file) { flv_stream * stream = (flv_stream *) malloc(sizeof(flv_stream)); if (stream == NULL) { return NULL; } stream->flvin = fopen(file, "rb"); if (stream->flvin == NULL) { free(stream); return NULL; } stream->current_tag_body_length = 0; stream->current_tag_body_overflow = 0; stream->current_tag_offset = 0; stream->state = FLV_STREAM_STATE_START; return stream; } int flv_read_header(flv_stream * stream, flv_header * header) { if (stream == NULL || stream->flvin == NULL || feof(stream->flvin) || stream->state != FLV_STREAM_STATE_START) { return FLV_ERROR_EOF; } if (fread(&header->signature, sizeof(header->signature), 1, stream->flvin) == 0 || fread(&header->version, sizeof(header->version), 1, stream->flvin) == 0 || fread(&header->flags, sizeof(header->flags), 1, stream->flvin) == 0 || fread(&header->offset, sizeof(header->offset), 1, stream->flvin) == 0) { return FLV_ERROR_EOF; } if (header->signature[0] != 'F' || header->signature[1] != 'L' || header->signature[2] != 'V') { return FLV_ERROR_NO_FLV; } stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE; return FLV_OK; } int flv_read_prev_tag_size(flv_stream * stream, uint32 * prev_tag_size) { uint32_be val; if (stream == NULL || stream->flvin == NULL || feof(stream->flvin)) { return FLV_ERROR_EOF; } /* skip remaining tag body bytes */ if (stream->state == FLV_STREAM_STATE_TAG_BODY) { lfs_fseek(stream->flvin, stream->current_tag_offset + FLV_TAG_SIZE + uint24_be_to_uint32(stream->current_tag.body_length), SEEK_SET); stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE; } if (stream->state == FLV_STREAM_STATE_PREV_TAG_SIZE) { if (fread(&val, sizeof(uint32_be), 1, stream->flvin) == 0) { return FLV_ERROR_EOF; } else { stream->state = FLV_STREAM_STATE_TAG; *prev_tag_size = swap_uint32(val); return FLV_OK; } } else { return FLV_ERROR_EOF; } } int flv_read_tag(flv_stream * stream, flv_tag * tag) { if (stream == NULL || stream->flvin == NULL || feof(stream->flvin)) { return FLV_ERROR_EOF; } /* skip header */ if (stream->state == FLV_STREAM_STATE_START) { lfs_fseek(stream->flvin, FLV_HEADER_SIZE, SEEK_CUR); stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE; } /* skip current tag body */ if (stream->state == FLV_STREAM_STATE_TAG_BODY) { lfs_fseek(stream->flvin, stream->current_tag_offset + FLV_TAG_SIZE + uint24_be_to_uint32(stream->current_tag.body_length), SEEK_SET); stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE; } /* skip previous tag size */ if (stream->state == FLV_STREAM_STATE_PREV_TAG_SIZE) { lfs_fseek(stream->flvin, sizeof(uint32_be), SEEK_CUR); stream->state = FLV_STREAM_STATE_TAG; } if (stream->state == FLV_STREAM_STATE_TAG) { stream->current_tag_offset = lfs_ftell(stream->flvin); if (fread(&tag->type, sizeof(tag->type), 1, stream->flvin) == 0 || fread(&tag->body_length, sizeof(tag->body_length), 1, stream->flvin) == 0 || fread(&tag->timestamp, sizeof(tag->timestamp), 1, stream->flvin) == 0 || fread(&tag->timestamp_extended, sizeof(tag->timestamp_extended), 1, stream->flvin) == 0 || fread(&tag->stream_id, sizeof(tag->stream_id), 1, stream->flvin) == 0) { return FLV_ERROR_EOF; } else { memcpy(&stream->current_tag, tag, sizeof(flv_tag)); stream->current_tag_body_length = uint24_be_to_uint32(tag->body_length); stream->current_tag_body_overflow = 0; stream->state = FLV_STREAM_STATE_TAG_BODY; return FLV_OK; } } else { return FLV_ERROR_EOF; } } int flv_read_audio_tag(flv_stream * stream, flv_audio_tag * tag) { if (stream == NULL || stream->flvin == NULL || feof(stream->flvin) || stream->state != FLV_STREAM_STATE_TAG_BODY) { return FLV_ERROR_EOF; } if (stream->current_tag_body_length == 0) { return FLV_ERROR_EMPTY_TAG; } if (fread(tag, sizeof(flv_audio_tag), 1, stream->flvin) == 0) { return FLV_ERROR_EOF; } if (stream->current_tag_body_length >= sizeof(flv_audio_tag)) { stream->current_tag_body_length -= sizeof(flv_audio_tag); } else { stream->current_tag_body_overflow = sizeof(flv_audio_tag) - stream->current_tag_body_length; stream->current_tag_body_length = 0; } if (stream->current_tag_body_length == 0) { stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE; if (stream->current_tag_body_overflow > 0) { lfs_fseek(stream->flvin, -(file_offset_t)stream->current_tag_body_overflow, SEEK_CUR); } } return FLV_OK; } int flv_read_video_tag(flv_stream * stream, flv_video_tag * tag) { if (stream == NULL || stream->flvin == NULL || feof(stream->flvin) || stream->state != FLV_STREAM_STATE_TAG_BODY) { return FLV_ERROR_EOF; } if (stream->current_tag_body_length == 0) { return FLV_ERROR_EMPTY_TAG; } if (fread(tag, sizeof(flv_video_tag), 1, stream->flvin) == 0) { return FLV_ERROR_EOF; } if (stream->current_tag_body_length >= sizeof(flv_video_tag)) { stream->current_tag_body_length -= sizeof(flv_video_tag); } else { stream->current_tag_body_overflow = sizeof(flv_video_tag) - stream->current_tag_body_length; stream->current_tag_body_length = 0; } if (stream->current_tag_body_length == 0) { stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE; if (stream->current_tag_body_overflow > 0) { lfs_fseek(stream->flvin, -(file_offset_t)stream->current_tag_body_overflow, SEEK_CUR); } } return FLV_OK; } int flv_read_metadata(flv_stream * stream, amf_data ** name, amf_data ** data) { amf_data * d; byte error_code; size_t data_size; if (stream == NULL || stream->flvin == NULL || feof(stream->flvin) || stream->state != FLV_STREAM_STATE_TAG_BODY) { return FLV_ERROR_EOF; } if (stream->current_tag_body_length == 0) { return FLV_ERROR_EMPTY_TAG; } /* read metadata name */ d = amf_data_file_read(stream->flvin); *name = d; error_code = amf_data_get_error_code(d); if (error_code == AMF_ERROR_EOF) { return FLV_ERROR_EOF; } else if (error_code != AMF_ERROR_OK) { return FLV_ERROR_INVALID_METADATA_NAME; } /* if only name can be read, metadata are invalid */ data_size = amf_data_size(d); if (stream->current_tag_body_length > data_size) { stream->current_tag_body_length -= (uint32)data_size; } else { stream->current_tag_body_length = 0; stream->current_tag_body_overflow = (uint32)data_size - stream->current_tag_body_length; stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE; if (stream->current_tag_body_overflow > 0) { lfs_fseek(stream->flvin, -(file_offset_t)stream->current_tag_body_overflow, SEEK_CUR); } return FLV_ERROR_INVALID_METADATA; } /* read metadata contents */ d = amf_data_file_read(stream->flvin); *data = d; error_code = amf_data_get_error_code(d); if (error_code == AMF_ERROR_EOF) { return FLV_ERROR_EOF; } if (error_code != AMF_ERROR_OK) { return FLV_ERROR_INVALID_METADATA; } data_size = amf_data_size(d); if (stream->current_tag_body_length >= data_size) { stream->current_tag_body_length -= (uint32)data_size; } else { stream->current_tag_body_overflow = (uint32)data_size - stream->current_tag_body_length; stream->current_tag_body_length = 0; } if (stream->current_tag_body_length == 0) { stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE; if (stream->current_tag_body_overflow > 0) { lfs_fseek(stream->flvin, -(file_offset_t)stream->current_tag_body_overflow, SEEK_CUR); } } return FLV_OK; } size_t flv_read_tag_body(flv_stream * stream, void * buffer, size_t buffer_size) { size_t bytes_number; if (stream == NULL || stream->flvin == NULL || feof(stream->flvin) || stream->state != FLV_STREAM_STATE_TAG_BODY) { return 0; } bytes_number = (buffer_size > stream->current_tag_body_length) ? stream->current_tag_body_length : buffer_size; bytes_number = fread(buffer, sizeof(byte), bytes_number, stream->flvin); stream->current_tag_body_length -= (uint32)bytes_number; if (stream->current_tag_body_length == 0) { stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE; } return bytes_number; } file_offset_t flv_get_current_tag_offset(flv_stream * stream) { return (stream != NULL) ? stream->current_tag_offset : 0; } file_offset_t flv_get_offset(flv_stream * stream) { return (stream != NULL) ? lfs_ftell(stream->flvin) : 0; } void flv_reset(flv_stream * stream) { /* go back to beginning of file */ if (stream != NULL && stream->flvin != NULL) { stream->current_tag_body_length = 0; stream->current_tag_offset = 0; stream->state = FLV_STREAM_STATE_START; lfs_fseek(stream->flvin, 0, SEEK_SET); } } void flv_close(flv_stream * stream) { if (stream != NULL) { if (stream->flvin != NULL) { fclose(stream->flvin); } free(stream); } } /* FLV buffer copy helper functions */ size_t flv_copy_header(void * to, const flv_header * header, size_t buffer_size) { char * out = to; if (buffer_size < FLV_HEADER_SIZE) { return 0; } memcpy(out, &header->signature, sizeof(header->signature)); out += sizeof(header->signature); memcpy(out, &header->version, sizeof(header->version)); out += sizeof(header->version); memcpy(out, &header->flags, sizeof(header->flags)); out += sizeof(header->flags); memcpy(out, &header->offset, sizeof(header->offset)); return FLV_HEADER_SIZE; } size_t flv_copy_tag(void * to, const flv_tag * tag, size_t buffer_size) { char * out = to; if (buffer_size < FLV_TAG_SIZE) { return 0; } memcpy(out, &tag->type, sizeof(tag->type)); out += sizeof(tag->type); memcpy(out, &tag->body_length, sizeof(tag->body_length)); out += sizeof(tag->body_length); memcpy(out, &tag->timestamp, sizeof(tag->timestamp)); out += sizeof(tag->timestamp); memcpy(out, &tag->timestamp_extended, sizeof(tag->timestamp_extended)); out += sizeof(tag->timestamp_extended); memcpy(out, &tag->stream_id, sizeof(tag->stream_id)); return FLV_TAG_SIZE; } size_t flv_copy_prev_tag_size(void *to, uint32 prev_tag_size, size_t buffer_size) { uint32_be pts = swap_uint32(prev_tag_size); if (buffer_size < sizeof(uint32)) { return 0; } memcpy(to, &pts, sizeof(uint32_be)); return sizeof(uint32_be); } /* FLV stdio writing helper functions */ size_t flv_write_header(FILE * out, const flv_header * header) { if (fwrite(&header->signature, sizeof(header->signature), 1, out) == 0) return 0; if (fwrite(&header->version, sizeof(header->version), 1, out) == 0) return 0; if (fwrite(&header->flags, sizeof(header->flags), 1, out) == 0) return 0; if (fwrite(&header->offset, sizeof(header->offset), 1, out) == 0) return 0; return 1; } size_t flv_write_tag(FILE * out, const flv_tag * tag) { if (fwrite(&tag->type, sizeof(tag->type), 1, out) == 0) return 0; if (fwrite(&tag->body_length, sizeof(tag->body_length), 1, out) == 0) return 0; if (fwrite(&tag->timestamp, sizeof(tag->timestamp), 1, out) == 0) return 0; if (fwrite(&tag->timestamp_extended, sizeof(tag->timestamp_extended), 1, out) == 0) return 0; if (fwrite(&tag->stream_id, sizeof(tag->stream_id), 1, out) == 0) return 0; return 1; } /* FLV event based parser */ int flv_parse(const char * file, flv_parser * parser) { flv_header header; flv_tag tag; flv_audio_tag at; flv_video_tag vt; amf_data * name, * data; char * name_str; uint32 prev_tag_size; int retval; if (parser == NULL) { return FLV_ERROR_EOF; } parser->stream = flv_open(file); if (parser->stream == NULL) { return FLV_ERROR_OPEN_READ; } retval = flv_read_header(parser->stream, &header); if (retval != FLV_OK) { flv_close(parser->stream); return retval; } if (parser->on_header != NULL) { retval = parser->on_header(&header, parser); if (retval != FLV_OK) { flv_close(parser->stream); return retval; } } while (flv_read_tag(parser->stream, &tag) == FLV_OK) { if (parser->on_tag != NULL) { retval = parser->on_tag(&tag, parser); if (retval != FLV_OK) { flv_close(parser->stream); return retval; } } if (tag.type == FLV_TAG_TYPE_AUDIO) { retval = flv_read_audio_tag(parser->stream, &at); if (retval == FLV_ERROR_EOF) { flv_close(parser->stream); return retval; } if (retval != FLV_ERROR_EMPTY_TAG && parser->on_audio_tag != NULL) { retval = parser->on_audio_tag(&tag, at, parser); if (retval != FLV_OK) { flv_close(parser->stream); return retval; } } } else if (tag.type == FLV_TAG_TYPE_VIDEO) { retval = flv_read_video_tag(parser->stream, &vt); if (retval == FLV_ERROR_EOF) { flv_close(parser->stream); return retval; } if (retval != FLV_ERROR_EMPTY_TAG && parser->on_video_tag != NULL) { retval = parser->on_video_tag(&tag, vt, parser); if (retval != FLV_OK) { flv_close(parser->stream); return retval; } } } else if (tag.type == FLV_TAG_TYPE_META) { name = data = NULL; retval = flv_read_metadata(parser->stream, &name, &data); if (retval == FLV_ERROR_EOF) { amf_data_free(name); amf_data_free(data); flv_close(parser->stream); return retval; } if (retval == FLV_OK && parser->on_metadata_tag != NULL && amf_data_get_type(name) == AMF_TYPE_STRING) { name_str = (char *)amf_string_get_bytes(name); retval = parser->on_metadata_tag(&tag, name_str, data, parser); if (retval != FLV_OK) { amf_data_free(name); amf_data_free(data); flv_close(parser->stream); return retval; } } amf_data_free(name); amf_data_free(data); } else { if (parser->on_unknown_tag != NULL) { retval = parser->on_unknown_tag(&tag, parser); if (retval != FLV_OK) { flv_close(parser->stream); return retval; } } } retval = flv_read_prev_tag_size(parser->stream, &prev_tag_size); if (retval != FLV_OK) { flv_close(parser->stream); return retval; } if (parser->on_prev_tag_size != NULL) { retval = parser->on_prev_tag_size(prev_tag_size, parser); if (retval != FLV_OK) { flv_close(parser->stream); return retval; } } } if (parser->on_stream_end != NULL) { retval = parser->on_stream_end(parser); if (retval != FLV_OK) { flv_close(parser->stream); return retval; } } flv_close(parser->stream); return FLV_OK; } flvmeta-1.2.2/src/flv.h000066400000000000000000000175321346231570700147210ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __FLV_H__ #define __FLV_H__ /* Configuration of the sources */ #ifdef HAVE_CONFIG_H # include #endif #include "types.h" #include "amf.h" /* error statuses */ #define FLV_OK 0 #define FLV_ERROR_OPEN_READ 1 #define FLV_ERROR_NO_FLV 2 #define FLV_ERROR_EOF 3 #define FLV_ERROR_MEMORY 4 #define FLV_ERROR_EMPTY_TAG 5 #define FLV_ERROR_INVALID_METADATA_NAME 6 #define FLV_ERROR_INVALID_METADATA 7 /* flv file format structure and definitions */ /* FLV file header */ #define FLV_SIGNATURE "FLV" #define FLV_VERSION ((uint8)0x01) #define FLV_FLAG_VIDEO ((uint8)0x01) #define FLV_FLAG_AUDIO ((uint8)0x04) typedef struct __flv_header { byte signature[3]; /* always "FLV" */ uint8 version; /* should be 1 */ uint8_bitmask flags; uint32_be offset; /* always 9 */ } flv_header; #define FLV_HEADER_SIZE 9u #define flv_header_has_video(header) ((header).flags & FLV_FLAG_VIDEO) #define flv_header_has_audio(header) ((header).flags & FLV_FLAG_AUDIO) #define flv_header_get_offset(header) (swap_uint32((header).offset)) /* FLV tag */ #define FLV_TAG_TYPE_AUDIO ((uint8)0x08) #define FLV_TAG_TYPE_VIDEO ((uint8)0x09) #define FLV_TAG_TYPE_META ((uint8)0x12) typedef struct __flv_tag { uint8 type; uint24_be body_length; /* in bytes, total tag size minus 11 */ uint24_be timestamp; /* milli-seconds */ uint8 timestamp_extended; /* timestamp extension */ uint24_be stream_id; /* reserved, must be "\0\0\0" */ /* body comes next */ } flv_tag; #define FLV_TAG_SIZE 11u #define flv_tag_get_body_length(tag) (uint24_be_to_uint32((tag).body_length)) #define flv_tag_get_timestamp(tag) \ (uint24_be_to_uint32((tag).timestamp) + ((tag).timestamp_extended << 24)) #define flv_tag_get_stream_id(tag) (uint24_be_to_uint32((tag).stream_id)) /* audio tag */ #define FLV_AUDIO_TAG_SOUND_TYPE_MONO 0 #define FLV_AUDIO_TAG_SOUND_TYPE_STEREO 1 #define FLV_AUDIO_TAG_SOUND_SIZE_8 0 #define FLV_AUDIO_TAG_SOUND_SIZE_16 1 #define FLV_AUDIO_TAG_SOUND_RATE_5_5 0 #define FLV_AUDIO_TAG_SOUND_RATE_11 1 #define FLV_AUDIO_TAG_SOUND_RATE_22 2 #define FLV_AUDIO_TAG_SOUND_RATE_44 3 #define FLV_AUDIO_TAG_SOUND_FORMAT_LINEAR_PCM 0 #define FLV_AUDIO_TAG_SOUND_FORMAT_ADPCM 1 #define FLV_AUDIO_TAG_SOUND_FORMAT_MP3 2 #define FLV_AUDIO_TAG_SOUND_FORMAT_LINEAR_PCM_LE 3 #define FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER_16_MONO 4 #define FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER_8_MONO 5 #define FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER 6 #define FLV_AUDIO_TAG_SOUND_FORMAT_G711_A 7 #define FLV_AUDIO_TAG_SOUND_FORMAT_G711_MU 8 #define FLV_AUDIO_TAG_SOUND_FORMAT_RESERVED 9 #define FLV_AUDIO_TAG_SOUND_FORMAT_AAC 10 #define FLV_AUDIO_TAG_SOUND_FORMAT_SPEEX 11 #define FLV_AUDIO_TAG_SOUND_FORMAT_MP3_8 14 #define FLV_AUDIO_TAG_SOUND_FORMAT_DEVICE_SPECIFIC 15 typedef byte flv_audio_tag; #define flv_audio_tag_sound_type(tag) (((tag) & 0x01) >> 0) #define flv_audio_tag_sound_size(tag) (((tag) & 0x02) >> 1) #define flv_audio_tag_sound_rate(tag) (((tag) & 0x0C) >> 2) #define flv_audio_tag_sound_format(tag) (((tag) & 0xF0) >> 4) /* video tag */ #define FLV_VIDEO_TAG_CODEC_JPEG 1 #define FLV_VIDEO_TAG_CODEC_SORENSEN_H263 2 #define FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO 3 #define FLV_VIDEO_TAG_CODEC_ON2_VP6 4 #define FLV_VIDEO_TAG_CODEC_ON2_VP6_ALPHA 5 #define FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO_V2 6 #define FLV_VIDEO_TAG_CODEC_AVC 7 #define FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME 1 #define FLV_VIDEO_TAG_FRAME_TYPE_INTERFRAME 2 #define FLV_VIDEO_TAG_FRAME_TYPE_DISPOSABLE_INTERFRAME 3 #define FLV_VIDEO_TAG_FRAME_TYPE_GENERATED_KEYFRAME 4 #define FLV_VIDEO_TAG_FRAME_TYPE_COMMAND_FRAME 5 typedef byte flv_video_tag; #define flv_video_tag_codec_id(tag) (((tag) & 0x0F) >> 0) #define flv_video_tag_frame_type(tag) (((tag) & 0xF0) >> 4) /* AVC packet types */ typedef byte flv_avc_packet_type; #define FLV_AVC_PACKET_TYPE_SEQUENCE_HEADER 0 #define FLV_AVC_PACKET_TYPE_NALU 1 #define FLV_AVC_PACKET_TYPE_SEQUENCE_END 2 /* AAC packet types */ typedef byte flv_aac_packet_type; #define FLV_AAC_PACKET_TYPE_SEQUENCE_HEADER 0 #define FLV_AAC_PACKET_TYPE_RAW 1 #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* FLV helper functions */ void flv_tag_set_timestamp(flv_tag * tag, uint32 timestamp); /* FLV stream */ #define FLV_STREAM_STATE_START 0 #define FLV_STREAM_STATE_TAG 1 #define FLV_STREAM_STATE_TAG_BODY 2 #define FLV_STREAM_STATE_PREV_TAG_SIZE 3 typedef struct __flv_stream { FILE * flvin; uint8 state; flv_tag current_tag; file_offset_t current_tag_offset; uint32 current_tag_body_length; uint32 current_tag_body_overflow; } flv_stream; /* FLV stream functions */ flv_stream * flv_open(const char * file); int flv_read_header(flv_stream * stream, flv_header * header); int flv_read_prev_tag_size(flv_stream * stream, uint32 * prev_tag_size); int flv_read_tag(flv_stream * stream, flv_tag * tag); int flv_read_audio_tag(flv_stream * stream, flv_audio_tag * tag); int flv_read_video_tag(flv_stream * stream, flv_video_tag * tag); int flv_read_metadata(flv_stream * stream, amf_data ** name, amf_data ** data); size_t flv_read_tag_body(flv_stream * stream, void * buffer, size_t buffer_size); file_offset_t flv_get_current_tag_offset(flv_stream * stream); file_offset_t flv_get_offset(flv_stream * stream); void flv_reset(flv_stream * stream); void flv_close(flv_stream * stream); /* FLV buffer copy helper functions */ size_t flv_copy_header(void * to, const flv_header * header, size_t buffer_size); size_t flv_copy_tag(void * to, const flv_tag * tag, size_t buffer_size); size_t flv_copy_prev_tag_size(void * to, uint32 prev_tag_size, size_t buffer_size); /* FLV stdio writing helper functions */ size_t flv_write_header(FILE * out, const flv_header * header); size_t flv_write_tag(FILE * out, const flv_tag * tag); /* FLV event based parser */ typedef struct __flv_parser { flv_stream * stream; void * user_data; int (* on_header)(flv_header * header, struct __flv_parser * parser); int (* on_tag)(flv_tag * tag, struct __flv_parser * parser); int (* on_metadata_tag)(flv_tag * tag, char * name, amf_data * data, struct __flv_parser * parser); int (* on_audio_tag)(flv_tag * tag, flv_audio_tag audio_tag, struct __flv_parser * parser); int (* on_video_tag)(flv_tag * tag, flv_video_tag audio_tag, struct __flv_parser * parser); int (* on_unknown_tag)(flv_tag * tag, struct __flv_parser * parser); int (* on_prev_tag_size)(uint32 size, struct __flv_parser * parser); int (* on_stream_end)(struct __flv_parser * parser); } flv_parser; int flv_parse(const char * file, flv_parser * parser); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __FLV_H__ */ flvmeta-1.2.2/src/flvmeta.c000066400000000000000000000420111346231570700155510ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "flvmeta.h" #include "check.h" #include "dump.h" #include "update.h" /* Command-line options */ static struct option long_options[] = { { "dump", no_argument, NULL, 'D'}, { "full-dump", no_argument, NULL, 'F'}, { "check", no_argument, NULL, 'C'}, { "update", no_argument, NULL, 'U'}, { "dump-format", required_argument, NULL, 'd'}, { "json", no_argument, NULL, 'j'}, { "raw", no_argument, NULL, 'r'}, { "xml", no_argument, NULL, 'x'}, { "yaml", no_argument, NULL, 'y'}, { "event", required_argument, NULL, 'e'}, { "level", required_argument, NULL, 'l'}, { "quiet", no_argument, NULL, 'q'}, { "print-metadata", no_argument, NULL, 'm'}, { "add", required_argument, NULL, 'a'}, { "no-lastsecond", no_argument, NULL, 's'}, { "preserve", no_argument, NULL, 'p'}, { "fix", no_argument, NULL, 'f'}, { "ignore", no_argument, NULL, 'i'}, { "reset-timestamps", no_argument, NULL, 't'}, { "all-keyframes", no_argument, NULL, 'k'}, { "verbose", no_argument, NULL, 'v'}, { "version", no_argument, NULL, 'V'}, { "help", no_argument, NULL, 'h'}, { 0, 0, 0, 0 } }; /* short options */ #define DUMP_COMMAND "D" #define FULL_DUMP_COMMAND "F" #define CHECK_COMMAND "C" #define UPDATE_COMMAND "U" #define DUMP_FORMAT_OPTION "d:" #define JSON_OPTION "j" #define RAW_OPTION "r" #define XML_OPTION "x" #define YAML_OPTION "y" #define EVENT_OPTION "e:" #define LEVEL_OPTION "l:" #define QUIET_OPTION "q" #define PRINT_METADATA_OPTION "m" #define ADD_OPTION "a:" #define NO_LASTSECOND_OPTION "s" #define PRESERVE_OPTION "p" #define FIX_OPTION "f" #define IGNORE_OPTION "i" #define RESET_TIMESTAMPS_OPTION "t" #define ALL_KEYFRAMES_OPTION "k" #define VERBOSE_OPTION "v" #define VERSION_OPTION "V" #define HELP_OPTION "h" static void version(void) { printf("%s\n\n", PACKAGE_STRING); printf("%s\n", COPYRIGHT_STR); printf("This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"); } static void usage(const char * name) { fprintf(stderr, "Usage: %s [COMMAND] [OPTIONS] INPUT_FILE [OUTPUT_FILE]\n", name); fprintf(stderr, "Try `%s --help' for more information.\n", name); } static void help(const char * name) { printf("Usage: %s [COMMAND] [OPTIONS] INPUT_FILE [OUTPUT_FILE]\n", name); printf("\nIf OUTPUT_FILE is omitted for commands expecting it, INPUT_FILE will be overwritten instead.\n" "\nCommands:\n" " -D, --dump dump onMetaData tag (default without output file)\n" " -F, --full-dump dump all tags\n" " -C, --check check the validity of INPUT_FILE, returning 0 if\n" " the file is valid, or 10 if it contains errors\n" " -U, --update update computed onMetaData tag from INPUT_FILE\n" " into OUTPUT_FILE (default with output file)\n" /* " -A, --extract-audio extract raw audio data into OUTPUT_FILE\n"*/ /* " -E, --extract-video extract raw video data into OUTPUT_FILE\n"*/ "\nDump options:\n" " -d, --dump-format=TYPE dump format is of type TYPE\n" " TYPE is 'xml' (default), 'json', 'raw', or 'yaml'\n" " -j, --json equivalent to --dump-format=json\n" " -r, --raw equivalent to --dump-format=raw\n" " -x, --xml equivalent to --dump-format=xml\n" " -y, --yaml equivalent to --dump-format=yaml\n" " -e, --event=EVENT specify the event to be dumped instead of 'onMetadata'\n" "\nCheck options:\n" " -l, --level=LEVEL print only messages where level is at least LEVEL\n" " LEVEL is 'info', 'warning' (default), 'error', or 'fatal'\n" " -q, --quiet do not print messages, only return the status code\n" " -x, --xml generate an XML report\n" " -j, --json generate a JSON report\n" "\nUpdate options:\n" " -m, --print-metadata print metadata to stdout after update using\n" " the specified format\n" " -a, --add=NAME=VALUE add a metadata string value to the output file\n" " -s, --no-lastsecond do not create the onLastSecond tag\n" " -p, --preserve preserve input file existing onMetadata tags\n" " -f, --fix fix invalid tags from the input file\n" " -i, --ignore ignore invalid tags from the input file\n" " (the default is to stop with an error)\n" " -t, --reset-timestamps reset timestamps so OUTPUT_FILE starts at zero\n" " -k, --all-keyframes index all keyframe tags, including duplicate timestamps\n" "\nCommon options:\n" " -v, --verbose display informative messages\n" "\nMiscellaneous:\n" " -V, --version print version information and exit\n" " -h, --help display this information and exit\n"); printf("\nPlease report bugs to <%s>\n", PACKAGE_BUGREPORT); } static int parse_command_line(int argc, char ** argv, flvmeta_opts * options) { int option, option_index; option_index = 0; do { option = getopt_long(argc, argv, DUMP_COMMAND FULL_DUMP_COMMAND CHECK_COMMAND UPDATE_COMMAND DUMP_FORMAT_OPTION JSON_OPTION RAW_OPTION XML_OPTION YAML_OPTION EVENT_OPTION LEVEL_OPTION QUIET_OPTION PRINT_METADATA_OPTION ADD_OPTION NO_LASTSECOND_OPTION PRESERVE_OPTION FIX_OPTION IGNORE_OPTION RESET_TIMESTAMPS_OPTION ALL_KEYFRAMES_OPTION VERBOSE_OPTION VERSION_OPTION HELP_OPTION, long_options, &option_index); switch (option) { /* commands */ case 'D': if (options->command != FLVMETA_DEFAULT_COMMAND) { fprintf(stderr, "%s: only one command can be specified -- %s\n", argv[0], argv[optind]); return EXIT_FAILURE; } options->command = FLVMETA_DUMP_COMMAND; break; case 'F': if (options->command != FLVMETA_DEFAULT_COMMAND) { fprintf(stderr, "%s: only one command can be specified -- %s\n", argv[0], argv[optind]); return EXIT_FAILURE; } options->command = FLVMETA_FULL_DUMP_COMMAND; break; case 'C': if (options->command != FLVMETA_DEFAULT_COMMAND) { fprintf(stderr, "%s: only one command can be specified -- %s\n", argv[0], argv[optind]); return EXIT_FAILURE; } options->command = FLVMETA_CHECK_COMMAND; break; case 'U': if (options->command != FLVMETA_DEFAULT_COMMAND) { fprintf(stderr, "%s: only one command can be specified -- %s\n", argv[0], argv[optind]); return EXIT_FAILURE; } options->command = FLVMETA_UPDATE_COMMAND; break; /* options */ /* check options */ case 'l': if (!strcmp(optarg, "info")) { options->check_level = FLVMETA_CHECK_LEVEL_INFO; } else if (!strcmp(optarg, "warning")) { options->check_level = FLVMETA_CHECK_LEVEL_WARNING; } else if (!strcmp(optarg, "error")) { options->check_level = FLVMETA_CHECK_LEVEL_ERROR; } else if (!strcmp(optarg, "fatal")) { options->check_level = FLVMETA_CHECK_LEVEL_FATAL; } else { fprintf(stderr, "%s: invalid message level -- %s\n", argv[0], optarg); usage(argv[0]); return EXIT_FAILURE; } break; case 'q': options->quiet = 1; break; /* dump options */ case 'd': if (!strcmp(optarg, "xml")) { options->dump_format = FLVMETA_FORMAT_XML; } if (!strcmp(optarg, "raw")) { options->dump_format = FLVMETA_FORMAT_RAW; } else if (!strcmp(optarg, "json")) { options->dump_format = FLVMETA_FORMAT_JSON; } else if (!strcmp(optarg, "yaml")) { options->dump_format = FLVMETA_FORMAT_YAML; } else { fprintf(stderr, "%s: invalid output format -- %s\n", argv[0], optarg); usage(argv[0]); return EXIT_FAILURE; } break; case 'j': /* json dump format, or generation of json check report */ options->dump_format = FLVMETA_FORMAT_JSON; options->check_report_format = FLVMETA_FORMAT_JSON; break; case 'r': options->dump_format = FLVMETA_FORMAT_RAW; break; case 'x': /* xml dump format, or generation of xml check report */ options->dump_format = FLVMETA_FORMAT_XML; options->check_report_format = FLVMETA_FORMAT_XML; break; case 'y': options->dump_format = FLVMETA_FORMAT_YAML; break; case 'e': options->metadata_event = optarg; break; /* update options */ case 'm': options->dump_metadata = 1; break; case 'a': { char * eq_pos; eq_pos = strchr(optarg, '='); if (eq_pos != NULL) { *eq_pos = 0; if (options->metadata == NULL) { options->metadata = amf_associative_array_new(); } amf_associative_array_add(options->metadata, optarg, amf_str(eq_pos + 1)); } else { fprintf(stderr, "%s: invalid metadata format -- %s\n", argv[0], optarg); usage(argv[0]); return EXIT_FAILURE; } } break; case 's': options->insert_onlastsecond = 0; break; case 'p': options->preserve_metadata = 1; break; case 'f': options->error_handling = FLVMETA_FIX_ERRORS; break; case 'i': options->error_handling = FLVMETA_IGNORE_ERRORS; break; case 't': options->reset_timestamps = 1; break; case 'k': options->all_keyframes = 1; break; /* common options */ case 'v': options->verbose = 1; break; /* Miscellaneous */ case 'V': options->command = FLVMETA_VERSION_COMMAND; return OK; break; case 'h': options->command = FLVMETA_HELP_COMMAND; return OK; break; /* last option */ case EOF: break; /* error cases */ /* unknown option */ case '?': /* missing argument */ case ':': /* we should not be here */ default: usage(argv[0]); return EXIT_FAILURE; } } while (option != EOF); /* input filename */ if (optind > 0 && optind < argc) { options->input_file = argv[optind]; } else { fprintf(stderr, "%s: no input file\n", argv[0]); usage(argv[0]); return EXIT_FAILURE; } /* output filename */ if (++optind < argc) { options->output_file = argv[optind]; } /* determine command if default */ if (options->command == FLVMETA_DEFAULT_COMMAND && options->output_file != NULL) { options->command = FLVMETA_UPDATE_COMMAND; } else if (options->command == FLVMETA_DEFAULT_COMMAND && options->output_file == NULL) { options->command = FLVMETA_DUMP_COMMAND; } /* output file is input file if not specified */ if (options->output_file == NULL) { options->output_file = options->input_file; } return OK; } int main(int argc, char ** argv) { int errcode; /* flvmeta default options */ static flvmeta_opts options; options.command = FLVMETA_DEFAULT_COMMAND; options.input_file = NULL; options.output_file = NULL; options.metadata = NULL; options.check_level = FLVMETA_CHECK_LEVEL_WARNING; options.quiet = 0; options.check_report_format = FLVMETA_FORMAT_RAW; options.dump_metadata = 0; options.insert_onlastsecond = 1; options.reset_timestamps = 0; options.all_keyframes = 0; options.preserve_metadata = 0; options.error_handling = FLVMETA_EXIT_ON_ERROR; options.dump_format = FLVMETA_FORMAT_XML; options.verbose = 0; options.metadata_event = NULL; /* Command-line parsing */ errcode = parse_command_line(argc, argv, &options); /* free metadata if necessary */ if ((errcode != OK || options.command != FLVMETA_UPDATE_COMMAND) && options.metadata != NULL) { amf_data_free(options.metadata); } if (errcode == OK) { /* execute command */ switch (options.command) { case FLVMETA_DUMP_COMMAND: errcode = dump_metadata(&options); break; case FLVMETA_FULL_DUMP_COMMAND: errcode = dump_flv_file(&options); break; case FLVMETA_CHECK_COMMAND: errcode = check_flv_file(&options); break; case FLVMETA_UPDATE_COMMAND: errcode = update_metadata(&options); break; case FLVMETA_VERSION_COMMAND: version(); break; case FLVMETA_HELP_COMMAND: help(argv[0]); break; } /* error report */ switch (errcode) { case ERROR_OPEN_READ: fprintf(stderr, "%s: cannot open %s for reading\n", argv[0], options.input_file); break; case ERROR_NO_FLV: fprintf(stderr, "%s: %s is not a valid FLV file\n", argv[0], options.input_file); break; case ERROR_EOF: fprintf(stderr, "%s: unexpected end of file\n", argv[0]); break; case ERROR_MEMORY: fprintf(stderr, "%s: memory allocation error\n", argv[0]); break; case ERROR_EMPTY_TAG: fprintf(stderr, "%s: empty FLV tag\n", argv[0]); break; case ERROR_OPEN_WRITE: fprintf(stderr, "%s: cannot open %s for writing\n", argv[0], options.output_file); break; case ERROR_INVALID_TAG: fprintf(stderr, "%s: invalid FLV tag\n", argv[0]); break; case ERROR_WRITE: fprintf(stderr, "%s: unable to write to %s\n", argv[0], options.output_file); break; } } return errcode; } flvmeta-1.2.2/src/flvmeta.h000066400000000000000000000055321346231570700155650ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __FLVMETA_H__ #define __FLVMETA_H__ /* Configuration of the sources */ #ifdef HAVE_CONFIG_H # include #endif #include "flv.h" /* copyright string */ #define COPYRIGHT_STR "Copyright (C) 2007-2019 Marc Noirot " /* error statuses */ #define OK FLV_OK #define ERROR_OPEN_READ FLV_ERROR_OPEN_READ #define ERROR_NO_FLV FLV_ERROR_NO_FLV #define ERROR_EOF FLV_ERROR_EOF #define ERROR_MEMORY FLV_ERROR_MEMORY #define ERROR_EMPTY_TAG FLV_ERROR_EMPTY_TAG #define ERROR_OPEN_WRITE 6 #define ERROR_INVALID_TAG 7 #define ERROR_WRITE 8 /* invalid flv file reported by the check command (one or more errors) */ #define ERROR_INVALID_FLV_FILE 9 /* stop file parsing without error */ #define FLVMETA_DUMP_STOP_OK 10 /* commands */ #define FLVMETA_DEFAULT_COMMAND 0 #define FLVMETA_DUMP_COMMAND 1 #define FLVMETA_FULL_DUMP_COMMAND 2 #define FLVMETA_CHECK_COMMAND 3 #define FLVMETA_UPDATE_COMMAND 4 #define FLVMETA_VERSION_COMMAND 5 #define FLVMETA_HELP_COMMAND 6 /* error handling */ #define FLVMETA_EXIT_ON_ERROR 0 #define FLVMETA_FIX_ERRORS 1 #define FLVMETA_IGNORE_ERRORS 2 /* check levels */ #define FLVMETA_CHECK_LEVEL_INFO 0 #define FLVMETA_CHECK_LEVEL_WARNING 1 #define FLVMETA_CHECK_LEVEL_ERROR 2 #define FLVMETA_CHECK_LEVEL_FATAL 3 /* dump and check formats */ #define FLVMETA_FORMAT_XML 0 #define FLVMETA_FORMAT_RAW 1 #define FLVMETA_FORMAT_JSON 2 #define FLVMETA_FORMAT_YAML 3 /* flvmeta options */ typedef struct __flvmeta_opts { int command; char * input_file; char * output_file; amf_data * metadata; int dump_metadata; int check_level; int quiet; int check_report_format; int insert_onlastsecond; int reset_timestamps; int all_keyframes; int preserve_metadata; int error_handling; int dump_format; int verbose; char * metadata_event; } flvmeta_opts; #endif /* __FLVMETA_H__ */ flvmeta-1.2.2/src/info.c000066400000000000000000000710211346231570700150510ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "info.h" #include "avc.h" #include /* compute Sorensen H.263 video size */ static int compute_h263_size(flv_stream * flv_in, flv_info * info, uint32 body_length) { byte header[9]; uint24_be psc_be; uint32 psc; /* make sure we have enough bytes to read in the current tag */ if (body_length >= 9) { if (flv_read_tag_body(flv_in, header, 9) < 9) { return FLV_ERROR_EOF; } psc_be.b[0] = header[0]; psc_be.b[1] = header[1]; psc_be.b[2] = header[2]; psc = uint24_be_to_uint32(psc_be) >> 7; if (psc == 1) { uint32 psize = ((header[3] & 0x03) << 1) + ((header[4] >> 7) & 0x01); switch (psize) { case 0: info->video_width = ((header[4] & 0x7f) << 1) + ((header[5] >> 7) & 0x01); info->video_height = ((header[5] & 0x7f) << 1) + ((header[6] >> 7) & 0x01); break; case 1: info->video_width = ((header[4] & 0x7f) << 9) + (header[5] << 1) + ((header[6] >> 7) & 0x01); info->video_height = ((header[6] & 0x7f) << 9) + (header[7] << 1) + ((header[8] >> 7) & 0x01); break; case 2: info->video_width = 352; info->video_height = 288; break; case 3: info->video_width = 176; info->video_height = 144; break; case 4: info->video_width = 128; info->video_height = 96; break; case 5: info->video_width = 320; info->video_height = 240; break; case 6: info->video_width = 160; info->video_height = 120; break; default: break; } } } return FLV_OK; } /* compute Screen video size */ static int compute_screen_size(flv_stream * flv_in, flv_info * info, uint32 body_length) { byte header[4]; /* make sure we have enough bytes to read in the current tag */ if (body_length >= 4) { if (flv_read_tag_body(flv_in, header, 4) < 4) { return FLV_ERROR_EOF; } info->video_width = ((header[0] & 0x0f) << 8) + header[1]; info->video_height = ((header[2] & 0x0f) << 8) + header[3]; } return FLV_OK; } /* compute On2 VP6 video size */ static int compute_vp6_size(flv_stream * flv_in, flv_info * info, uint32 body_length) { byte header[7], offset; /* make sure we have enough bytes to read in the current tag */ if (body_length >= 7) { if (flv_read_tag_body(flv_in, header, 7) < 7) { return FLV_ERROR_EOF; } /* two bytes offset if VP6 0 */ offset = (header[1] & 0x01 || !(header[2] & 0x06)) << 1; info->video_width = (header[4 + offset] << 4) - (header[0] >> 4); info->video_height = (header[3 + offset] << 4) - (header[0] & 0x0f); } return FLV_OK; } /* compute On2 VP6 with Alpha video size */ static int compute_vp6_alpha_size(flv_stream * flv_in, flv_info * info, uint32 body_length) { byte header[10], offset; /* make sure we have enough bytes to read in the current tag */ if (body_length >= 10) { if (flv_read_tag_body(flv_in, header, 10) < 10) { return FLV_ERROR_EOF; } /* two bytes offset if VP6 0 */ offset = (header[4] & 0x01 || !(header[5] & 0x06)) << 1; info->video_width = (header[7 + offset] << 4) - (header[0] >> 4); info->video_height = (header[6 + offset] << 4) - (header[0] & 0x0f); } return FLV_OK; } /* compute AVC (H.264) video size (experimental) */ static int compute_avc_size(flv_stream * flv_in, flv_info * info, uint32 body_length) { return read_avc_resolution(flv_in, body_length, &(info->video_width), &(info->video_height)); } /* compute video width and height from the first video frame */ static int compute_video_size(flv_stream * flv_in, flv_info * info, uint32 body_length) { switch (info->video_codec) { case FLV_VIDEO_TAG_CODEC_SORENSEN_H263: return compute_h263_size(flv_in, info, body_length); case FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO: case FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO_V2: return compute_screen_size(flv_in, info, body_length); case FLV_VIDEO_TAG_CODEC_ON2_VP6: return compute_vp6_size(flv_in, info, body_length); case FLV_VIDEO_TAG_CODEC_ON2_VP6_ALPHA: return compute_vp6_alpha_size(flv_in, info, body_length); case FLV_VIDEO_TAG_CODEC_AVC: return compute_avc_size(flv_in, info, body_length); default: return FLV_OK; } } /* read the flv file thoroughly to get all necessary information. we need to check : - timestamp of first audio for audio delay - whether we have audio and video - first frames codecs (audio, video) - total audio and video data sizes - keyframe offsets and timestamps - whether the last video frame is a keyframe - last keyframe timestamp - onMetaData tag total size - total tags size - first tag after onMetaData offset - last timestamp - real video data size, number of frames, duration to compute framerate and video data rate - real audio data size, duration to compute audio data rate - video headers to find width and height. (depends on the encoding) */ int get_flv_info(flv_stream * flv_in, flv_info * info, const flvmeta_opts * opts) { uint32 prev_timestamp_video; uint32 prev_timestamp_audio; uint32 prev_timestamp_meta; uint8 timestamp_extended_video; uint8 timestamp_extended_audio; uint8 timestamp_extended_meta; uint8 have_video_size; uint8 have_first_timestamp; uint32 tag_number; int result; flv_tag ft; info->have_video = 0; info->have_audio = 0; info->video_width = 0; info->video_height = 0; info->video_codec = 0; info->video_frames_number = 0; info->audio_codec = 0; info->audio_size = 0; info->audio_rate = 0; info->audio_stereo = 0; info->video_data_size = 0; info->audio_data_size = 0; info->meta_data_size = 0; info->real_video_data_size = 0; info->real_audio_data_size = 0; info->video_first_timestamp = 0; info->audio_first_timestamp = 0; info->first_timestamp = 0; info->can_seek_to_end = 0; info->have_keyframes = 0; info->last_keyframe_timestamp = 0; info->on_metadata_size = 0; info->on_metadata_offset = 0; info->biggest_tag_body_size = 0; info->last_timestamp = 0; info->video_frame_duration = 0; info->audio_frame_duration = 0; info->total_prev_tags_size = 0; info->have_on_last_second = 0; info->last_media_frame_type = 0; info->original_on_metadata = NULL; info->keyframes = NULL; info->times = NULL; info->filepositions = NULL; if (opts->verbose) { fprintf(stdout, "Parsing %s...\n", opts->input_file); } /* read FLV header */ if (flv_read_header(flv_in, &(info->header)) != FLV_OK) { return ERROR_NO_FLV; } info->keyframes = amf_object_new(); info->times = amf_array_new(); info->filepositions = amf_array_new(); amf_object_add(info->keyframes, "times", info->times); amf_object_add(info->keyframes, "filepositions", info->filepositions); /* first empty previous tag size */ info->total_prev_tags_size = sizeof(uint32_be); /* first timestamp */ have_first_timestamp = 0; /* extended timestamp initialization */ prev_timestamp_video = 0; prev_timestamp_audio = 0; prev_timestamp_meta = 0; timestamp_extended_video = 0; timestamp_extended_audio = 0; timestamp_extended_meta = 0; tag_number = 0; have_video_size = 0; while (flv_read_tag(flv_in, &ft) == FLV_OK) { file_offset_t offset; uint32 body_length; uint32 timestamp; offset = flv_get_current_tag_offset(flv_in); body_length = flv_tag_get_body_length(ft); timestamp = flv_tag_get_timestamp(ft); /* extended timestamp fixing */ if (ft.type == FLV_TAG_TYPE_META) { if (timestamp < prev_timestamp_meta && prev_timestamp_meta - timestamp > 0xF00000) { ++timestamp_extended_meta; } prev_timestamp_meta = timestamp; if (timestamp_extended_meta > 0) { timestamp += timestamp_extended_meta << 24; } } else if (ft.type == FLV_TAG_TYPE_AUDIO) { if (timestamp < prev_timestamp_audio && prev_timestamp_audio - timestamp > 0xF00000) { ++timestamp_extended_audio; } prev_timestamp_audio = timestamp; if (timestamp_extended_audio > 0) { timestamp += timestamp_extended_audio << 24; } } else if (ft.type == FLV_TAG_TYPE_VIDEO) { if (timestamp < prev_timestamp_video && prev_timestamp_video - timestamp > 0xF00000) { ++timestamp_extended_video; } prev_timestamp_video = timestamp; if (timestamp_extended_video > 0) { timestamp += timestamp_extended_video << 24; } } /* non-zero starting timestamp handling */ if (!have_first_timestamp && ft.type != FLV_TAG_TYPE_META) { info->first_timestamp = timestamp; have_first_timestamp = 1; } if (opts->reset_timestamps && timestamp > 0) { timestamp -= info->first_timestamp; } /* update the info struct only if the tag is valid */ if (ft.type == FLV_TAG_TYPE_META || ft.type == FLV_TAG_TYPE_AUDIO || ft.type == FLV_TAG_TYPE_VIDEO) { if (info->biggest_tag_body_size < body_length) { info->biggest_tag_body_size = body_length; } info->last_timestamp = timestamp; } if (ft.type == FLV_TAG_TYPE_META) { amf_data *tag_name, *data; int retval; tag_name = data = NULL; if (body_length == 0) { if (opts->verbose) { fprintf(stdout, "Warning: empty metadata tag at 0x%" FILE_OFFSET_PRINTF_FORMAT "X\n", FILE_OFFSET_PRINTF_TYPE(offset)); } } else { retval = flv_read_metadata(flv_in, &tag_name, &data); if (retval == FLV_ERROR_EOF) { amf_data_free(tag_name); amf_data_free(data); return ERROR_EOF; } else if (retval == FLV_ERROR_INVALID_METADATA_NAME) { if (opts->verbose) { fprintf(stdout, "Warning: invalid metadata name at 0x%" FILE_OFFSET_PRINTF_FORMAT "X\n", FILE_OFFSET_PRINTF_TYPE(offset)); } } else if (retval == FLV_ERROR_INVALID_METADATA) { if (opts->verbose) { fprintf(stdout, "Warning: invalid metadata at 0x%" FILE_OFFSET_PRINTF_FORMAT "X\n", FILE_OFFSET_PRINTF_TYPE(offset)); } if (opts->error_handling == FLVMETA_EXIT_ON_ERROR) { amf_data_free(tag_name); amf_data_free(data); return ERROR_INVALID_TAG; } } } /* check metadata name */ if (body_length > 0 && amf_data_get_type(tag_name) == AMF_TYPE_STRING) { char * name = (char *)amf_string_get_bytes(tag_name); size_t len = (size_t)amf_string_get_size(tag_name); /* get info only on the first onMetaData we read */ if (info->on_metadata_size == 0 && !strncmp(name, "onMetaData", len)) { info->on_metadata_size = body_length + FLV_TAG_SIZE + sizeof(uint32_be); info->on_metadata_offset = offset; /* if we want to preserve existing metadata, then extract them */ if (opts->preserve_metadata == 1) { /* we need an AMF associative array here, so we must discard errors and mis-typed data */ if (amf_data_get_error_code(data) != AMF_ERROR_OK || amf_data_get_type(data) != AMF_TYPE_ASSOCIATIVE_ARRAY) { amf_data_free(data); data = amf_associative_array_new(); } info->original_on_metadata = data; } else { amf_data_free(data); } } else { if (!strncmp(name, "onLastSecond", len)) { info->have_on_last_second = 1; } info->meta_data_size += (body_length + FLV_TAG_SIZE); info->total_prev_tags_size += sizeof(uint32_be); if (data != NULL) { amf_data_free(data); } } } /* just ignore metadata that don't have a proper name */ else { info->meta_data_size += (body_length + FLV_TAG_SIZE); info->total_prev_tags_size += sizeof(uint32_be); amf_data_free(data); } amf_data_free(tag_name); } else if (ft.type == FLV_TAG_TYPE_VIDEO) { flv_video_tag vt; /* do not take video frame into account if body length is zero and we ignore errors */ if (body_length == 0) { if (opts->verbose) { fprintf(stdout, "Warning: empty video tag at 0x%" FILE_OFFSET_PRINTF_FORMAT "X\n", FILE_OFFSET_PRINTF_TYPE(offset)); } } else { if (flv_read_video_tag(flv_in, &vt) != FLV_OK) { return ERROR_EOF; } if (info->have_video != 1) { info->have_video = 1; info->video_codec = flv_video_tag_codec_id(vt); info->video_first_timestamp = timestamp; } if (have_video_size != 1 && flv_video_tag_frame_type(vt) == FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME) { /* read first video frame to get critical info */ result = compute_video_size(flv_in, info, body_length - sizeof(flv_video_tag)); if (result != FLV_OK) { return result; } if (info->video_width > 0 && info->video_height > 0) { have_video_size = 1; } /* if we cannot fetch that information from the first tag, we'll try for each following video key frame */ } /* add keyframe to list */ if (flv_video_tag_frame_type(vt) == FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME) { /* do not add keyframe if the previous one has the same timestamp */ if (!info->have_keyframes || (info->have_keyframes && info->last_keyframe_timestamp != timestamp) || opts->all_keyframes) { info->have_keyframes = 1; info->last_keyframe_timestamp = timestamp; amf_array_push(info->times, amf_number_new(timestamp / 1000.0)); amf_array_push(info->filepositions, amf_number_new((number64)offset)); } /* is last frame a key frame ? if so, we can seek to end */ info->can_seek_to_end = 1; } else { info->can_seek_to_end = 0; } info->real_video_data_size += (body_length - 1); } info->video_frames_number++; /* we assume all video frames have the same size as the first one */ if (info->video_frame_duration == 0) { info->video_frame_duration = timestamp - info->video_first_timestamp; } info->last_media_frame_type = FLV_TAG_TYPE_VIDEO; info->video_data_size += (body_length + FLV_TAG_SIZE); info->total_prev_tags_size += sizeof(uint32_be); } else if (ft.type == FLV_TAG_TYPE_AUDIO) { flv_audio_tag at; /* do not take audio frame into account if body length is zero and we ignore errors */ if (body_length == 0) { if (opts->verbose) { fprintf(stdout, "Warning: empty audio tag at 0x%" FILE_OFFSET_PRINTF_FORMAT "X\n", FILE_OFFSET_PRINTF_TYPE(offset)); } } else { if (flv_read_audio_tag(flv_in, &at) != FLV_OK) { return ERROR_EOF; } if (info->have_audio != 1) { info->have_audio = 1; info->audio_codec = flv_audio_tag_sound_format(at); info->audio_rate = flv_audio_tag_sound_rate(at); info->audio_size = flv_audio_tag_sound_size(at); info->audio_stereo = flv_audio_tag_sound_type(at); info->audio_first_timestamp = timestamp; } /* we assume all audio frames have the same size as the first one */ if (info->audio_frame_duration == 0) { info->audio_frame_duration = timestamp - info->audio_first_timestamp; } info->real_audio_data_size += (body_length - 1); } info->last_media_frame_type = FLV_TAG_TYPE_AUDIO; info->audio_data_size += (body_length + FLV_TAG_SIZE); info->total_prev_tags_size += sizeof(uint32_be); } else { if (opts->error_handling == FLVMETA_FIX_ERRORS) { /* TODO : fix errors if possible */ } else if (opts->error_handling == FLVMETA_IGNORE_ERRORS) { /* let's continue the parsing */ if (opts->verbose) { fprintf(stdout, "Warning: invalid tag at 0x%" FILE_OFFSET_PRINTF_FORMAT "X\n", FILE_OFFSET_PRINTF_TYPE(offset)); } info->total_prev_tags_size += sizeof(uint32_be); } else { return ERROR_INVALID_TAG; } } ++tag_number; } if (opts->verbose) { fprintf(stdout, "Found %d tags\n", tag_number); } return OK; } /* compute the metadata */ void compute_metadata(flv_info * info, flv_metadata * meta, const flvmeta_opts * opts) { uint32 new_on_metadata_size, on_last_second_size; file_offset_t data_size, total_filesize; number64 duration, video_data_rate, framerate; amf_data * amf_total_filesize; amf_data * amf_total_data_size; amf_node * node_t; amf_node * node_f; if (opts->verbose) { fprintf(stdout, "Computing metadata...\n"); } meta->on_last_second_name = amf_str("onLastSecond"); meta->on_last_second = amf_associative_array_new(); meta->on_metadata_name = amf_str("onMetaData"); if (opts->metadata == NULL) { meta->on_metadata = amf_associative_array_new(); } else { meta->on_metadata = opts->metadata; } amf_associative_array_add(meta->on_metadata, "hasMetadata", amf_boolean_new(1)); amf_associative_array_add(meta->on_metadata, "hasVideo", amf_boolean_new(info->have_video)); amf_associative_array_add(meta->on_metadata, "hasAudio", amf_boolean_new(info->have_audio)); if (info->last_media_frame_type == FLV_TAG_TYPE_AUDIO) { duration = (info->last_timestamp - (opts->reset_timestamps ? 0 : info->first_timestamp) + info->audio_frame_duration) / 1000.0; } else if (info->last_media_frame_type == FLV_TAG_TYPE_VIDEO) { duration = (info->last_timestamp - (opts->reset_timestamps ? 0 : info->first_timestamp) + info->video_frame_duration) / 1000.0; } else { /* no last frame type means no audio and no video, therefore no duration */ duration = 0; } amf_associative_array_add(meta->on_metadata, "duration", amf_number_new(duration)); amf_associative_array_add(meta->on_metadata, "lasttimestamp", amf_number_new(info->last_timestamp / 1000.0)); amf_associative_array_add(meta->on_metadata, "lastkeyframetimestamp", amf_number_new(info->last_keyframe_timestamp / 1000.0)); if (info->video_width > 0) amf_associative_array_add(meta->on_metadata, "width", amf_number_new(info->video_width)); if (info->video_height > 0) amf_associative_array_add(meta->on_metadata, "height", amf_number_new(info->video_height)); video_data_rate = ((info->real_video_data_size / 1024.0) * 8.0) / duration; amf_associative_array_add(meta->on_metadata, "videodatarate", amf_number_new(video_data_rate)); framerate = info->video_frames_number / duration; amf_associative_array_add(meta->on_metadata, "framerate", amf_number_new(framerate)); if (info->have_audio) { number64 audio_khz, audio_sample_rate; number64 audio_data_rate = ((info->real_audio_data_size / 1024.0) * 8.0) / duration; amf_associative_array_add(meta->on_metadata, "audiodatarate", amf_number_new(audio_data_rate)); audio_khz = 0.0; switch (info->audio_rate) { case FLV_AUDIO_TAG_SOUND_RATE_5_5: audio_khz = 5500.0; break; case FLV_AUDIO_TAG_SOUND_RATE_11: audio_khz = 11000.0; break; case FLV_AUDIO_TAG_SOUND_RATE_22: audio_khz = 22050.0; break; case FLV_AUDIO_TAG_SOUND_RATE_44: audio_khz = 44100.0; break; } amf_associative_array_add(meta->on_metadata, "audiosamplerate", amf_number_new(audio_khz)); audio_sample_rate = 0.0; switch (info->audio_size) { case FLV_AUDIO_TAG_SOUND_SIZE_8: audio_sample_rate = 8.0; break; case FLV_AUDIO_TAG_SOUND_SIZE_16: audio_sample_rate = 16.0; break; } amf_associative_array_add(meta->on_metadata, "audiosamplesize", amf_number_new(audio_sample_rate)); amf_associative_array_add(meta->on_metadata, "stereo", amf_boolean_new(info->audio_stereo == FLV_AUDIO_TAG_SOUND_TYPE_STEREO)); } /* to be computed later */ amf_total_filesize = amf_number_new(0); amf_associative_array_add(meta->on_metadata, "filesize", amf_total_filesize); if (info->have_video) { amf_associative_array_add(meta->on_metadata, "videosize", amf_number_new((number64)info->video_data_size)); } if (info->have_audio) { amf_associative_array_add(meta->on_metadata, "audiosize", amf_number_new((number64)info->audio_data_size)); } /* to be computed later */ amf_total_data_size = amf_number_new(0); amf_associative_array_add(meta->on_metadata, "datasize", amf_total_data_size); amf_associative_array_add(meta->on_metadata, "metadatacreator", amf_str(PACKAGE_STRING)); amf_associative_array_add(meta->on_metadata, "metadatadate", amf_date_new((number64)time(NULL)*1000, 0)); if (info->have_audio) { amf_associative_array_add(meta->on_metadata, "audiocodecid", amf_number_new((number64)info->audio_codec)); } if (info->have_video) { amf_associative_array_add(meta->on_metadata, "videocodecid", amf_number_new((number64)info->video_codec)); } if (info->have_audio && info->have_video) { number64 audio_delay = ((sint32)info->audio_first_timestamp - (sint32)info->video_first_timestamp) / 1000.0; amf_associative_array_add(meta->on_metadata, "audiodelay", amf_number_new((number64)audio_delay)); } amf_associative_array_add(meta->on_metadata, "canSeekToEnd", amf_boolean_new(info->can_seek_to_end)); /* only add empty cuepoints if we don't preserve existing tags OR if the existing tags don't have cuepoints */ if (opts->preserve_metadata == 0 || (opts->preserve_metadata == 1 && amf_associative_array_get(info->original_on_metadata, "cuePoints") == NULL)) { amf_associative_array_add(meta->on_metadata, "hasCuePoints", amf_boolean_new(0)); amf_associative_array_add(meta->on_metadata, "cuePoints", amf_array_new()); } amf_associative_array_add(meta->on_metadata, "hasKeyframes", amf_boolean_new(info->have_keyframes)); amf_associative_array_add(meta->on_metadata, "keyframes", info->keyframes); /* merge metadata from input file if we specified the preserve option */ if (opts->preserve_metadata) { /* for each tag in the original metadata, we add it if it does not exist */ amf_node * node = amf_associative_array_first(info->original_on_metadata); while (node != NULL) { char * name = (char *)amf_string_get_bytes(amf_associative_array_get_name(node)); if (amf_associative_array_get(meta->on_metadata, name) == NULL) { /* add metadata */ amf_associative_array_add(meta->on_metadata, name, amf_data_clone(amf_associative_array_get_data(node))); } node = amf_associative_array_next(node); } /* all old data has been duplicated to the new metadata, we can safely delete it */ amf_data_free(info->original_on_metadata); info->original_on_metadata = NULL; } /* When we know the final size, we can recompute te offsets for the filepositions, and the final datasize. */ new_on_metadata_size = FLV_TAG_SIZE + sizeof(uint32_be) + (uint32)(amf_data_size(meta->on_metadata_name) + amf_data_size(meta->on_metadata)); on_last_second_size = (uint32)(amf_data_size(meta->on_last_second_name) + amf_data_size(meta->on_last_second)); node_t = amf_array_first(info->times); node_f = amf_array_first(info->filepositions); while (node_t != NULL || node_f != NULL) { amf_data * amf_filepos = amf_array_get(node_f); number64 offset = amf_number_get_value(amf_filepos) + new_on_metadata_size - info->on_metadata_size; number64 timestamp = amf_number_get_value(amf_array_get(node_t)); /* after the onLastSecond event we need to take in account the tag size */ if (opts->insert_onlastsecond && !info->have_on_last_second && (info->last_timestamp - timestamp * 1000) <= 1000) { offset += (FLV_TAG_SIZE + on_last_second_size + sizeof(uint32_be)); } amf_number_set_value(amf_filepos, offset); node_t = amf_array_next(node_t); node_f = amf_array_next(node_f); } /* compute data size, ie. size of metadata excluding prev_tag_size */ data_size = info->meta_data_size + FLV_TAG_SIZE + (uint32)(amf_data_size(meta->on_metadata_name) + amf_data_size(meta->on_metadata)); if (!info->have_on_last_second && opts->insert_onlastsecond) { data_size += (uint32)on_last_second_size + FLV_TAG_SIZE; } amf_number_set_value(amf_total_data_size, (number64)data_size); /* compute total file size */ total_filesize = FLV_HEADER_SIZE + info->total_prev_tags_size + info->video_data_size + info->audio_data_size + info->meta_data_size + new_on_metadata_size; if (!info->have_on_last_second && opts->insert_onlastsecond) { /* if we have to add onLastSecond, we must count the header and new prevTagSize we add */ total_filesize += (uint32)(FLV_TAG_SIZE + on_last_second_size + sizeof(uint32_be)); } amf_number_set_value(amf_total_filesize, (number64)total_filesize); } flvmeta-1.2.2/src/info.h000066400000000000000000000047631346231570700150670ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __INFO_H__ #define __INFO_H__ #include "flvmeta.h" typedef struct __flv_info { flv_header header; uint8 have_video; uint8 have_audio; uint32 video_width; uint32 video_height; uint8 video_codec; uint32 video_frames_number; uint8 audio_codec; uint8 audio_size; uint8 audio_rate; uint8 audio_stereo; file_offset_t video_data_size; file_offset_t audio_data_size; file_offset_t meta_data_size; file_offset_t real_video_data_size; file_offset_t real_audio_data_size; uint32 video_first_timestamp; uint32 audio_first_timestamp; uint32 first_timestamp; uint8 can_seek_to_end; uint8 have_keyframes; uint32 last_keyframe_timestamp; uint32 on_metadata_size; file_offset_t on_metadata_offset; uint32 biggest_tag_body_size; uint32 last_timestamp; uint32 video_frame_duration; uint32 audio_frame_duration; file_offset_t total_prev_tags_size; uint8 have_on_last_second; uint8 last_media_frame_type; amf_data * original_on_metadata; amf_data * keyframes; amf_data * times; amf_data * filepositions; } flv_info; typedef struct __flv_metadata { amf_data * on_last_second_name; amf_data * on_last_second; amf_data * on_metadata_name; amf_data * on_metadata; } flv_metadata; #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ int get_flv_info(flv_stream * flv_in, flv_info * info, const flvmeta_opts * opts); void compute_metadata(flv_info * info, flv_metadata * meta, const flvmeta_opts * opts); void compute_current_metadata(flv_info * info, flv_metadata * meta); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __INFO_H__ */ flvmeta-1.2.2/src/json.c000066400000000000000000000077541346231570700151030ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "json.h" #include #include #include #ifdef HAVE_ISFINITE # include #else # include "util.h" # define isfinite flvmeta_isfinite #endif static void json_print_string(const char * str, size_t bytes) { size_t i; printf("\""); for (i = 0; i < bytes; ++i) { switch (str[i]) { case '\"': printf("\\\""); break; case '\\': printf("\\\\"); break; case '/': printf("\\/"); break; case '\b': printf("\\b"); break; case '\f': printf("\\f"); break; case '\n': printf("\\n"); break; case '\r': printf("\\r"); break; case '\t': printf("\\t"); break; default: if (iscntrl(str[i])) { printf("\\u%.4u", str[i]); } else { printf("%c", str[i]); } } } printf("\""); } static void json_print_comma(json_emitter * je) { if (je->print_comma != 0) { printf(","); je->print_comma = 0; } } void json_emit_init(json_emitter * je) { je->print_comma = 0; } void json_emit_object_start(json_emitter * je) { json_print_comma(je); printf("{"); } void json_emit_object_key(json_emitter * je, const char * str, size_t bytes) { json_print_comma(je); json_print_string(str, bytes); printf(":"); je->print_comma = 0; } void json_emit_object_key_z(json_emitter * je, const char * str) { json_print_comma(je); json_print_string(str, strlen(str)); printf(":"); je->print_comma = 0; } void json_emit_object_end(json_emitter * je) { printf("}"); je->print_comma = 1; } void json_emit_array_start(json_emitter * je) { json_print_comma(je); printf("["); } void json_emit_array_end(json_emitter * je) { printf("]"); je->print_comma = 1; } void json_emit_boolean(json_emitter * je, byte value) { json_print_comma(je); printf("%s", value != 0 ? "true" : "false"); je->print_comma = 1; } void json_emit_null(json_emitter * je) { json_print_comma(je); printf("null"); je->print_comma = 1; } void json_emit_integer(json_emitter * je, int value) { json_print_comma(je); printf("%i", value); je->print_comma = 1; } void json_emit_file_offset(json_emitter * je, file_offset_t value) { json_print_comma(je); printf("%" FILE_OFFSET_PRINTF_FORMAT "u", FILE_OFFSET_PRINTF_TYPE(value)); je->print_comma = 1; } void json_emit_number(json_emitter * je, number64 value) { /* http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf (page 208) states that NaN and Infinity are represented as null. */ if (!isfinite(value)) { json_emit_null(je); return; } json_print_comma(je); printf("%.12g", value); je->print_comma = 1; } void json_emit_string(json_emitter * je, const char * str, size_t bytes) { json_print_comma(je); json_print_string(str, bytes); je->print_comma = 1; } void json_emit_string_z(json_emitter * je, const char * str) { json_print_comma(je); json_print_string(str, strlen(str)); je->print_comma = 1; } flvmeta-1.2.2/src/json.h000066400000000000000000000040171346231570700150750ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __JSON_H__ #define __JSON_H__ #include "types.h" /** This is a basic JSON emitter. It prints JSON-formatted data to stdout without creating an in-memory tree. */ /* json emitter structure */ typedef struct __json_emitter { byte print_comma; } json_emitter; #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ void json_emit_init(json_emitter * je); void json_emit_object_start(json_emitter * je); void json_emit_object_key(json_emitter * je, const char * str, size_t bytes); void json_emit_object_key_z(json_emitter * je, const char * str); void json_emit_object_end(json_emitter * je); void json_emit_array_start(json_emitter * je); void json_emit_array_end(json_emitter * je); void json_emit_boolean(json_emitter * je, byte value); void json_emit_null(json_emitter * je); void json_emit_integer(json_emitter * je, int value); void json_emit_file_offset(json_emitter * je, file_offset_t value); void json_emit_number(json_emitter * je, number64 value); void json_emit_string(json_emitter * je, const char * str, size_t bytes); void json_emit_string_z(json_emitter * je, const char * str); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __JSON_H__ */ flvmeta-1.2.2/src/libyaml/000077500000000000000000000000001346231570700154025ustar00rootroot00000000000000flvmeta-1.2.2/src/libyaml/CMakeLists.txt000066400000000000000000000003631346231570700201440ustar00rootroot00000000000000set(LIBYAML_SRC api.c config.h dumper.c emitter.c loader.c parser.c reader.c scanner.c writer.c yaml.h yaml_private.h ) include_directories(BEFORE ${CMAKE_SOURCE_DIR}/src/libyaml) add_library(yaml STATIC ${LIBYAML_SRC})flvmeta-1.2.2/src/libyaml/api.c000066400000000000000000001017371346231570700163300ustar00rootroot00000000000000 #include "yaml_private.h" /* * Get the library version. */ YAML_DECLARE(const char *) yaml_get_version_string(void) { return YAML_VERSION_STRING; } /* * Get the library version numbers. */ YAML_DECLARE(void) yaml_get_version(int *major, int *minor, int *patch) { *major = YAML_VERSION_MAJOR; *minor = YAML_VERSION_MINOR; *patch = YAML_VERSION_PATCH; } /* * Allocate a dynamic memory block. */ YAML_DECLARE(void *) yaml_malloc(size_t size) { return malloc(size ? size : 1); } /* * Reallocate a dynamic memory block. */ YAML_DECLARE(void *) yaml_realloc(void *ptr, size_t size) { return ptr ? realloc(ptr, size ? size : 1) : malloc(size ? size : 1); } /* * Free a dynamic memory block. */ YAML_DECLARE(void) yaml_free(void *ptr) { if (ptr) free(ptr); } /* * Duplicate a string. */ YAML_DECLARE(yaml_char_t *) yaml_strdup(const yaml_char_t *str) { if (!str) return NULL; return (yaml_char_t *)strdup((char *)str); } /* * Extend a string. */ YAML_DECLARE(int) yaml_string_extend(yaml_char_t **start, yaml_char_t **pointer, yaml_char_t **end) { yaml_char_t *new_start = yaml_realloc(*start, (*end - *start)*2); if (!new_start) return 0; memset(new_start + (*end - *start), 0, *end - *start); *pointer = new_start + (*pointer - *start); *end = new_start + (*end - *start)*2; *start = new_start; return 1; } /* * Append a string B to a string A. */ YAML_DECLARE(int) yaml_string_join( yaml_char_t **a_start, yaml_char_t **a_pointer, yaml_char_t **a_end, yaml_char_t **b_start, yaml_char_t **b_pointer, yaml_char_t **b_end) { if (*b_start == *b_pointer) return 1; while (*a_end - *a_pointer <= *b_pointer - *b_start) { if (!yaml_string_extend(a_start, a_pointer, a_end)) return 0; } memcpy(*a_pointer, *b_start, *b_pointer - *b_start); *a_pointer += *b_pointer - *b_start; return 1; } /* * Extend a stack. */ YAML_DECLARE(int) yaml_stack_extend(void **start, void **top, void **end) { void *new_start = yaml_realloc(*start, ((char *)*end - (char *)*start)*2); if (!new_start) return 0; *top = (char *)new_start + ((char *)*top - (char *)*start); *end = (char *)new_start + ((char *)*end - (char *)*start)*2; *start = new_start; return 1; } /* * Extend or move a queue. */ YAML_DECLARE(int) yaml_queue_extend(void **start, void **head, void **tail, void **end) { /* Check if we need to resize the queue. */ if (*start == *head && *tail == *end) { void *new_start = yaml_realloc(*start, ((char *)*end - (char *)*start)*2); if (!new_start) return 0; *head = (char *)new_start + ((char *)*head - (char *)*start); *tail = (char *)new_start + ((char *)*tail - (char *)*start); *end = (char *)new_start + ((char *)*end - (char *)*start)*2; *start = new_start; } /* Check if we need to move the queue at the beginning of the buffer. */ if (*tail == *end) { if (*head != *tail) { memmove(*start, *head, (char *)*tail - (char *)*head); } *tail = (char *)*tail - (char *)*head + (char *)*start; *head = *start; } return 1; } /* * Create a new parser object. */ YAML_DECLARE(int) yaml_parser_initialize(yaml_parser_t *parser) { assert(parser); /* Non-NULL parser object expected. */ memset(parser, 0, sizeof(yaml_parser_t)); if (!BUFFER_INIT(parser, parser->raw_buffer, INPUT_RAW_BUFFER_SIZE)) goto error; if (!BUFFER_INIT(parser, parser->buffer, INPUT_BUFFER_SIZE)) goto error; if (!QUEUE_INIT(parser, parser->tokens, INITIAL_QUEUE_SIZE)) goto error; if (!STACK_INIT(parser, parser->indents, INITIAL_STACK_SIZE)) goto error; if (!STACK_INIT(parser, parser->simple_keys, INITIAL_STACK_SIZE)) goto error; if (!STACK_INIT(parser, parser->states, INITIAL_STACK_SIZE)) goto error; if (!STACK_INIT(parser, parser->marks, INITIAL_STACK_SIZE)) goto error; if (!STACK_INIT(parser, parser->tag_directives, INITIAL_STACK_SIZE)) goto error; return 1; error: BUFFER_DEL(parser, parser->raw_buffer); BUFFER_DEL(parser, parser->buffer); QUEUE_DEL(parser, parser->tokens); STACK_DEL(parser, parser->indents); STACK_DEL(parser, parser->simple_keys); STACK_DEL(parser, parser->states); STACK_DEL(parser, parser->marks); STACK_DEL(parser, parser->tag_directives); return 0; } /* * Destroy a parser object. */ YAML_DECLARE(void) yaml_parser_delete(yaml_parser_t *parser) { assert(parser); /* Non-NULL parser object expected. */ BUFFER_DEL(parser, parser->raw_buffer); BUFFER_DEL(parser, parser->buffer); while (!QUEUE_EMPTY(parser, parser->tokens)) { yaml_token_delete(&DEQUEUE(parser, parser->tokens)); } QUEUE_DEL(parser, parser->tokens); STACK_DEL(parser, parser->indents); STACK_DEL(parser, parser->simple_keys); STACK_DEL(parser, parser->states); STACK_DEL(parser, parser->marks); while (!STACK_EMPTY(parser, parser->tag_directives)) { yaml_tag_directive_t tag_directive = POP(parser, parser->tag_directives); yaml_free(tag_directive.handle); yaml_free(tag_directive.prefix); } STACK_DEL(parser, parser->tag_directives); memset(parser, 0, sizeof(yaml_parser_t)); } /* * String read handler. */ static int yaml_string_read_handler(void *data, unsigned char *buffer, size_t size, size_t *size_read) { yaml_parser_t *parser = data; if (parser->input.string.current == parser->input.string.end) { *size_read = 0; return 1; } if (size > (size_t)(parser->input.string.end - parser->input.string.current)) { size = parser->input.string.end - parser->input.string.current; } memcpy(buffer, parser->input.string.current, size); parser->input.string.current += size; *size_read = size; return 1; } /* * File read handler. */ static int yaml_file_read_handler(void *data, unsigned char *buffer, size_t size, size_t *size_read) { yaml_parser_t *parser = data; *size_read = fread(buffer, 1, size, parser->input.file); return !ferror(parser->input.file); } /* * Set a string input. */ YAML_DECLARE(void) yaml_parser_set_input_string(yaml_parser_t *parser, const unsigned char *input, size_t size) { assert(parser); /* Non-NULL parser object expected. */ assert(!parser->read_handler); /* You can set the source only once. */ assert(input); /* Non-NULL input string expected. */ parser->read_handler = yaml_string_read_handler; parser->read_handler_data = parser; parser->input.string.start = input; parser->input.string.current = input; parser->input.string.end = input+size; } /* * Set a file input. */ YAML_DECLARE(void) yaml_parser_set_input_file(yaml_parser_t *parser, FILE *file) { assert(parser); /* Non-NULL parser object expected. */ assert(!parser->read_handler); /* You can set the source only once. */ assert(file); /* Non-NULL file object expected. */ parser->read_handler = yaml_file_read_handler; parser->read_handler_data = parser; parser->input.file = file; } /* * Set a generic input. */ YAML_DECLARE(void) yaml_parser_set_input(yaml_parser_t *parser, yaml_read_handler_t *handler, void *data) { assert(parser); /* Non-NULL parser object expected. */ assert(!parser->read_handler); /* You can set the source only once. */ assert(handler); /* Non-NULL read handler expected. */ parser->read_handler = handler; parser->read_handler_data = data; } /* * Set the source encoding. */ YAML_DECLARE(void) yaml_parser_set_encoding(yaml_parser_t *parser, yaml_encoding_t encoding) { assert(parser); /* Non-NULL parser object expected. */ assert(!parser->encoding); /* Encoding is already set or detected. */ parser->encoding = encoding; } /* * Create a new emitter object. */ YAML_DECLARE(int) yaml_emitter_initialize(yaml_emitter_t *emitter) { assert(emitter); /* Non-NULL emitter object expected. */ memset(emitter, 0, sizeof(yaml_emitter_t)); if (!BUFFER_INIT(emitter, emitter->buffer, OUTPUT_BUFFER_SIZE)) goto error; if (!BUFFER_INIT(emitter, emitter->raw_buffer, OUTPUT_RAW_BUFFER_SIZE)) goto error; if (!STACK_INIT(emitter, emitter->states, INITIAL_STACK_SIZE)) goto error; if (!QUEUE_INIT(emitter, emitter->events, INITIAL_QUEUE_SIZE)) goto error; if (!STACK_INIT(emitter, emitter->indents, INITIAL_STACK_SIZE)) goto error; if (!STACK_INIT(emitter, emitter->tag_directives, INITIAL_STACK_SIZE)) goto error; return 1; error: BUFFER_DEL(emitter, emitter->buffer); BUFFER_DEL(emitter, emitter->raw_buffer); STACK_DEL(emitter, emitter->states); QUEUE_DEL(emitter, emitter->events); STACK_DEL(emitter, emitter->indents); STACK_DEL(emitter, emitter->tag_directives); return 0; } /* * Destroy an emitter object. */ YAML_DECLARE(void) yaml_emitter_delete(yaml_emitter_t *emitter) { assert(emitter); /* Non-NULL emitter object expected. */ BUFFER_DEL(emitter, emitter->buffer); BUFFER_DEL(emitter, emitter->raw_buffer); STACK_DEL(emitter, emitter->states); while (!QUEUE_EMPTY(emitter, emitter->events)) { yaml_event_delete(&DEQUEUE(emitter, emitter->events)); } QUEUE_DEL(emitter, emitter->events); STACK_DEL(emitter, emitter->indents); while (!STACK_EMPTY(empty, emitter->tag_directives)) { yaml_tag_directive_t tag_directive = POP(emitter, emitter->tag_directives); yaml_free(tag_directive.handle); yaml_free(tag_directive.prefix); } STACK_DEL(emitter, emitter->tag_directives); yaml_free(emitter->anchors); memset(emitter, 0, sizeof(yaml_emitter_t)); } /* * String write handler. */ static int yaml_string_write_handler(void *data, unsigned char *buffer, size_t size) { yaml_emitter_t *emitter = data; if (emitter->output.string.size + *emitter->output.string.size_written < size) { memcpy(emitter->output.string.buffer + *emitter->output.string.size_written, buffer, emitter->output.string.size - *emitter->output.string.size_written); *emitter->output.string.size_written = emitter->output.string.size; return 0; } memcpy(emitter->output.string.buffer + *emitter->output.string.size_written, buffer, size); *emitter->output.string.size_written += size; return 1; } /* * File write handler. */ static int yaml_file_write_handler(void *data, unsigned char *buffer, size_t size) { yaml_emitter_t *emitter = data; return (fwrite(buffer, 1, size, emitter->output.file) == size); } /* * Set a string output. */ YAML_DECLARE(void) yaml_emitter_set_output_string(yaml_emitter_t *emitter, unsigned char *output, size_t size, size_t *size_written) { assert(emitter); /* Non-NULL emitter object expected. */ assert(!emitter->write_handler); /* You can set the output only once. */ assert(output); /* Non-NULL output string expected. */ emitter->write_handler = yaml_string_write_handler; emitter->write_handler_data = emitter; emitter->output.string.buffer = output; emitter->output.string.size = size; emitter->output.string.size_written = size_written; *size_written = 0; } /* * Set a file output. */ YAML_DECLARE(void) yaml_emitter_set_output_file(yaml_emitter_t *emitter, FILE *file) { assert(emitter); /* Non-NULL emitter object expected. */ assert(!emitter->write_handler); /* You can set the output only once. */ assert(file); /* Non-NULL file object expected. */ emitter->write_handler = yaml_file_write_handler; emitter->write_handler_data = emitter; emitter->output.file = file; } /* * Set a generic output handler. */ YAML_DECLARE(void) yaml_emitter_set_output(yaml_emitter_t *emitter, yaml_write_handler_t *handler, void *data) { assert(emitter); /* Non-NULL emitter object expected. */ assert(!emitter->write_handler); /* You can set the output only once. */ assert(handler); /* Non-NULL handler object expected. */ emitter->write_handler = handler; emitter->write_handler_data = data; } /* * Set the output encoding. */ YAML_DECLARE(void) yaml_emitter_set_encoding(yaml_emitter_t *emitter, yaml_encoding_t encoding) { assert(emitter); /* Non-NULL emitter object expected. */ assert(!emitter->encoding); /* You can set encoding only once. */ emitter->encoding = encoding; } /* * Set the canonical output style. */ YAML_DECLARE(void) yaml_emitter_set_canonical(yaml_emitter_t *emitter, int canonical) { assert(emitter); /* Non-NULL emitter object expected. */ emitter->canonical = (canonical != 0); } /* * Set the indentation increment. */ YAML_DECLARE(void) yaml_emitter_set_indent(yaml_emitter_t *emitter, int indent) { assert(emitter); /* Non-NULL emitter object expected. */ emitter->best_indent = (1 < indent && indent < 10) ? indent : 2; } /* * Set the preferred line width. */ YAML_DECLARE(void) yaml_emitter_set_width(yaml_emitter_t *emitter, int width) { assert(emitter); /* Non-NULL emitter object expected. */ emitter->best_width = (width >= 0) ? width : -1; } /* * Set if unescaped non-ASCII characters are allowed. */ YAML_DECLARE(void) yaml_emitter_set_unicode(yaml_emitter_t *emitter, int unicode) { assert(emitter); /* Non-NULL emitter object expected. */ emitter->unicode = (unicode != 0); } /* * Set the preferred line break character. */ YAML_DECLARE(void) yaml_emitter_set_break(yaml_emitter_t *emitter, yaml_break_t line_break) { assert(emitter); /* Non-NULL emitter object expected. */ emitter->line_break = line_break; } /* * Destroy a token object. */ YAML_DECLARE(void) yaml_token_delete(yaml_token_t *token) { assert(token); /* Non-NULL token object expected. */ switch (token->type) { case YAML_TAG_DIRECTIVE_TOKEN: yaml_free(token->data.tag_directive.handle); yaml_free(token->data.tag_directive.prefix); break; case YAML_ALIAS_TOKEN: yaml_free(token->data.alias.value); break; case YAML_ANCHOR_TOKEN: yaml_free(token->data.anchor.value); break; case YAML_TAG_TOKEN: yaml_free(token->data.tag.handle); yaml_free(token->data.tag.suffix); break; case YAML_SCALAR_TOKEN: yaml_free(token->data.scalar.value); break; default: break; } memset(token, 0, sizeof(yaml_token_t)); } /* * Create STREAM-START. */ YAML_DECLARE(int) yaml_stream_start_event_initialize(yaml_event_t *event, yaml_encoding_t encoding) { yaml_mark_t mark = { 0, 0, 0 }; assert(event); /* Non-NULL event object is expected. */ STREAM_START_EVENT_INIT(*event, encoding, mark, mark); return 1; } /* * Create STREAM-END. */ YAML_DECLARE(int) yaml_stream_end_event_initialize(yaml_event_t *event) { yaml_mark_t mark = { 0, 0, 0 }; assert(event); /* Non-NULL event object is expected. */ STREAM_END_EVENT_INIT(*event, mark, mark); return 1; } /* * Create DOCUMENT-START. */ YAML_DECLARE(int) yaml_document_start_event_initialize(yaml_event_t *event, yaml_version_directive_t *version_directive, yaml_tag_directive_t *tag_directives_start, yaml_tag_directive_t *tag_directives_end, int implicit) { struct { yaml_error_type_t error; } context; yaml_mark_t mark = { 0, 0, 0 }; yaml_version_directive_t *version_directive_copy = NULL; struct { yaml_tag_directive_t *start; yaml_tag_directive_t *end; yaml_tag_directive_t *top; } tag_directives_copy = { NULL, NULL, NULL }; yaml_tag_directive_t value = { NULL, NULL }; assert(event); /* Non-NULL event object is expected. */ assert((tag_directives_start && tag_directives_end) || (tag_directives_start == tag_directives_end)); /* Valid tag directives are expected. */ if (version_directive) { version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)); if (!version_directive_copy) goto error; version_directive_copy->major = version_directive->major; version_directive_copy->minor = version_directive->minor; } if (tag_directives_start != tag_directives_end) { yaml_tag_directive_t *tag_directive; if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) goto error; for (tag_directive = tag_directives_start; tag_directive != tag_directives_end; tag_directive ++) { assert(tag_directive->handle); assert(tag_directive->prefix); value.handle = yaml_strdup(tag_directive->handle); value.prefix = yaml_strdup(tag_directive->prefix); if (!value.handle || !value.prefix) goto error; if (!PUSH(&context, tag_directives_copy, value)) goto error; value.handle = NULL; value.prefix = NULL; } } DOCUMENT_START_EVENT_INIT(*event, version_directive_copy, tag_directives_copy.start, tag_directives_copy.top, implicit, mark, mark); return 1; error: yaml_free(version_directive_copy); while (!STACK_EMPTY(context, tag_directives_copy)) { yaml_tag_directive_t value = POP(context, tag_directives_copy); yaml_free(value.handle); yaml_free(value.prefix); } STACK_DEL(context, tag_directives_copy); yaml_free(value.handle); yaml_free(value.prefix); return 0; } /* * Create DOCUMENT-END. */ YAML_DECLARE(int) yaml_document_end_event_initialize(yaml_event_t *event, int implicit) { yaml_mark_t mark = { 0, 0, 0 }; assert(event); /* Non-NULL emitter object is expected. */ DOCUMENT_END_EVENT_INIT(*event, implicit, mark, mark); return 1; } /* * Create ALIAS. */ YAML_DECLARE(int) yaml_alias_event_initialize(yaml_event_t *event, yaml_char_t *anchor) { yaml_mark_t mark = { 0, 0, 0 }; yaml_char_t *anchor_copy = NULL; assert(event); /* Non-NULL event object is expected. */ assert(anchor); /* Non-NULL anchor is expected. */ anchor_copy = yaml_strdup(anchor); if (!anchor_copy) return 0; ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark); return 1; } /* * Create SCALAR. */ YAML_DECLARE(int) yaml_scalar_event_initialize(yaml_event_t *event, yaml_char_t *anchor, yaml_char_t *tag, yaml_char_t *value, int length, int plain_implicit, int quoted_implicit, yaml_scalar_style_t style) { yaml_mark_t mark = { 0, 0, 0 }; yaml_char_t *anchor_copy = NULL; yaml_char_t *tag_copy = NULL; yaml_char_t *value_copy = NULL; assert(event); /* Non-NULL event object is expected. */ assert(value); /* Non-NULL anchor is expected. */ if (anchor) { anchor_copy = yaml_strdup(anchor); if (!anchor_copy) goto error; } if (tag) { tag_copy = yaml_strdup(tag); if (!tag_copy) goto error; } if (length < 0) { length = strlen((char *)value); } value_copy = yaml_malloc(length+1); if (!value_copy) goto error; memcpy(value_copy, value, length); value_copy[length] = '\0'; SCALAR_EVENT_INIT(*event, anchor_copy, tag_copy, value_copy, length, plain_implicit, quoted_implicit, style, mark, mark); return 1; error: yaml_free(anchor_copy); yaml_free(tag_copy); yaml_free(value_copy); return 0; } /* * Create SEQUENCE-START. */ YAML_DECLARE(int) yaml_sequence_start_event_initialize(yaml_event_t *event, yaml_char_t *anchor, yaml_char_t *tag, int implicit, yaml_sequence_style_t style) { yaml_mark_t mark = { 0, 0, 0 }; yaml_char_t *anchor_copy = NULL; yaml_char_t *tag_copy = NULL; assert(event); /* Non-NULL event object is expected. */ if (anchor) { anchor_copy = yaml_strdup(anchor); if (!anchor_copy) goto error; } if (tag) { tag_copy = yaml_strdup(tag); if (!tag_copy) goto error; } SEQUENCE_START_EVENT_INIT(*event, anchor_copy, tag_copy, implicit, style, mark, mark); return 1; error: yaml_free(anchor_copy); yaml_free(tag_copy); return 0; } /* * Create SEQUENCE-END. */ YAML_DECLARE(int) yaml_sequence_end_event_initialize(yaml_event_t *event) { yaml_mark_t mark = { 0, 0, 0 }; assert(event); /* Non-NULL event object is expected. */ SEQUENCE_END_EVENT_INIT(*event, mark, mark); return 1; } /* * Create MAPPING-START. */ YAML_DECLARE(int) yaml_mapping_start_event_initialize(yaml_event_t *event, yaml_char_t *anchor, yaml_char_t *tag, int implicit, yaml_mapping_style_t style) { yaml_mark_t mark = { 0, 0, 0 }; yaml_char_t *anchor_copy = NULL; yaml_char_t *tag_copy = NULL; assert(event); /* Non-NULL event object is expected. */ if (anchor) { anchor_copy = yaml_strdup(anchor); if (!anchor_copy) goto error; } if (tag) { tag_copy = yaml_strdup(tag); if (!tag_copy) goto error; } MAPPING_START_EVENT_INIT(*event, anchor_copy, tag_copy, implicit, style, mark, mark); return 1; error: yaml_free(anchor_copy); yaml_free(tag_copy); return 0; } /* * Create MAPPING-END. */ YAML_DECLARE(int) yaml_mapping_end_event_initialize(yaml_event_t *event) { yaml_mark_t mark = { 0, 0, 0 }; assert(event); /* Non-NULL event object is expected. */ MAPPING_END_EVENT_INIT(*event, mark, mark); return 1; } /* * Destroy an event object. */ YAML_DECLARE(void) yaml_event_delete(yaml_event_t *event) { yaml_tag_directive_t *tag_directive; assert(event); /* Non-NULL event object expected. */ switch (event->type) { case YAML_DOCUMENT_START_EVENT: yaml_free(event->data.document_start.version_directive); for (tag_directive = event->data.document_start.tag_directives.start; tag_directive != event->data.document_start.tag_directives.end; tag_directive++) { yaml_free(tag_directive->handle); yaml_free(tag_directive->prefix); } yaml_free(event->data.document_start.tag_directives.start); break; case YAML_ALIAS_EVENT: yaml_free(event->data.alias.anchor); break; case YAML_SCALAR_EVENT: yaml_free(event->data.scalar.anchor); yaml_free(event->data.scalar.tag); yaml_free(event->data.scalar.value); break; case YAML_SEQUENCE_START_EVENT: yaml_free(event->data.sequence_start.anchor); yaml_free(event->data.sequence_start.tag); break; case YAML_MAPPING_START_EVENT: yaml_free(event->data.mapping_start.anchor); yaml_free(event->data.mapping_start.tag); break; default: break; } memset(event, 0, sizeof(yaml_event_t)); } /* * Create a document object. */ YAML_DECLARE(int) yaml_document_initialize(yaml_document_t *document, yaml_version_directive_t *version_directive, yaml_tag_directive_t *tag_directives_start, yaml_tag_directive_t *tag_directives_end, int start_implicit, int end_implicit) { struct { yaml_error_type_t error; } context; struct { yaml_node_t *start; yaml_node_t *end; yaml_node_t *top; } nodes = { NULL, NULL, NULL }; yaml_version_directive_t *version_directive_copy = NULL; struct { yaml_tag_directive_t *start; yaml_tag_directive_t *end; yaml_tag_directive_t *top; } tag_directives_copy = { NULL, NULL, NULL }; yaml_tag_directive_t value = { NULL, NULL }; yaml_mark_t mark = { 0, 0, 0 }; assert(document); /* Non-NULL document object is expected. */ assert((tag_directives_start && tag_directives_end) || (tag_directives_start == tag_directives_end)); /* Valid tag directives are expected. */ if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error; if (version_directive) { version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)); if (!version_directive_copy) goto error; version_directive_copy->major = version_directive->major; version_directive_copy->minor = version_directive->minor; } if (tag_directives_start != tag_directives_end) { yaml_tag_directive_t *tag_directive; if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) goto error; for (tag_directive = tag_directives_start; tag_directive != tag_directives_end; tag_directive ++) { assert(tag_directive->handle); assert(tag_directive->prefix); value.handle = yaml_strdup(tag_directive->handle); value.prefix = yaml_strdup(tag_directive->prefix); if (!value.handle || !value.prefix) goto error; if (!PUSH(&context, tag_directives_copy, value)) goto error; value.handle = NULL; value.prefix = NULL; } } DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, tag_directives_copy.start, tag_directives_copy.top, start_implicit, end_implicit, mark, mark); return 1; error: STACK_DEL(&context, nodes); yaml_free(version_directive_copy); while (!STACK_EMPTY(&context, tag_directives_copy)) { yaml_tag_directive_t value = POP(&context, tag_directives_copy); yaml_free(value.handle); yaml_free(value.prefix); } STACK_DEL(&context, tag_directives_copy); yaml_free(value.handle); yaml_free(value.prefix); return 0; } /* * Destroy a document object. */ YAML_DECLARE(void) yaml_document_delete(yaml_document_t *document) { struct { yaml_error_type_t error; } context; yaml_tag_directive_t *tag_directive; context.error = YAML_NO_ERROR; /* Eliminate a compliler warning. */ assert(document); /* Non-NULL document object is expected. */ while (!STACK_EMPTY(&context, document->nodes)) { yaml_node_t node = POP(&context, document->nodes); yaml_free(node.tag); switch (node.type) { case YAML_SCALAR_NODE: yaml_free(node.data.scalar.value); break; case YAML_SEQUENCE_NODE: STACK_DEL(&context, node.data.sequence.items); break; case YAML_MAPPING_NODE: STACK_DEL(&context, node.data.mapping.pairs); break; default: assert(0); /* Should not happen. */ } } STACK_DEL(&context, document->nodes); yaml_free(document->version_directive); for (tag_directive = document->tag_directives.start; tag_directive != document->tag_directives.end; tag_directive++) { yaml_free(tag_directive->handle); yaml_free(tag_directive->prefix); } yaml_free(document->tag_directives.start); memset(document, 0, sizeof(yaml_document_t)); } /** * Get a document node. */ YAML_DECLARE(yaml_node_t *) yaml_document_get_node(yaml_document_t *document, int index) { assert(document); /* Non-NULL document object is expected. */ if (index > 0 && document->nodes.start + index <= document->nodes.top) { return document->nodes.start + index - 1; } return NULL; } /** * Get the root object. */ YAML_DECLARE(yaml_node_t *) yaml_document_get_root_node(yaml_document_t *document) { assert(document); /* Non-NULL document object is expected. */ if (document->nodes.top != document->nodes.start) { return document->nodes.start; } return NULL; } /* * Add a scalar node to a document. */ YAML_DECLARE(int) yaml_document_add_scalar(yaml_document_t *document, yaml_char_t *tag, yaml_char_t *value, int length, yaml_scalar_style_t style) { struct { yaml_error_type_t error; } context; yaml_mark_t mark = { 0, 0, 0 }; yaml_char_t *tag_copy = NULL; yaml_char_t *value_copy = NULL; yaml_node_t node; assert(document); /* Non-NULL document object is expected. */ assert(value); /* Non-NULL value is expected. */ if (!tag) { tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG; } tag_copy = yaml_strdup(tag); if (!tag_copy) goto error; if (length < 0) { length = strlen((char *)value); } value_copy = yaml_malloc(length+1); if (!value_copy) goto error; memcpy(value_copy, value, length); value_copy[length] = '\0'; SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark); if (!PUSH(&context, document->nodes, node)) goto error; return document->nodes.top - document->nodes.start; error: yaml_free(tag_copy); yaml_free(value_copy); return 0; } /* * Add a sequence node to a document. */ YAML_DECLARE(int) yaml_document_add_sequence(yaml_document_t *document, yaml_char_t *tag, yaml_sequence_style_t style) { struct { yaml_error_type_t error; } context; yaml_mark_t mark = { 0, 0, 0 }; yaml_char_t *tag_copy = NULL; struct { yaml_node_item_t *start; yaml_node_item_t *end; yaml_node_item_t *top; } items = { NULL, NULL, NULL }; yaml_node_t node; assert(document); /* Non-NULL document object is expected. */ if (!tag) { tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG; } tag_copy = yaml_strdup(tag); if (!tag_copy) goto error; if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error; SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, style, mark, mark); if (!PUSH(&context, document->nodes, node)) goto error; return document->nodes.top - document->nodes.start; error: STACK_DEL(&context, items); yaml_free(tag_copy); return 0; } /* * Add a mapping node to a document. */ YAML_DECLARE(int) yaml_document_add_mapping(yaml_document_t *document, yaml_char_t *tag, yaml_mapping_style_t style) { struct { yaml_error_type_t error; } context; yaml_mark_t mark = { 0, 0, 0 }; yaml_char_t *tag_copy = NULL; struct { yaml_node_pair_t *start; yaml_node_pair_t *end; yaml_node_pair_t *top; } pairs = { NULL, NULL, NULL }; yaml_node_t node; assert(document); /* Non-NULL document object is expected. */ if (!tag) { tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG; } tag_copy = yaml_strdup(tag); if (!tag_copy) goto error; if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error; MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, style, mark, mark); if (!PUSH(&context, document->nodes, node)) goto error; return document->nodes.top - document->nodes.start; error: STACK_DEL(&context, pairs); yaml_free(tag_copy); return 0; } /* * Append an item to a sequence node. */ YAML_DECLARE(int) yaml_document_append_sequence_item(yaml_document_t *document, int sequence, int item) { struct { yaml_error_type_t error; } context; assert(document); /* Non-NULL document is required. */ assert(sequence > 0 && document->nodes.start + sequence <= document->nodes.top); /* Valid sequence id is required. */ assert(document->nodes.start[sequence-1].type == YAML_SEQUENCE_NODE); /* A sequence node is required. */ assert(item > 0 && document->nodes.start + item <= document->nodes.top); /* Valid item id is required. */ if (!PUSH(&context, document->nodes.start[sequence-1].data.sequence.items, item)) return 0; return 1; } /* * Append a pair of a key and a value to a mapping node. */ YAML_DECLARE(int) yaml_document_append_mapping_pair(yaml_document_t *document, int mapping, int key, int value) { struct { yaml_error_type_t error; } context; yaml_node_pair_t pair; assert(document); /* Non-NULL document is required. */ assert(mapping > 0 && document->nodes.start + mapping <= document->nodes.top); /* Valid mapping id is required. */ assert(document->nodes.start[mapping-1].type == YAML_MAPPING_NODE); /* A mapping node is required. */ assert(key > 0 && document->nodes.start + key <= document->nodes.top); /* Valid key id is required. */ assert(value > 0 && document->nodes.start + value <= document->nodes.top); /* Valid value id is required. */ pair.key = key; pair.value = value; if (!PUSH(&context, document->nodes.start[mapping-1].data.mapping.pairs, pair)) return 0; return 1; } flvmeta-1.2.2/src/libyaml/config.h000066400000000000000000000001731346231570700170210ustar00rootroot00000000000000#define YAML_VERSION_MAJOR 0 #define YAML_VERSION_MINOR 1 #define YAML_VERSION_PATCH 3 #define YAML_VERSION_STRING "0.1.3" flvmeta-1.2.2/src/libyaml/dumper.c000066400000000000000000000234741346231570700170540ustar00rootroot00000000000000 #include "yaml_private.h" /* * API functions. */ YAML_DECLARE(int) yaml_emitter_open(yaml_emitter_t *emitter); YAML_DECLARE(int) yaml_emitter_close(yaml_emitter_t *emitter); YAML_DECLARE(int) yaml_emitter_dump(yaml_emitter_t *emitter, yaml_document_t *document); /* * Clean up functions. */ static void yaml_emitter_delete_document_and_anchors(yaml_emitter_t *emitter); /* * Anchor functions. */ static void yaml_emitter_anchor_node(yaml_emitter_t *emitter, int index); static yaml_char_t * yaml_emitter_generate_anchor(yaml_emitter_t *emitter, int anchor_id); /* * Serialize functions. */ static int yaml_emitter_dump_node(yaml_emitter_t *emitter, int index); static int yaml_emitter_dump_alias(yaml_emitter_t *emitter, yaml_char_t *anchor); static int yaml_emitter_dump_scalar(yaml_emitter_t *emitter, yaml_node_t *node, yaml_char_t *anchor); static int yaml_emitter_dump_sequence(yaml_emitter_t *emitter, yaml_node_t *node, yaml_char_t *anchor); static int yaml_emitter_dump_mapping(yaml_emitter_t *emitter, yaml_node_t *node, yaml_char_t *anchor); /* * Issue a STREAM-START event. */ YAML_DECLARE(int) yaml_emitter_open(yaml_emitter_t *emitter) { yaml_event_t event; yaml_mark_t mark = { 0, 0, 0 }; assert(emitter); /* Non-NULL emitter object is required. */ assert(!emitter->opened); /* Emitter should not be opened yet. */ STREAM_START_EVENT_INIT(event, YAML_ANY_ENCODING, mark, mark); if (!yaml_emitter_emit(emitter, &event)) { return 0; } emitter->opened = 1; return 1; } /* * Issue a STREAM-END event. */ YAML_DECLARE(int) yaml_emitter_close(yaml_emitter_t *emitter) { yaml_event_t event; yaml_mark_t mark = { 0, 0, 0 }; assert(emitter); /* Non-NULL emitter object is required. */ assert(emitter->opened); /* Emitter should be opened. */ if (emitter->closed) return 1; STREAM_END_EVENT_INIT(event, mark, mark); if (!yaml_emitter_emit(emitter, &event)) { return 0; } emitter->closed = 1; return 1; } /* * Dump a YAML document. */ YAML_DECLARE(int) yaml_emitter_dump(yaml_emitter_t *emitter, yaml_document_t *document) { yaml_event_t event; yaml_mark_t mark = { 0, 0, 0 }; assert(emitter); /* Non-NULL emitter object is required. */ assert(document); /* Non-NULL emitter object is expected. */ emitter->document = document; if (!emitter->opened) { if (!yaml_emitter_open(emitter)) goto error; } if (STACK_EMPTY(emitter, document->nodes)) { if (!yaml_emitter_close(emitter)) goto error; yaml_emitter_delete_document_and_anchors(emitter); return 1; } assert(emitter->opened); /* Emitter should be opened. */ emitter->anchors = yaml_malloc(sizeof(*(emitter->anchors)) * (document->nodes.top - document->nodes.start)); if (!emitter->anchors) goto error; memset(emitter->anchors, 0, sizeof(*(emitter->anchors)) * (document->nodes.top - document->nodes.start)); DOCUMENT_START_EVENT_INIT(event, document->version_directive, document->tag_directives.start, document->tag_directives.end, document->start_implicit, mark, mark); if (!yaml_emitter_emit(emitter, &event)) goto error; yaml_emitter_anchor_node(emitter, 1); if (!yaml_emitter_dump_node(emitter, 1)) goto error; DOCUMENT_END_EVENT_INIT(event, document->end_implicit, mark, mark); if (!yaml_emitter_emit(emitter, &event)) goto error; yaml_emitter_delete_document_and_anchors(emitter); return 1; error: yaml_emitter_delete_document_and_anchors(emitter); return 0; } /* * Clean up the emitter object after a document is dumped. */ static void yaml_emitter_delete_document_and_anchors(yaml_emitter_t *emitter) { int index; if (!emitter->anchors) { yaml_document_delete(emitter->document); emitter->document = NULL; return; } for (index = 0; emitter->document->nodes.start + index < emitter->document->nodes.top; index ++) { yaml_node_t node = emitter->document->nodes.start[index]; if (!emitter->anchors[index].serialized) { yaml_free(node.tag); if (node.type == YAML_SCALAR_NODE) { yaml_free(node.data.scalar.value); } } if (node.type == YAML_SEQUENCE_NODE) { STACK_DEL(emitter, node.data.sequence.items); } if (node.type == YAML_MAPPING_NODE) { STACK_DEL(emitter, node.data.mapping.pairs); } } STACK_DEL(emitter, emitter->document->nodes); yaml_free(emitter->anchors); emitter->anchors = NULL; emitter->last_anchor_id = 0; emitter->document = NULL; } /* * Check the references of a node and assign the anchor id if needed. */ static void yaml_emitter_anchor_node(yaml_emitter_t *emitter, int index) { yaml_node_t *node = emitter->document->nodes.start + index - 1; yaml_node_item_t *item; yaml_node_pair_t *pair; emitter->anchors[index-1].references ++; if (emitter->anchors[index-1].references == 1) { switch (node->type) { case YAML_SEQUENCE_NODE: for (item = node->data.sequence.items.start; item < node->data.sequence.items.top; item ++) { yaml_emitter_anchor_node(emitter, *item); } break; case YAML_MAPPING_NODE: for (pair = node->data.mapping.pairs.start; pair < node->data.mapping.pairs.top; pair ++) { yaml_emitter_anchor_node(emitter, pair->key); yaml_emitter_anchor_node(emitter, pair->value); } break; default: break; } } else if (emitter->anchors[index-1].references == 2) { emitter->anchors[index-1].anchor = (++ emitter->last_anchor_id); } } /* * Generate a textual representation for an anchor. */ #define ANCHOR_TEMPLATE "id%03d" #define ANCHOR_TEMPLATE_LENGTH 16 static yaml_char_t * yaml_emitter_generate_anchor(yaml_emitter_t *emitter, int anchor_id) { yaml_char_t *anchor = yaml_malloc(ANCHOR_TEMPLATE_LENGTH); if (!anchor) return NULL; sprintf((char *)anchor, ANCHOR_TEMPLATE, anchor_id); return anchor; } /* * Serialize a node. */ static int yaml_emitter_dump_node(yaml_emitter_t *emitter, int index) { yaml_node_t *node = emitter->document->nodes.start + index - 1; int anchor_id = emitter->anchors[index-1].anchor; yaml_char_t *anchor = NULL; if (anchor_id) { anchor = yaml_emitter_generate_anchor(emitter, anchor_id); if (!anchor) return 0; } if (emitter->anchors[index-1].serialized) { return yaml_emitter_dump_alias(emitter, anchor); } emitter->anchors[index-1].serialized = 1; switch (node->type) { case YAML_SCALAR_NODE: return yaml_emitter_dump_scalar(emitter, node, anchor); case YAML_SEQUENCE_NODE: return yaml_emitter_dump_sequence(emitter, node, anchor); case YAML_MAPPING_NODE: return yaml_emitter_dump_mapping(emitter, node, anchor); default: assert(0); /* Could not happen. */ break; } return 0; /* Could not happen. */ } /* * Serialize an alias. */ static int yaml_emitter_dump_alias(yaml_emitter_t *emitter, yaml_char_t *anchor) { yaml_event_t event; yaml_mark_t mark = { 0, 0, 0 }; ALIAS_EVENT_INIT(event, anchor, mark, mark); return yaml_emitter_emit(emitter, &event); } /* * Serialize a scalar. */ static int yaml_emitter_dump_scalar(yaml_emitter_t *emitter, yaml_node_t *node, yaml_char_t *anchor) { yaml_event_t event; yaml_mark_t mark = { 0, 0, 0 }; int plain_implicit = (strcmp((char *)node->tag, YAML_DEFAULT_SCALAR_TAG) == 0); int quoted_implicit = (strcmp((char *)node->tag, YAML_DEFAULT_SCALAR_TAG) == 0); SCALAR_EVENT_INIT(event, anchor, node->tag, node->data.scalar.value, node->data.scalar.length, plain_implicit, quoted_implicit, node->data.scalar.style, mark, mark); return yaml_emitter_emit(emitter, &event); } /* * Serialize a sequence. */ static int yaml_emitter_dump_sequence(yaml_emitter_t *emitter, yaml_node_t *node, yaml_char_t *anchor) { yaml_event_t event; yaml_mark_t mark = { 0, 0, 0 }; int implicit = (strcmp((char *)node->tag, YAML_DEFAULT_SEQUENCE_TAG) == 0); yaml_node_item_t *item; SEQUENCE_START_EVENT_INIT(event, anchor, node->tag, implicit, node->data.sequence.style, mark, mark); if (!yaml_emitter_emit(emitter, &event)) return 0; for (item = node->data.sequence.items.start; item < node->data.sequence.items.top; item ++) { if (!yaml_emitter_dump_node(emitter, *item)) return 0; } SEQUENCE_END_EVENT_INIT(event, mark, mark); if (!yaml_emitter_emit(emitter, &event)) return 0; return 1; } /* * Serialize a mapping. */ static int yaml_emitter_dump_mapping(yaml_emitter_t *emitter, yaml_node_t *node, yaml_char_t *anchor) { yaml_event_t event; yaml_mark_t mark = { 0, 0, 0 }; int implicit = (strcmp((char *)node->tag, YAML_DEFAULT_MAPPING_TAG) == 0); yaml_node_pair_t *pair; MAPPING_START_EVENT_INIT(event, anchor, node->tag, implicit, node->data.mapping.style, mark, mark); if (!yaml_emitter_emit(emitter, &event)) return 0; for (pair = node->data.mapping.pairs.start; pair < node->data.mapping.pairs.top; pair ++) { if (!yaml_emitter_dump_node(emitter, pair->key)) return 0; if (!yaml_emitter_dump_node(emitter, pair->value)) return 0; } MAPPING_END_EVENT_INIT(event, mark, mark); if (!yaml_emitter_emit(emitter, &event)) return 0; return 1; } flvmeta-1.2.2/src/libyaml/emitter.c000066400000000000000000002057571346231570700172370ustar00rootroot00000000000000 #include "yaml_private.h" /* * Flush the buffer if needed. */ #define FLUSH(emitter) \ ((emitter->buffer.pointer+5 < emitter->buffer.end) \ || yaml_emitter_flush(emitter)) /* * Put a character to the output buffer. */ #define PUT(emitter,value) \ (FLUSH(emitter) \ && (*(emitter->buffer.pointer++) = (yaml_char_t)(value), \ emitter->column ++, \ 1)) /* * Put a line break to the output buffer. */ #define PUT_BREAK(emitter) \ (FLUSH(emitter) \ && ((emitter->line_break == YAML_CR_BREAK ? \ (*(emitter->buffer.pointer++) = (yaml_char_t) '\r') : \ emitter->line_break == YAML_LN_BREAK ? \ (*(emitter->buffer.pointer++) = (yaml_char_t) '\n') : \ emitter->line_break == YAML_CRLN_BREAK ? \ (*(emitter->buffer.pointer++) = (yaml_char_t) '\r', \ *(emitter->buffer.pointer++) = (yaml_char_t) '\n') : 0), \ emitter->column = 0, \ emitter->line ++, \ 1)) /* * Copy a byte from a string into buffer. */ #define WRITE(emitter,string) \ (FLUSH(emitter) \ && (COPY(emitter->buffer,string), \ emitter->column ++, \ 1)) #define WRITEN(emitter,string,n) \ (FLUSH(emitter) \ && (COPYN(emitter->buffer,string,n), \ emitter->column ++, \ 1)) /* * Copy a line break character from a string into buffer. */ #define WRITE_BREAK(emitter,string) \ (FLUSH(emitter) \ && (CHECK(string,'\n') ? \ (PUT_BREAK(emitter), \ string.pointer ++, \ 1) : \ (COPYN(emitter->buffer,string,WIDTH(string)), \ emitter->column = 0, \ emitter->line ++, \ 1))) /* * API functions. */ YAML_DECLARE(int) yaml_emitter_emit(yaml_emitter_t *emitter, yaml_event_t *event); /* * Utility functions. */ static int yaml_emitter_set_emitter_error(yaml_emitter_t *emitter, const char *problem); static int yaml_emitter_need_more_events(yaml_emitter_t *emitter); static int yaml_emitter_append_tag_directive(yaml_emitter_t *emitter, yaml_tag_directive_t value, int allow_duplicates); static int yaml_emitter_increase_indent(yaml_emitter_t *emitter, int flow, int indentless); /* * State functions. */ static int yaml_emitter_state_machine(yaml_emitter_t *emitter, yaml_event_t *event); static int yaml_emitter_emit_stream_start(yaml_emitter_t *emitter, yaml_event_t *event); static int yaml_emitter_emit_document_start(yaml_emitter_t *emitter, yaml_event_t *event, int first); static int yaml_emitter_emit_document_content(yaml_emitter_t *emitter, yaml_event_t *event); static int yaml_emitter_emit_document_end(yaml_emitter_t *emitter, yaml_event_t *event); static int yaml_emitter_emit_flow_sequence_item(yaml_emitter_t *emitter, yaml_event_t *event, int first); static int yaml_emitter_emit_flow_mapping_key(yaml_emitter_t *emitter, yaml_event_t *event, int first); static int yaml_emitter_emit_flow_mapping_value(yaml_emitter_t *emitter, yaml_event_t *event, int simple); static int yaml_emitter_emit_block_sequence_item(yaml_emitter_t *emitter, yaml_event_t *event, int first); static int yaml_emitter_emit_block_mapping_key(yaml_emitter_t *emitter, yaml_event_t *event, int first); static int yaml_emitter_emit_block_mapping_value(yaml_emitter_t *emitter, yaml_event_t *event, int simple); static int yaml_emitter_emit_node(yaml_emitter_t *emitter, yaml_event_t *event, int root, int sequence, int mapping, int simple_key); static int yaml_emitter_emit_alias(yaml_emitter_t *emitter, yaml_event_t *event); static int yaml_emitter_emit_scalar(yaml_emitter_t *emitter, yaml_event_t *event); static int yaml_emitter_emit_sequence_start(yaml_emitter_t *emitter, yaml_event_t *event); static int yaml_emitter_emit_mapping_start(yaml_emitter_t *emitter, yaml_event_t *event); /* * Checkers. */ static int yaml_emitter_check_empty_document(yaml_emitter_t *emitter); static int yaml_emitter_check_empty_sequence(yaml_emitter_t *emitter); static int yaml_emitter_check_empty_mapping(yaml_emitter_t *emitter); static int yaml_emitter_check_simple_key(yaml_emitter_t *emitter); static int yaml_emitter_select_scalar_style(yaml_emitter_t *emitter, yaml_event_t *event); /* * Processors. */ static int yaml_emitter_process_anchor(yaml_emitter_t *emitter); static int yaml_emitter_process_tag(yaml_emitter_t *emitter); static int yaml_emitter_process_scalar(yaml_emitter_t *emitter); /* * Analyzers. */ static int yaml_emitter_analyze_version_directive(yaml_emitter_t *emitter, yaml_version_directive_t version_directive); static int yaml_emitter_analyze_tag_directive(yaml_emitter_t *emitter, yaml_tag_directive_t tag_directive); static int yaml_emitter_analyze_anchor(yaml_emitter_t *emitter, yaml_char_t *anchor, int alias); static int yaml_emitter_analyze_tag(yaml_emitter_t *emitter, yaml_char_t *tag); static int yaml_emitter_analyze_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length); static int yaml_emitter_analyze_event(yaml_emitter_t *emitter, yaml_event_t *event); /* * Writers. */ static int yaml_emitter_write_bom(yaml_emitter_t *emitter); static int yaml_emitter_write_indent(yaml_emitter_t *emitter); static int yaml_emitter_write_indicator(yaml_emitter_t *emitter, char *indicator, int need_whitespace, int is_whitespace, int is_indention); static int yaml_emitter_write_anchor(yaml_emitter_t *emitter, yaml_char_t *value, size_t length); static int yaml_emitter_write_tag_handle(yaml_emitter_t *emitter, yaml_char_t *value, size_t length); static int yaml_emitter_write_tag_content(yaml_emitter_t *emitter, yaml_char_t *value, size_t length, int need_whitespace); static int yaml_emitter_write_plain_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length, int allow_breaks); static int yaml_emitter_write_single_quoted_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length, int allow_breaks); static int yaml_emitter_write_double_quoted_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length, int allow_breaks); static int yaml_emitter_write_block_scalar_hints(yaml_emitter_t *emitter, yaml_string_t string); static int yaml_emitter_write_literal_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length); static int yaml_emitter_write_folded_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length); /* * Set an emitter error and return 0. */ static int yaml_emitter_set_emitter_error(yaml_emitter_t *emitter, const char *problem) { emitter->error = YAML_EMITTER_ERROR; emitter->problem = problem; return 0; } /* * Emit an event. */ YAML_DECLARE(int) yaml_emitter_emit(yaml_emitter_t *emitter, yaml_event_t *event) { if (!ENQUEUE(emitter, emitter->events, *event)) { yaml_event_delete(event); return 0; } while (!yaml_emitter_need_more_events(emitter)) { if (!yaml_emitter_analyze_event(emitter, emitter->events.head)) return 0; if (!yaml_emitter_state_machine(emitter, emitter->events.head)) return 0; yaml_event_delete(&DEQUEUE(emitter, emitter->events)); } return 1; } /* * Check if we need to accumulate more events before emitting. * * We accumulate extra * - 1 event for DOCUMENT-START * - 2 events for SEQUENCE-START * - 3 events for MAPPING-START */ static int yaml_emitter_need_more_events(yaml_emitter_t *emitter) { int level = 0; int accumulate = 0; yaml_event_t *event; if (QUEUE_EMPTY(emitter, emitter->events)) return 1; switch (emitter->events.head->type) { case YAML_DOCUMENT_START_EVENT: accumulate = 1; break; case YAML_SEQUENCE_START_EVENT: accumulate = 2; break; case YAML_MAPPING_START_EVENT: accumulate = 3; break; default: return 0; } if (emitter->events.tail - emitter->events.head > accumulate) return 0; for (event = emitter->events.head; event != emitter->events.tail; event ++) { switch (event->type) { case YAML_STREAM_START_EVENT: case YAML_DOCUMENT_START_EVENT: case YAML_SEQUENCE_START_EVENT: case YAML_MAPPING_START_EVENT: level += 1; break; case YAML_STREAM_END_EVENT: case YAML_DOCUMENT_END_EVENT: case YAML_SEQUENCE_END_EVENT: case YAML_MAPPING_END_EVENT: level -= 1; break; default: break; } if (!level) return 0; } return 1; } /* * Append a directive to the directives stack. */ static int yaml_emitter_append_tag_directive(yaml_emitter_t *emitter, yaml_tag_directive_t value, int allow_duplicates) { yaml_tag_directive_t *tag_directive; yaml_tag_directive_t copy = { NULL, NULL }; for (tag_directive = emitter->tag_directives.start; tag_directive != emitter->tag_directives.top; tag_directive ++) { if (strcmp((char *)value.handle, (char *)tag_directive->handle) == 0) { if (allow_duplicates) return 1; return yaml_emitter_set_emitter_error(emitter, "duplicate %TAG directive"); } } copy.handle = yaml_strdup(value.handle); copy.prefix = yaml_strdup(value.prefix); if (!copy.handle || !copy.prefix) { emitter->error = YAML_MEMORY_ERROR; goto error; } if (!PUSH(emitter, emitter->tag_directives, copy)) goto error; return 1; error: yaml_free(copy.handle); yaml_free(copy.prefix); return 0; } /* * Increase the indentation level. */ static int yaml_emitter_increase_indent(yaml_emitter_t *emitter, int flow, int indentless) { if (!PUSH(emitter, emitter->indents, emitter->indent)) return 0; if (emitter->indent < 0) { emitter->indent = flow ? emitter->best_indent : 0; } else if (!indentless) { emitter->indent += emitter->best_indent; } return 1; } /* * State dispatcher. */ static int yaml_emitter_state_machine(yaml_emitter_t *emitter, yaml_event_t *event) { switch (emitter->state) { case YAML_EMIT_STREAM_START_STATE: return yaml_emitter_emit_stream_start(emitter, event); case YAML_EMIT_FIRST_DOCUMENT_START_STATE: return yaml_emitter_emit_document_start(emitter, event, 1); case YAML_EMIT_DOCUMENT_START_STATE: return yaml_emitter_emit_document_start(emitter, event, 0); case YAML_EMIT_DOCUMENT_CONTENT_STATE: return yaml_emitter_emit_document_content(emitter, event); case YAML_EMIT_DOCUMENT_END_STATE: return yaml_emitter_emit_document_end(emitter, event); case YAML_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: return yaml_emitter_emit_flow_sequence_item(emitter, event, 1); case YAML_EMIT_FLOW_SEQUENCE_ITEM_STATE: return yaml_emitter_emit_flow_sequence_item(emitter, event, 0); case YAML_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: return yaml_emitter_emit_flow_mapping_key(emitter, event, 1); case YAML_EMIT_FLOW_MAPPING_KEY_STATE: return yaml_emitter_emit_flow_mapping_key(emitter, event, 0); case YAML_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: return yaml_emitter_emit_flow_mapping_value(emitter, event, 1); case YAML_EMIT_FLOW_MAPPING_VALUE_STATE: return yaml_emitter_emit_flow_mapping_value(emitter, event, 0); case YAML_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: return yaml_emitter_emit_block_sequence_item(emitter, event, 1); case YAML_EMIT_BLOCK_SEQUENCE_ITEM_STATE: return yaml_emitter_emit_block_sequence_item(emitter, event, 0); case YAML_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: return yaml_emitter_emit_block_mapping_key(emitter, event, 1); case YAML_EMIT_BLOCK_MAPPING_KEY_STATE: return yaml_emitter_emit_block_mapping_key(emitter, event, 0); case YAML_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: return yaml_emitter_emit_block_mapping_value(emitter, event, 1); case YAML_EMIT_BLOCK_MAPPING_VALUE_STATE: return yaml_emitter_emit_block_mapping_value(emitter, event, 0); case YAML_EMIT_END_STATE: return yaml_emitter_set_emitter_error(emitter, "expected nothing after STREAM-END"); default: assert(1); /* Invalid state. */ } return 0; } /* * Expect STREAM-START. */ static int yaml_emitter_emit_stream_start(yaml_emitter_t *emitter, yaml_event_t *event) { if (event->type == YAML_STREAM_START_EVENT) { if (!emitter->encoding) { emitter->encoding = event->data.stream_start.encoding; } if (!emitter->encoding) { emitter->encoding = YAML_UTF8_ENCODING; } if (emitter->best_indent < 2 || emitter->best_indent > 9) { emitter->best_indent = 2; } if (emitter->best_width >= 0 && emitter->best_width <= emitter->best_indent*2) { emitter->best_width = 80; } if (emitter->best_width < 0) { emitter->best_width = INT_MAX; } if (!emitter->line_break) { emitter->line_break = YAML_LN_BREAK; } emitter->indent = -1; emitter->line = 0; emitter->column = 0; emitter->whitespace = 1; emitter->indention = 1; if (emitter->encoding != YAML_UTF8_ENCODING) { if (!yaml_emitter_write_bom(emitter)) return 0; } emitter->state = YAML_EMIT_FIRST_DOCUMENT_START_STATE; return 1; } return yaml_emitter_set_emitter_error(emitter, "expected STREAM-START"); } /* * Expect DOCUMENT-START or STREAM-END. */ static int yaml_emitter_emit_document_start(yaml_emitter_t *emitter, yaml_event_t *event, int first) { if (event->type == YAML_DOCUMENT_START_EVENT) { yaml_tag_directive_t default_tag_directives[] = { {(yaml_char_t *)"!", (yaml_char_t *)"!"}, {(yaml_char_t *)"!!", (yaml_char_t *)"tag:yaml.org,2002:"}, {NULL, NULL} }; yaml_tag_directive_t *tag_directive; int implicit; if (event->data.document_start.version_directive) { if (!yaml_emitter_analyze_version_directive(emitter, *event->data.document_start.version_directive)) return 0; } for (tag_directive = event->data.document_start.tag_directives.start; tag_directive != event->data.document_start.tag_directives.end; tag_directive ++) { if (!yaml_emitter_analyze_tag_directive(emitter, *tag_directive)) return 0; if (!yaml_emitter_append_tag_directive(emitter, *tag_directive, 0)) return 0; } for (tag_directive = default_tag_directives; tag_directive->handle; tag_directive ++) { if (!yaml_emitter_append_tag_directive(emitter, *tag_directive, 1)) return 0; } implicit = event->data.document_start.implicit; if (!first || emitter->canonical) { implicit = 0; } if ((event->data.document_start.version_directive || (event->data.document_start.tag_directives.start != event->data.document_start.tag_directives.end)) && emitter->open_ended) { if (!yaml_emitter_write_indicator(emitter, "...", 1, 0, 0)) return 0; if (!yaml_emitter_write_indent(emitter)) return 0; } if (event->data.document_start.version_directive) { implicit = 0; if (!yaml_emitter_write_indicator(emitter, "%YAML", 1, 0, 0)) return 0; if (!yaml_emitter_write_indicator(emitter, "1.1", 1, 0, 0)) return 0; if (!yaml_emitter_write_indent(emitter)) return 0; } if (event->data.document_start.tag_directives.start != event->data.document_start.tag_directives.end) { implicit = 0; for (tag_directive = event->data.document_start.tag_directives.start; tag_directive != event->data.document_start.tag_directives.end; tag_directive ++) { if (!yaml_emitter_write_indicator(emitter, "%TAG", 1, 0, 0)) return 0; if (!yaml_emitter_write_tag_handle(emitter, tag_directive->handle, strlen((char *)tag_directive->handle))) return 0; if (!yaml_emitter_write_tag_content(emitter, tag_directive->prefix, strlen((char *)tag_directive->prefix), 1)) return 0; if (!yaml_emitter_write_indent(emitter)) return 0; } } if (yaml_emitter_check_empty_document(emitter)) { implicit = 0; } if (!implicit) { if (!yaml_emitter_write_indent(emitter)) return 0; if (!yaml_emitter_write_indicator(emitter, "---", 1, 0, 0)) return 0; if (emitter->canonical) { if (!yaml_emitter_write_indent(emitter)) return 0; } } emitter->state = YAML_EMIT_DOCUMENT_CONTENT_STATE; return 1; } else if (event->type == YAML_STREAM_END_EVENT) { if (emitter->open_ended) { if (!yaml_emitter_write_indicator(emitter, "...", 1, 0, 0)) return 0; if (!yaml_emitter_write_indent(emitter)) return 0; } if (!yaml_emitter_flush(emitter)) return 0; emitter->state = YAML_EMIT_END_STATE; return 1; } return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-START or STREAM-END"); } /* * Expect the root node. */ static int yaml_emitter_emit_document_content(yaml_emitter_t *emitter, yaml_event_t *event) { if (!PUSH(emitter, emitter->states, YAML_EMIT_DOCUMENT_END_STATE)) return 0; return yaml_emitter_emit_node(emitter, event, 1, 0, 0, 0); } /* * Expect DOCUMENT-END. */ static int yaml_emitter_emit_document_end(yaml_emitter_t *emitter, yaml_event_t *event) { if (event->type == YAML_DOCUMENT_END_EVENT) { if (!yaml_emitter_write_indent(emitter)) return 0; if (!event->data.document_end.implicit) { if (!yaml_emitter_write_indicator(emitter, "...", 1, 0, 0)) return 0; if (!yaml_emitter_write_indent(emitter)) return 0; } if (!yaml_emitter_flush(emitter)) return 0; emitter->state = YAML_EMIT_DOCUMENT_START_STATE; while (!STACK_EMPTY(emitter, emitter->tag_directives)) { yaml_tag_directive_t tag_directive = POP(emitter, emitter->tag_directives); yaml_free(tag_directive.handle); yaml_free(tag_directive.prefix); } return 1; } return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-END"); } /* * * Expect a flow item node. */ static int yaml_emitter_emit_flow_sequence_item(yaml_emitter_t *emitter, yaml_event_t *event, int first) { if (first) { if (!yaml_emitter_write_indicator(emitter, "[", 1, 1, 0)) return 0; if (!yaml_emitter_increase_indent(emitter, 1, 0)) return 0; emitter->flow_level ++; } if (event->type == YAML_SEQUENCE_END_EVENT) { emitter->flow_level --; emitter->indent = POP(emitter, emitter->indents); if (emitter->canonical && !first) { if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) return 0; if (!yaml_emitter_write_indent(emitter)) return 0; } if (!yaml_emitter_write_indicator(emitter, "]", 0, 0, 0)) return 0; emitter->state = POP(emitter, emitter->states); return 1; } if (!first) { if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) return 0; } if (emitter->canonical || emitter->column > emitter->best_width) { if (!yaml_emitter_write_indent(emitter)) return 0; } if (!PUSH(emitter, emitter->states, YAML_EMIT_FLOW_SEQUENCE_ITEM_STATE)) return 0; return yaml_emitter_emit_node(emitter, event, 0, 1, 0, 0); } /* * Expect a flow key node. */ static int yaml_emitter_emit_flow_mapping_key(yaml_emitter_t *emitter, yaml_event_t *event, int first) { if (first) { if (!yaml_emitter_write_indicator(emitter, "{", 1, 1, 0)) return 0; if (!yaml_emitter_increase_indent(emitter, 1, 0)) return 0; emitter->flow_level ++; } if (event->type == YAML_MAPPING_END_EVENT) { emitter->flow_level --; emitter->indent = POP(emitter, emitter->indents); if (emitter->canonical && !first) { if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) return 0; if (!yaml_emitter_write_indent(emitter)) return 0; } if (!yaml_emitter_write_indicator(emitter, "}", 0, 0, 0)) return 0; emitter->state = POP(emitter, emitter->states); return 1; } if (!first) { if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) return 0; } if (emitter->canonical || emitter->column > emitter->best_width) { if (!yaml_emitter_write_indent(emitter)) return 0; } if (!emitter->canonical && yaml_emitter_check_simple_key(emitter)) { if (!PUSH(emitter, emitter->states, YAML_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE)) return 0; return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 1); } else { if (!yaml_emitter_write_indicator(emitter, "?", 1, 0, 0)) return 0; if (!PUSH(emitter, emitter->states, YAML_EMIT_FLOW_MAPPING_VALUE_STATE)) return 0; return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); } } /* * Expect a flow value node. */ static int yaml_emitter_emit_flow_mapping_value(yaml_emitter_t *emitter, yaml_event_t *event, int simple) { if (simple) { if (!yaml_emitter_write_indicator(emitter, ":", 0, 0, 0)) return 0; } else { if (emitter->canonical || emitter->column > emitter->best_width) { if (!yaml_emitter_write_indent(emitter)) return 0; } if (!yaml_emitter_write_indicator(emitter, ":", 1, 0, 0)) return 0; } if (!PUSH(emitter, emitter->states, YAML_EMIT_FLOW_MAPPING_KEY_STATE)) return 0; return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); } /* * Expect a block item node. */ static int yaml_emitter_emit_block_sequence_item(yaml_emitter_t *emitter, yaml_event_t *event, int first) { if (first) { if (!yaml_emitter_increase_indent(emitter, 0, (emitter->mapping_context && !emitter->indention))) return 0; } if (event->type == YAML_SEQUENCE_END_EVENT) { emitter->indent = POP(emitter, emitter->indents); emitter->state = POP(emitter, emitter->states); return 1; } if (!yaml_emitter_write_indent(emitter)) return 0; if (!yaml_emitter_write_indicator(emitter, "-", 1, 0, 1)) return 0; if (!PUSH(emitter, emitter->states, YAML_EMIT_BLOCK_SEQUENCE_ITEM_STATE)) return 0; return yaml_emitter_emit_node(emitter, event, 0, 1, 0, 0); } /* * Expect a block key node. */ static int yaml_emitter_emit_block_mapping_key(yaml_emitter_t *emitter, yaml_event_t *event, int first) { if (first) { if (!yaml_emitter_increase_indent(emitter, 0, 0)) return 0; } if (event->type == YAML_MAPPING_END_EVENT) { emitter->indent = POP(emitter, emitter->indents); emitter->state = POP(emitter, emitter->states); return 1; } if (!yaml_emitter_write_indent(emitter)) return 0; if (yaml_emitter_check_simple_key(emitter)) { if (!PUSH(emitter, emitter->states, YAML_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE)) return 0; return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 1); } else { if (!yaml_emitter_write_indicator(emitter, "?", 1, 0, 1)) return 0; if (!PUSH(emitter, emitter->states, YAML_EMIT_BLOCK_MAPPING_VALUE_STATE)) return 0; return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); } } /* * Expect a block value node. */ static int yaml_emitter_emit_block_mapping_value(yaml_emitter_t *emitter, yaml_event_t *event, int simple) { if (simple) { if (!yaml_emitter_write_indicator(emitter, ":", 0, 0, 0)) return 0; } else { if (!yaml_emitter_write_indent(emitter)) return 0; if (!yaml_emitter_write_indicator(emitter, ":", 1, 0, 1)) return 0; } if (!PUSH(emitter, emitter->states, YAML_EMIT_BLOCK_MAPPING_KEY_STATE)) return 0; return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); } /* * Expect a node. */ static int yaml_emitter_emit_node(yaml_emitter_t *emitter, yaml_event_t *event, int root, int sequence, int mapping, int simple_key) { emitter->root_context = root; emitter->sequence_context = sequence; emitter->mapping_context = mapping; emitter->simple_key_context = simple_key; switch (event->type) { case YAML_ALIAS_EVENT: return yaml_emitter_emit_alias(emitter, event); case YAML_SCALAR_EVENT: return yaml_emitter_emit_scalar(emitter, event); case YAML_SEQUENCE_START_EVENT: return yaml_emitter_emit_sequence_start(emitter, event); case YAML_MAPPING_START_EVENT: return yaml_emitter_emit_mapping_start(emitter, event); default: return yaml_emitter_set_emitter_error(emitter, "expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS"); } return 0; } /* * Expect ALIAS. */ static int yaml_emitter_emit_alias(yaml_emitter_t *emitter, yaml_event_t *event) { if (!yaml_emitter_process_anchor(emitter)) return 0; emitter->state = POP(emitter, emitter->states); return 1; } /* * Expect SCALAR. */ static int yaml_emitter_emit_scalar(yaml_emitter_t *emitter, yaml_event_t *event) { if (!yaml_emitter_select_scalar_style(emitter, event)) return 0; if (!yaml_emitter_process_anchor(emitter)) return 0; if (!yaml_emitter_process_tag(emitter)) return 0; if (!yaml_emitter_increase_indent(emitter, 1, 0)) return 0; if (!yaml_emitter_process_scalar(emitter)) return 0; emitter->indent = POP(emitter, emitter->indents); emitter->state = POP(emitter, emitter->states); return 1; } /* * Expect SEQUENCE-START. */ static int yaml_emitter_emit_sequence_start(yaml_emitter_t *emitter, yaml_event_t *event) { if (!yaml_emitter_process_anchor(emitter)) return 0; if (!yaml_emitter_process_tag(emitter)) return 0; if (emitter->flow_level || emitter->canonical || event->data.sequence_start.style == YAML_FLOW_SEQUENCE_STYLE || yaml_emitter_check_empty_sequence(emitter)) { emitter->state = YAML_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE; } else { emitter->state = YAML_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE; } return 1; } /* * Expect MAPPING-START. */ static int yaml_emitter_emit_mapping_start(yaml_emitter_t *emitter, yaml_event_t *event) { if (!yaml_emitter_process_anchor(emitter)) return 0; if (!yaml_emitter_process_tag(emitter)) return 0; if (emitter->flow_level || emitter->canonical || event->data.mapping_start.style == YAML_FLOW_MAPPING_STYLE || yaml_emitter_check_empty_mapping(emitter)) { emitter->state = YAML_EMIT_FLOW_MAPPING_FIRST_KEY_STATE; } else { emitter->state = YAML_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE; } return 1; } /* * Check if the document content is an empty scalar. */ static int yaml_emitter_check_empty_document(yaml_emitter_t *emitter) { return 0; } /* * Check if the next events represent an empty sequence. */ static int yaml_emitter_check_empty_sequence(yaml_emitter_t *emitter) { if (emitter->events.tail - emitter->events.head < 2) return 0; return (emitter->events.head[0].type == YAML_SEQUENCE_START_EVENT && emitter->events.head[1].type == YAML_SEQUENCE_END_EVENT); } /* * Check if the next events represent an empty mapping. */ static int yaml_emitter_check_empty_mapping(yaml_emitter_t *emitter) { if (emitter->events.tail - emitter->events.head < 2) return 0; return (emitter->events.head[0].type == YAML_MAPPING_START_EVENT && emitter->events.head[1].type == YAML_MAPPING_END_EVENT); } /* * Check if the next node can be expressed as a simple key. */ static int yaml_emitter_check_simple_key(yaml_emitter_t *emitter) { yaml_event_t *event = emitter->events.head; size_t length = 0; switch (event->type) { case YAML_ALIAS_EVENT: length += emitter->anchor_data.anchor_length; break; case YAML_SCALAR_EVENT: if (emitter->scalar_data.multiline) return 0; length += emitter->anchor_data.anchor_length + emitter->tag_data.handle_length + emitter->tag_data.suffix_length + emitter->scalar_data.length; break; case YAML_SEQUENCE_START_EVENT: if (!yaml_emitter_check_empty_sequence(emitter)) return 0; length += emitter->anchor_data.anchor_length + emitter->tag_data.handle_length + emitter->tag_data.suffix_length; break; case YAML_MAPPING_START_EVENT: if (!yaml_emitter_check_empty_sequence(emitter)) return 0; length += emitter->anchor_data.anchor_length + emitter->tag_data.handle_length + emitter->tag_data.suffix_length; break; default: return 0; } if (length > 128) return 0; return 1; } /* * Determine an acceptable scalar style. */ static int yaml_emitter_select_scalar_style(yaml_emitter_t *emitter, yaml_event_t *event) { yaml_scalar_style_t style = event->data.scalar.style; int no_tag = (!emitter->tag_data.handle && !emitter->tag_data.suffix); if (no_tag && !event->data.scalar.plain_implicit && !event->data.scalar.quoted_implicit) { return yaml_emitter_set_emitter_error(emitter, "neither tag nor implicit flags are specified"); } if (style == YAML_ANY_SCALAR_STYLE) style = YAML_PLAIN_SCALAR_STYLE; if (emitter->canonical) style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; if (emitter->simple_key_context && emitter->scalar_data.multiline) style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; if (style == YAML_PLAIN_SCALAR_STYLE) { if ((emitter->flow_level && !emitter->scalar_data.flow_plain_allowed) || (!emitter->flow_level && !emitter->scalar_data.block_plain_allowed)) style = YAML_SINGLE_QUOTED_SCALAR_STYLE; if (!emitter->scalar_data.length && (emitter->flow_level || emitter->simple_key_context)) style = YAML_SINGLE_QUOTED_SCALAR_STYLE; if (no_tag && !event->data.scalar.plain_implicit) style = YAML_SINGLE_QUOTED_SCALAR_STYLE; } if (style == YAML_SINGLE_QUOTED_SCALAR_STYLE) { if (!emitter->scalar_data.single_quoted_allowed) style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; } if (style == YAML_LITERAL_SCALAR_STYLE || style == YAML_FOLDED_SCALAR_STYLE) { if (!emitter->scalar_data.block_allowed || emitter->flow_level || emitter->simple_key_context) style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; } if (no_tag && !event->data.scalar.quoted_implicit && style != YAML_PLAIN_SCALAR_STYLE) { emitter->tag_data.handle = (yaml_char_t *)"!"; emitter->tag_data.handle_length = 1; } emitter->scalar_data.style = style; return 1; } /* * Write an achor. */ static int yaml_emitter_process_anchor(yaml_emitter_t *emitter) { if (!emitter->anchor_data.anchor) return 1; if (!yaml_emitter_write_indicator(emitter, (emitter->anchor_data.alias ? "*" : "&"), 1, 0, 0)) return 0; return yaml_emitter_write_anchor(emitter, emitter->anchor_data.anchor, emitter->anchor_data.anchor_length); } /* * Write a tag. */ static int yaml_emitter_process_tag(yaml_emitter_t *emitter) { if (!emitter->tag_data.handle && !emitter->tag_data.suffix) return 1; if (emitter->tag_data.handle) { if (!yaml_emitter_write_tag_handle(emitter, emitter->tag_data.handle, emitter->tag_data.handle_length)) return 0; if (emitter->tag_data.suffix) { if (!yaml_emitter_write_tag_content(emitter, emitter->tag_data.suffix, emitter->tag_data.suffix_length, 0)) return 0; } } else { if (!yaml_emitter_write_indicator(emitter, "!<", 1, 0, 0)) return 0; if (!yaml_emitter_write_tag_content(emitter, emitter->tag_data.suffix, emitter->tag_data.suffix_length, 0)) return 0; if (!yaml_emitter_write_indicator(emitter, ">", 0, 0, 0)) return 0; } return 1; } /* * Write a scalar. */ static int yaml_emitter_process_scalar(yaml_emitter_t *emitter) { switch (emitter->scalar_data.style) { case YAML_PLAIN_SCALAR_STYLE: return yaml_emitter_write_plain_scalar(emitter, emitter->scalar_data.value, emitter->scalar_data.length, !emitter->simple_key_context); case YAML_SINGLE_QUOTED_SCALAR_STYLE: return yaml_emitter_write_single_quoted_scalar(emitter, emitter->scalar_data.value, emitter->scalar_data.length, !emitter->simple_key_context); case YAML_DOUBLE_QUOTED_SCALAR_STYLE: return yaml_emitter_write_double_quoted_scalar(emitter, emitter->scalar_data.value, emitter->scalar_data.length, !emitter->simple_key_context); case YAML_LITERAL_SCALAR_STYLE: return yaml_emitter_write_literal_scalar(emitter, emitter->scalar_data.value, emitter->scalar_data.length); case YAML_FOLDED_SCALAR_STYLE: return yaml_emitter_write_folded_scalar(emitter, emitter->scalar_data.value, emitter->scalar_data.length); default: assert(1); /* Impossible. */ } return 0; } /* * Check if a %YAML directive is valid. */ static int yaml_emitter_analyze_version_directive(yaml_emitter_t *emitter, yaml_version_directive_t version_directive) { if (version_directive.major != 1 || version_directive.minor != 1) { return yaml_emitter_set_emitter_error(emitter, "incompatible %YAML directive"); } return 1; } /* * Check if a %TAG directive is valid. */ static int yaml_emitter_analyze_tag_directive(yaml_emitter_t *emitter, yaml_tag_directive_t tag_directive) { yaml_string_t handle; yaml_string_t prefix; size_t handle_length; size_t prefix_length; handle_length = strlen((char *)tag_directive.handle); prefix_length = strlen((char *)tag_directive.prefix); STRING_ASSIGN(handle, tag_directive.handle, handle_length); STRING_ASSIGN(prefix, tag_directive.prefix, prefix_length); if (handle.start == handle.end) { return yaml_emitter_set_emitter_error(emitter, "tag handle must not be empty"); } if (handle.start[0] != '!') { return yaml_emitter_set_emitter_error(emitter, "tag handle must start with '!'"); } if (handle.end[-1] != '!') { return yaml_emitter_set_emitter_error(emitter, "tag handle must end with '!'"); } handle.pointer ++; while (handle.pointer < handle.end-1) { if (!IS_ALPHA(handle)) { return yaml_emitter_set_emitter_error(emitter, "tag handle must contain alphanumerical characters only"); } MOVE(handle); } if (prefix.start == prefix.end) { return yaml_emitter_set_emitter_error(emitter, "tag prefix must not be empty"); } return 1; } /* * Check if an anchor is valid. */ static int yaml_emitter_analyze_anchor(yaml_emitter_t *emitter, yaml_char_t *anchor, int alias) { size_t anchor_length; yaml_string_t string; anchor_length = strlen((char *)anchor); STRING_ASSIGN(string, anchor, anchor_length); if (string.start == string.end) { return yaml_emitter_set_emitter_error(emitter, alias ? "alias value must not be empty" : "anchor value must not be empty"); } while (string.pointer != string.end) { if (!IS_ALPHA(string)) { return yaml_emitter_set_emitter_error(emitter, alias ? "alias value must contain alphanumerical characters only" : "anchor value must contain alphanumerical characters only"); } MOVE(string); } emitter->anchor_data.anchor = string.start; emitter->anchor_data.anchor_length = string.end - string.start; emitter->anchor_data.alias = alias; return 1; } /* * Check if a tag is valid. */ static int yaml_emitter_analyze_tag(yaml_emitter_t *emitter, yaml_char_t *tag) { size_t tag_length; yaml_string_t string; yaml_tag_directive_t *tag_directive; tag_length = strlen((char *)tag); STRING_ASSIGN(string, tag, tag_length); if (string.start == string.end) { return yaml_emitter_set_emitter_error(emitter, "tag value must not be empty"); } for (tag_directive = emitter->tag_directives.start; tag_directive != emitter->tag_directives.top; tag_directive ++) { size_t prefix_length = strlen((char *)tag_directive->prefix); if (prefix_length < (size_t)(string.end - string.start) && strncmp((char *)tag_directive->prefix, (char *)string.start, prefix_length) == 0) { emitter->tag_data.handle = tag_directive->handle; emitter->tag_data.handle_length = strlen((char *)tag_directive->handle); emitter->tag_data.suffix = string.start + prefix_length; emitter->tag_data.suffix_length = (string.end - string.start) - prefix_length; return 1; } } emitter->tag_data.suffix = string.start; emitter->tag_data.suffix_length = string.end - string.start; return 1; } /** Return the number of bytes in the next UTF-8 character. Returns 0 for any error, includinge incorrect bytes, not enough bytes before the end pointer, and overlong encodings. If 0 is returned, pointer[0] will always have the high bit set and will thus never match any ASCII character. The encodings of surrogate halves are allowed! Otherwise it is not possible to losslessly encode invalid UTF-16 into UTF-8. (There is a vocal contingent trying to sabotage UTF-8 by declaring surrogate half encodings invalid. Anybody such claim should be investigated carefully: if that user's code does not also reject invalid UTF-16, then they are being hypocrites and can be ignored.) */ static unsigned int utf8_width(yaml_char_t* pointer, yaml_char_t* end) { unsigned char octet = pointer[0]; if (octet < 0x80) { /* 1-byte character */ return 1; } else if (octet < 0xC2) { /* continuation byte or overlong 2-byte encoding */ return 0; } else if (octet < 0xE0) { /* 2-byte character */ if (end-pointer < 2) return 0; if ((pointer[1] & 0xC0) != 0x80) return 0; return 2; } else if (octet < 0xF0) { /* 3-byte character */ if (end-pointer < 3) return 0; if (octet == 0xE0 && pointer[1] < 0xA0) return 0; /* overlong */ if ((pointer[1] & 0xC0) != 0x80) return 0; if ((pointer[2] & 0xC0) != 0x80) return 0; return 3; } else if (octet < 0xF5) { /* 4-byte character */ if (end-pointer < 4) return 0; if (octet == 0xF0 && pointer[1] < 0x90) return 0; /* overlong */ if (octet == 0xF4 && pointer[1] > 0x8F) return 0; /* > 0x10FFFF */ if ((pointer[1] & 0xC0) != 0x80) return 0; if ((pointer[2] & 0xC0) != 0x80) return 0; if ((pointer[3] & 0xC0) != 0x80) return 0; return 4; } else { /* can never appear in UTF-8 */ return 0; } } /* * Check if a scalar is valid. */ static int yaml_emitter_analyze_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length) { yaml_string_t string; int block_indicators = 0; int flow_indicators = 0; int line_breaks = 0; int special_characters = 0; int leading_space = 0; int leading_break = 0; int trailing_space = 0; int trailing_break = 0; int break_space = 0; int space_break = 0; int preceeded_by_whitespace = 0; int previous_space = 0; int previous_break = 0; STRING_ASSIGN(string, value, length); emitter->scalar_data.value = value; emitter->scalar_data.length = length; if (string.start == string.end) { emitter->scalar_data.multiline = 0; emitter->scalar_data.flow_plain_allowed = 0; emitter->scalar_data.block_plain_allowed = 1; emitter->scalar_data.single_quoted_allowed = 1; emitter->scalar_data.block_allowed = 0; return 1; } if ((CHECK_AT(string, '-', 0) && CHECK_AT(string, '-', 1) && CHECK_AT(string, '-', 2)) || (CHECK_AT(string, '.', 0) && CHECK_AT(string, '.', 1) && CHECK_AT(string, '.', 2))) { block_indicators = 1; flow_indicators = 1; } preceeded_by_whitespace = 1; while (string.pointer != string.end) { unsigned int width = utf8_width(string.pointer, string.end); if (!width) { special_characters = 1; string.pointer++; continue; } if (string.start == string.pointer) { if (CHECK(string, '#') || CHECK(string, ',') || CHECK(string, '[') || CHECK(string, ']') || CHECK(string, '{') || CHECK(string, '}') || CHECK(string, '&') || CHECK(string, '*') || CHECK(string, '!') || CHECK(string, '|') || CHECK(string, '>') || CHECK(string, '\'') || CHECK(string, '"') || CHECK(string, '%') || CHECK(string, '@') || CHECK(string, '`')) { flow_indicators = 1; block_indicators = 1; } if (CHECK(string, '?') || CHECK(string, ':')) { flow_indicators = 1; if (IS_BLANKZ_AT(string, 1)) { block_indicators = 1; } } if (CHECK(string, '-') && IS_BLANKZ_AT(string, 1)) { flow_indicators = 1; block_indicators = 1; } } else { if (CHECK(string, ',') || CHECK(string, '?') || CHECK(string, '[') || CHECK(string, ']') || CHECK(string, '{') || CHECK(string, '}')) { flow_indicators = 1; } if (CHECK(string, ':')) { flow_indicators = 1; if (IS_BLANKZ_AT(string, 1)) { block_indicators = 1; } } if (CHECK(string, '#') && preceeded_by_whitespace) { flow_indicators = 1; block_indicators = 1; } } if (!IS_PRINTABLE(string) || (!IS_ASCII(string) && !emitter->unicode)) { special_characters = 1; } if (IS_SPACE(string)) { if (string.start == string.pointer) { leading_space = 1; } if (string.pointer+WIDTH(string) == string.end) { trailing_space = 1; } if (previous_break) { break_space = 1; } previous_space = 1; previous_break = 0; } else if (IS_BREAK(string)) { line_breaks = 1; if (string.start == string.pointer) { leading_break = 1; } if (string.pointer+WIDTH(string) == string.end) { trailing_break = 1; } if (previous_space) { space_break = 1; } previous_space = 0; previous_break = 1; } else { previous_space = 0; previous_break = 0; } preceeded_by_whitespace = IS_BLANKZ(string); MOVEN(string, width); } emitter->scalar_data.multiline = line_breaks; emitter->scalar_data.flow_plain_allowed = 1; emitter->scalar_data.block_plain_allowed = 1; emitter->scalar_data.single_quoted_allowed = 1; emitter->scalar_data.block_allowed = 1; if (leading_space || leading_break || trailing_space || trailing_break) { emitter->scalar_data.flow_plain_allowed = 0; emitter->scalar_data.block_plain_allowed = 0; } if (trailing_space) { emitter->scalar_data.block_allowed = 0; } if (break_space) { emitter->scalar_data.flow_plain_allowed = 0; emitter->scalar_data.block_plain_allowed = 0; emitter->scalar_data.single_quoted_allowed = 0; } if (space_break || special_characters) { emitter->scalar_data.flow_plain_allowed = 0; emitter->scalar_data.block_plain_allowed = 0; emitter->scalar_data.single_quoted_allowed = 0; emitter->scalar_data.block_allowed = 0; } if (line_breaks) { emitter->scalar_data.flow_plain_allowed = 0; emitter->scalar_data.block_plain_allowed = 0; } if (flow_indicators) { emitter->scalar_data.flow_plain_allowed = 0; } if (block_indicators) { emitter->scalar_data.block_plain_allowed = 0; } return 1; } /* * Check if the event data is valid. */ static int yaml_emitter_analyze_event(yaml_emitter_t *emitter, yaml_event_t *event) { emitter->anchor_data.anchor = NULL; emitter->anchor_data.anchor_length = 0; emitter->tag_data.handle = NULL; emitter->tag_data.handle_length = 0; emitter->tag_data.suffix = NULL; emitter->tag_data.suffix_length = 0; emitter->scalar_data.value = NULL; emitter->scalar_data.length = 0; switch (event->type) { case YAML_ALIAS_EVENT: if (!yaml_emitter_analyze_anchor(emitter, event->data.alias.anchor, 1)) return 0; return 1; case YAML_SCALAR_EVENT: if (event->data.scalar.anchor) { if (!yaml_emitter_analyze_anchor(emitter, event->data.scalar.anchor, 0)) return 0; } if (event->data.scalar.tag && (emitter->canonical || (!event->data.scalar.plain_implicit && !event->data.scalar.quoted_implicit))) { if (!yaml_emitter_analyze_tag(emitter, event->data.scalar.tag)) return 0; } if (!yaml_emitter_analyze_scalar(emitter, event->data.scalar.value, event->data.scalar.length)) return 0; return 1; case YAML_SEQUENCE_START_EVENT: if (event->data.sequence_start.anchor) { if (!yaml_emitter_analyze_anchor(emitter, event->data.sequence_start.anchor, 0)) return 0; } if (event->data.sequence_start.tag && (emitter->canonical || !event->data.sequence_start.implicit)) { if (!yaml_emitter_analyze_tag(emitter, event->data.sequence_start.tag)) return 0; } return 1; case YAML_MAPPING_START_EVENT: if (event->data.mapping_start.anchor) { if (!yaml_emitter_analyze_anchor(emitter, event->data.mapping_start.anchor, 0)) return 0; } if (event->data.mapping_start.tag && (emitter->canonical || !event->data.mapping_start.implicit)) { if (!yaml_emitter_analyze_tag(emitter, event->data.mapping_start.tag)) return 0; } return 1; default: return 1; } } /* * Write the BOM character. */ static int yaml_emitter_write_bom(yaml_emitter_t *emitter) { if (!FLUSH(emitter)) return 0; *(emitter->buffer.pointer++) = (yaml_char_t) '\xEF'; *(emitter->buffer.pointer++) = (yaml_char_t) '\xBB'; *(emitter->buffer.pointer++) = (yaml_char_t) '\xBF'; return 1; } static int yaml_emitter_write_indent(yaml_emitter_t *emitter) { int indent = (emitter->indent >= 0) ? emitter->indent : 0; if (!emitter->indention || emitter->column > indent || (emitter->column == indent && !emitter->whitespace)) { if (!PUT_BREAK(emitter)) return 0; } while (emitter->column < indent) { if (!PUT(emitter, ' ')) return 0; } emitter->whitespace = 1; emitter->indention = 1; return 1; } static int yaml_emitter_write_indicator(yaml_emitter_t *emitter, char *indicator, int need_whitespace, int is_whitespace, int is_indention) { size_t indicator_length; yaml_string_t string; indicator_length = strlen(indicator); STRING_ASSIGN(string, (yaml_char_t *)indicator, indicator_length); if (need_whitespace && !emitter->whitespace) { if (!PUT(emitter, ' ')) return 0; } while (string.pointer != string.end) { if (!WRITE(emitter, string)) return 0; } emitter->whitespace = is_whitespace; emitter->indention = (emitter->indention && is_indention); emitter->open_ended = 0; return 1; } static int yaml_emitter_write_anchor(yaml_emitter_t *emitter, yaml_char_t *value, size_t length) { yaml_string_t string; STRING_ASSIGN(string, value, length); while (string.pointer != string.end) { if (!WRITE(emitter, string)) return 0; } emitter->whitespace = 0; emitter->indention = 0; return 1; } static int yaml_emitter_write_tag_handle(yaml_emitter_t *emitter, yaml_char_t *value, size_t length) { yaml_string_t string; STRING_ASSIGN(string, value, length); if (!emitter->whitespace) { if (!PUT(emitter, ' ')) return 0; } while (string.pointer != string.end) { if (!WRITE(emitter, string)) return 0; } emitter->whitespace = 0; emitter->indention = 0; return 1; } static int yaml_emitter_write_tag_content(yaml_emitter_t *emitter, yaml_char_t *value, size_t length, int need_whitespace) { yaml_string_t string; STRING_ASSIGN(string, value, length); if (need_whitespace && !emitter->whitespace) { if (!PUT(emitter, ' ')) return 0; } while (string.pointer != string.end) { if (IS_ALPHA(string) || CHECK(string, ';') || CHECK(string, '/') || CHECK(string, '?') || CHECK(string, ':') || CHECK(string, '@') || CHECK(string, '&') || CHECK(string, '=') || CHECK(string, '+') || CHECK(string, '$') || CHECK(string, ',') || CHECK(string, '_') || CHECK(string, '.') || CHECK(string, '~') || CHECK(string, '*') || CHECK(string, '\'') || CHECK(string, '(') || CHECK(string, ')') || CHECK(string, '[') || CHECK(string, ']')) { if (!WRITE(emitter, string)) return 0; } else { /* %-encode all other bytes, including valid and invalid UTF-8 */ unsigned char value = *(string.pointer++); if (!PUT(emitter, '%')) return 0; if (!PUT(emitter, (value >> 4) + ((value >> 4) < 10 ? '0' : 'A' - 10))) return 0; if (!PUT(emitter, (value & 0x0F) + ((value & 0x0F) < 10 ? '0' : 'A' - 10))) return 0; } } emitter->whitespace = 0; emitter->indention = 0; return 1; } static int yaml_emitter_write_plain_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length, int allow_breaks) { yaml_string_t string; int spaces = 0; int breaks = 0; STRING_ASSIGN(string, value, length); if (!emitter->whitespace) { if (!PUT(emitter, ' ')) return 0; } while (string.pointer != string.end) { if (IS_SPACE(string)) { if (allow_breaks && !spaces && emitter->column > emitter->best_width && !IS_SPACE_AT(string, 1)) { if (!yaml_emitter_write_indent(emitter)) return 0; MOVE(string); } else { if (!WRITE(emitter, string)) return 0; } spaces = 1; } else if (IS_BREAK(string)) { if (!breaks && CHECK(string, '\n')) { if (!PUT_BREAK(emitter)) return 0; } if (!WRITE_BREAK(emitter, string)) return 0; emitter->indention = 1; breaks = 1; } else { if (breaks) { if (!yaml_emitter_write_indent(emitter)) return 0; } if (!WRITE(emitter, string)) return 0; emitter->indention = 0; spaces = 0; breaks = 0; } } emitter->whitespace = 0; emitter->indention = 0; if (emitter->root_context) { emitter->open_ended = 1; } return 1; } static int yaml_emitter_write_single_quoted_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length, int allow_breaks) { yaml_string_t string; int spaces = 0; int breaks = 0; STRING_ASSIGN(string, value, length); if (!yaml_emitter_write_indicator(emitter, "'", 1, 0, 0)) return 0; while (string.pointer != string.end) { if (IS_SPACE(string)) { if (allow_breaks && !spaces && emitter->column > emitter->best_width && string.pointer != string.start && string.pointer != string.end - 1 && !IS_SPACE_AT(string, 1)) { if (!yaml_emitter_write_indent(emitter)) return 0; MOVE(string); } else { if (!WRITE(emitter, string)) return 0; } spaces = 1; } else if (IS_BREAK(string)) { if (!breaks && CHECK(string, '\n')) { if (!PUT_BREAK(emitter)) return 0; } if (!WRITE_BREAK(emitter, string)) return 0; emitter->indention = 1; breaks = 1; } else { if (breaks) { if (!yaml_emitter_write_indent(emitter)) return 0; } if (CHECK(string, '\'')) { if (!PUT(emitter, '\'')) return 0; } if (!WRITE(emitter, string)) return 0; emitter->indention = 0; spaces = 0; breaks = 0; } } if (!yaml_emitter_write_indicator(emitter, "'", 0, 0, 0)) return 0; emitter->whitespace = 0; emitter->indention = 0; return 1; } static unsigned char utf8_mask[5] = {0xFF, 0x7F, 0x1F, 0x0F, 0x07}; static int yaml_emitter_write_double_quoted_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length, int allow_breaks) { yaml_string_t string; int spaces = 0; STRING_ASSIGN(string, value, length); if (!yaml_emitter_write_indicator(emitter, "\"", 1, 0, 0)) return 0; while (string.pointer != string.end) { unsigned int width = utf8_width(string.pointer, string.end); if (width == 0) { /* UTF-8 encoding error. This is a byte with the high bit set. The parser has been altered to read \XNN as a raw byte. I would prefer to use lowercase x but the old writer produces that for legal UTF-8 encodings of U+0080..U+00FF. It is not clear what other parsers will do with this, though previous libyaml threw an error. */ unsigned int value = string.pointer[0]; int digit; if (!PUT(emitter, '\\')) return 0; if (!PUT(emitter, 'x')) return 0; digit = (value >> 4) & 0x0F; if (!PUT(emitter, digit + (digit < 10 ? '0' : 'A'-10))) return 0; digit = value & 0x0F; if (!PUT(emitter, digit + (digit < 10 ? '0' : 'A'-10))) return 0; MOVE(string); spaces = 0; continue; } if (!IS_PRINTABLE(string) || (!emitter->unicode && !IS_ASCII(string)) || IS_BOM(string) || IS_BREAK(string) || CHECK(string, '"') || CHECK(string, '\\')) { unsigned int value; int k; value = string.pointer[0] & utf8_mask[width]; for (k = 1; k < (int)width; k ++) { value = (value << 6) + (string.pointer[k] & 0x3F); } string.pointer += width; if (!PUT(emitter, '\\')) return 0; switch (value) { case 0x00: if (!PUT(emitter, '0')) return 0; break; case 0x07: if (!PUT(emitter, 'a')) return 0; break; case 0x08: if (!PUT(emitter, 'b')) return 0; break; case 0x09: if (!PUT(emitter, 't')) return 0; break; case 0x0A: if (!PUT(emitter, 'n')) return 0; break; case 0x0B: if (!PUT(emitter, 'v')) return 0; break; case 0x0C: if (!PUT(emitter, 'f')) return 0; break; case 0x0D: if (!PUT(emitter, 'r')) return 0; break; case 0x1B: if (!PUT(emitter, 'e')) return 0; break; case 0x22: if (!PUT(emitter, '\"')) return 0; break; case 0x5C: if (!PUT(emitter, '\\')) return 0; break; case 0x85: if (!PUT(emitter, 'N')) return 0; break; case 0xA0: if (!PUT(emitter, '_')) return 0; break; case 0x2028: if (!PUT(emitter, 'L')) return 0; break; case 0x2029: if (!PUT(emitter, 'P')) return 0; break; default: /* \xNN sequence is disabled so that it may be used in the future for invalid byte sequences */ /*if (value <= 0xFF) { if (!PUT(emitter, 'x')) return 0; width = 2; } else*/ if (value <= 0xFFFF) { if (!PUT(emitter, 'u')) return 0; width = 4; } else { if (!PUT(emitter, 'U')) return 0; width = 8; } for (k = (width-1)*4; k >= 0; k -= 4) { int digit = (value >> k) & 0x0F; if (!PUT(emitter, digit + (digit < 10 ? '0' : 'A'-10))) return 0; } } spaces = 0; } else if (IS_SPACE(string)) { if (allow_breaks && !spaces && emitter->column > emitter->best_width && string.pointer != string.start && string.pointer != string.end - 1) { if (!yaml_emitter_write_indent(emitter)) return 0; if (IS_SPACE_AT(string, 1)) { if (!PUT(emitter, '\\')) return 0; } MOVE(string); } else { if (!WRITE(emitter, string)) return 0; } spaces = 1; } else { if (!WRITEN(emitter, string, width)) return 0; spaces = 0; } } if (!yaml_emitter_write_indicator(emitter, "\"", 0, 0, 0)) return 0; emitter->whitespace = 0; emitter->indention = 0; return 1; } static int yaml_emitter_write_block_scalar_hints(yaml_emitter_t *emitter, yaml_string_t string) { char indent_hint[2]; char *chomp_hint = NULL; if (IS_SPACE(string) || IS_BREAK(string)) { indent_hint[0] = '0' + (char)emitter->best_indent; indent_hint[1] = '\0'; if (!yaml_emitter_write_indicator(emitter, indent_hint, 0, 0, 0)) return 0; } emitter->open_ended = 0; string.pointer = string.end; if (string.start == string.pointer) { chomp_hint = "-"; } else { do { string.pointer --; } while ((*string.pointer & 0xC0) == 0x80); if (!IS_BREAK(string)) { chomp_hint = "-"; } else if (string.start == string.pointer) { chomp_hint = "+"; emitter->open_ended = 1; } else { do { string.pointer --; } while ((*string.pointer & 0xC0) == 0x80); if (IS_BREAK(string)) { chomp_hint = "+"; emitter->open_ended = 1; } } } if (chomp_hint) { if (!yaml_emitter_write_indicator(emitter, chomp_hint, 0, 0, 0)) return 0; } return 1; } static int yaml_emitter_write_literal_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length) { yaml_string_t string; int breaks = 1; STRING_ASSIGN(string, value, length); if (!yaml_emitter_write_indicator(emitter, "|", 1, 0, 0)) return 0; if (!yaml_emitter_write_block_scalar_hints(emitter, string)) return 0; if (!PUT_BREAK(emitter)) return 0; emitter->indention = 1; emitter->whitespace = 1; while (string.pointer != string.end) { if (IS_BREAK(string)) { if (!WRITE_BREAK(emitter, string)) return 0; emitter->indention = 1; breaks = 1; } else { if (breaks) { if (!yaml_emitter_write_indent(emitter)) return 0; } if (!WRITE(emitter, string)) return 0; emitter->indention = 0; breaks = 0; } } return 1; } static int yaml_emitter_write_folded_scalar(yaml_emitter_t *emitter, yaml_char_t *value, size_t length) { yaml_string_t string; int breaks = 1; int leading_spaces = 1; STRING_ASSIGN(string, value, length); if (!yaml_emitter_write_indicator(emitter, ">", 1, 0, 0)) return 0; if (!yaml_emitter_write_block_scalar_hints(emitter, string)) return 0; if (!PUT_BREAK(emitter)) return 0; emitter->indention = 1; emitter->whitespace = 1; while (string.pointer != string.end) { if (IS_BREAK(string)) { if (!breaks && !leading_spaces && CHECK(string, '\n')) { int k = 0; while (IS_BREAK_AT(string, k)) { k += WIDTH_AT(string, k); } if (!IS_BLANKZ_AT(string, k)) { if (!PUT_BREAK(emitter)) return 0; } } if (!WRITE_BREAK(emitter, string)) return 0; emitter->indention = 1; breaks = 1; } else { if (breaks) { if (!yaml_emitter_write_indent(emitter)) return 0; leading_spaces = IS_BLANK(string); } if (!breaks && IS_SPACE(string) && !IS_SPACE_AT(string, 1) && emitter->column > emitter->best_width) { if (!yaml_emitter_write_indent(emitter)) return 0; MOVE(string); } else { if (!WRITE(emitter, string)) return 0; } emitter->indention = 0; breaks = 0; } } return 1; } flvmeta-1.2.2/src/libyaml/loader.c000066400000000000000000000261251346231570700170220ustar00rootroot00000000000000 #include "yaml_private.h" /* * API functions. */ YAML_DECLARE(int) yaml_parser_load(yaml_parser_t *parser, yaml_document_t *document); /* * Error handling. */ static int yaml_parser_set_composer_error(yaml_parser_t *parser, const char *problem, yaml_mark_t problem_mark); static int yaml_parser_set_composer_error_context(yaml_parser_t *parser, const char *context, yaml_mark_t context_mark, const char *problem, yaml_mark_t problem_mark); /* * Alias handling. */ static int yaml_parser_register_anchor(yaml_parser_t *parser, int index, yaml_char_t *anchor); /* * Clean up functions. */ static void yaml_parser_delete_aliases(yaml_parser_t *parser); /* * Composer functions. */ static int yaml_parser_load_document(yaml_parser_t *parser, yaml_event_t *first_event); static int yaml_parser_load_node(yaml_parser_t *parser, yaml_event_t *first_event); static int yaml_parser_load_alias(yaml_parser_t *parser, yaml_event_t *first_event); static int yaml_parser_load_scalar(yaml_parser_t *parser, yaml_event_t *first_event); static int yaml_parser_load_sequence(yaml_parser_t *parser, yaml_event_t *first_event); static int yaml_parser_load_mapping(yaml_parser_t *parser, yaml_event_t *first_event); /* * Load the next document of the stream. */ YAML_DECLARE(int) yaml_parser_load(yaml_parser_t *parser, yaml_document_t *document) { yaml_event_t event; assert(parser); /* Non-NULL parser object is expected. */ assert(document); /* Non-NULL document object is expected. */ memset(document, 0, sizeof(yaml_document_t)); if (!STACK_INIT(parser, document->nodes, INITIAL_STACK_SIZE)) goto error; if (!parser->stream_start_produced) { if (!yaml_parser_parse(parser, &event)) goto error; assert(event.type == YAML_STREAM_START_EVENT); /* STREAM-START is expected. */ } if (parser->stream_end_produced) { return 1; } if (!yaml_parser_parse(parser, &event)) goto error; if (event.type == YAML_STREAM_END_EVENT) { return 1; } if (!STACK_INIT(parser, parser->aliases, INITIAL_STACK_SIZE)) goto error; parser->document = document; if (!yaml_parser_load_document(parser, &event)) goto error; yaml_parser_delete_aliases(parser); parser->document = NULL; return 1; error: yaml_parser_delete_aliases(parser); yaml_document_delete(document); parser->document = NULL; return 0; } /* * Set composer error. */ static int yaml_parser_set_composer_error(yaml_parser_t *parser, const char *problem, yaml_mark_t problem_mark) { parser->error = YAML_COMPOSER_ERROR; parser->problem = problem; parser->problem_mark = problem_mark; return 0; } /* * Set composer error with context. */ static int yaml_parser_set_composer_error_context(yaml_parser_t *parser, const char *context, yaml_mark_t context_mark, const char *problem, yaml_mark_t problem_mark) { parser->error = YAML_COMPOSER_ERROR; parser->context = context; parser->context_mark = context_mark; parser->problem = problem; parser->problem_mark = problem_mark; return 0; } /* * Delete the stack of aliases. */ static void yaml_parser_delete_aliases(yaml_parser_t *parser) { while (!STACK_EMPTY(parser, parser->aliases)) { yaml_free(POP(parser, parser->aliases).anchor); } STACK_DEL(parser, parser->aliases); } /* * Compose a document object. */ static int yaml_parser_load_document(yaml_parser_t *parser, yaml_event_t *first_event) { yaml_event_t event; assert(first_event->type == YAML_DOCUMENT_START_EVENT); /* DOCUMENT-START is expected. */ parser->document->version_directive = first_event->data.document_start.version_directive; parser->document->tag_directives.start = first_event->data.document_start.tag_directives.start; parser->document->tag_directives.end = first_event->data.document_start.tag_directives.end; parser->document->start_implicit = first_event->data.document_start.implicit; parser->document->start_mark = first_event->start_mark; if (!yaml_parser_parse(parser, &event)) return 0; if (!yaml_parser_load_node(parser, &event)) return 0; if (!yaml_parser_parse(parser, &event)) return 0; assert(event.type == YAML_DOCUMENT_END_EVENT); /* DOCUMENT-END is expected. */ parser->document->end_implicit = event.data.document_end.implicit; parser->document->end_mark = event.end_mark; return 1; } /* * Compose a node. */ static int yaml_parser_load_node(yaml_parser_t *parser, yaml_event_t *first_event) { switch (first_event->type) { case YAML_ALIAS_EVENT: return yaml_parser_load_alias(parser, first_event); case YAML_SCALAR_EVENT: return yaml_parser_load_scalar(parser, first_event); case YAML_SEQUENCE_START_EVENT: return yaml_parser_load_sequence(parser, first_event); case YAML_MAPPING_START_EVENT: return yaml_parser_load_mapping(parser, first_event); default: assert(0); /* Could not happen. */ return 0; } return 0; } /* * Add an anchor. */ static int yaml_parser_register_anchor(yaml_parser_t *parser, int index, yaml_char_t *anchor) { yaml_alias_data_t data; yaml_alias_data_t *alias_data; if (!anchor) return 1; data.anchor = anchor; data.index = index; data.mark = parser->document->nodes.start[index-1].start_mark; for (alias_data = parser->aliases.start; alias_data != parser->aliases.top; alias_data ++) { if (strcmp((char *)alias_data->anchor, (char *)anchor) == 0) { yaml_free(anchor); return yaml_parser_set_composer_error_context(parser, "found duplicate anchor; first occurence", alias_data->mark, "second occurence", data.mark); } } if (!PUSH(parser, parser->aliases, data)) { yaml_free(anchor); return 0; } return 1; } /* * Compose a node corresponding to an alias. */ static int yaml_parser_load_alias(yaml_parser_t *parser, yaml_event_t *first_event) { yaml_char_t *anchor = first_event->data.alias.anchor; yaml_alias_data_t *alias_data; for (alias_data = parser->aliases.start; alias_data != parser->aliases.top; alias_data ++) { if (strcmp((char *)alias_data->anchor, (char *)anchor) == 0) { yaml_free(anchor); return alias_data->index; } } yaml_free(anchor); return yaml_parser_set_composer_error(parser, "found undefined alias", first_event->start_mark); } /* * Compose a scalar node. */ static int yaml_parser_load_scalar(yaml_parser_t *parser, yaml_event_t *first_event) { yaml_node_t node; int index; yaml_char_t *tag = first_event->data.scalar.tag; if (!tag || strcmp((char *)tag, "!") == 0) { yaml_free(tag); tag = yaml_strdup((yaml_char_t *)YAML_DEFAULT_SCALAR_TAG); if (!tag) goto error; } SCALAR_NODE_INIT(node, tag, first_event->data.scalar.value, first_event->data.scalar.length, first_event->data.scalar.style, first_event->start_mark, first_event->end_mark); if (!PUSH(parser, parser->document->nodes, node)) goto error; index = parser->document->nodes.top - parser->document->nodes.start; if (!yaml_parser_register_anchor(parser, index, first_event->data.scalar.anchor)) return 0; return index; error: yaml_free(tag); yaml_free(first_event->data.scalar.anchor); yaml_free(first_event->data.scalar.value); return 0; } /* * Compose a sequence node. */ static int yaml_parser_load_sequence(yaml_parser_t *parser, yaml_event_t *first_event) { yaml_event_t event; yaml_node_t node; struct { yaml_node_item_t *start; yaml_node_item_t *end; yaml_node_item_t *top; } items = { NULL, NULL, NULL }; int index, item_index; yaml_char_t *tag = first_event->data.sequence_start.tag; if (!tag || strcmp((char *)tag, "!") == 0) { yaml_free(tag); tag = yaml_strdup((yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG); if (!tag) goto error; } if (!STACK_INIT(parser, items, INITIAL_STACK_SIZE)) goto error; SEQUENCE_NODE_INIT(node, tag, items.start, items.end, first_event->data.sequence_start.style, first_event->start_mark, first_event->end_mark); if (!PUSH(parser, parser->document->nodes, node)) goto error; index = parser->document->nodes.top - parser->document->nodes.start; if (!yaml_parser_register_anchor(parser, index, first_event->data.sequence_start.anchor)) return 0; if (!yaml_parser_parse(parser, &event)) return 0; while (event.type != YAML_SEQUENCE_END_EVENT) { item_index = yaml_parser_load_node(parser, &event); if (!item_index) return 0; if (!PUSH(parser, parser->document->nodes.start[index-1].data.sequence.items, item_index)) return 0; if (!yaml_parser_parse(parser, &event)) return 0; } parser->document->nodes.start[index-1].end_mark = event.end_mark; return index; error: yaml_free(tag); yaml_free(first_event->data.sequence_start.anchor); return 0; } /* * Compose a mapping node. */ static int yaml_parser_load_mapping(yaml_parser_t *parser, yaml_event_t *first_event) { yaml_event_t event; yaml_node_t node; struct { yaml_node_pair_t *start; yaml_node_pair_t *end; yaml_node_pair_t *top; } pairs = { NULL, NULL, NULL }; int index; yaml_node_pair_t pair; yaml_char_t *tag = first_event->data.mapping_start.tag; if (!tag || strcmp((char *)tag, "!") == 0) { yaml_free(tag); tag = yaml_strdup((yaml_char_t *)YAML_DEFAULT_MAPPING_TAG); if (!tag) goto error; } if (!STACK_INIT(parser, pairs, INITIAL_STACK_SIZE)) goto error; MAPPING_NODE_INIT(node, tag, pairs.start, pairs.end, first_event->data.mapping_start.style, first_event->start_mark, first_event->end_mark); if (!PUSH(parser, parser->document->nodes, node)) goto error; index = parser->document->nodes.top - parser->document->nodes.start; if (!yaml_parser_register_anchor(parser, index, first_event->data.mapping_start.anchor)) return 0; if (!yaml_parser_parse(parser, &event)) return 0; while (event.type != YAML_MAPPING_END_EVENT) { pair.key = yaml_parser_load_node(parser, &event); if (!pair.key) return 0; if (!yaml_parser_parse(parser, &event)) return 0; pair.value = yaml_parser_load_node(parser, &event); if (!pair.value) return 0; if (!PUSH(parser, parser->document->nodes.start[index-1].data.mapping.pairs, pair)) return 0; if (!yaml_parser_parse(parser, &event)) return 0; } parser->document->nodes.start[index-1].end_mark = event.end_mark; return index; error: yaml_free(tag); yaml_free(first_event->data.mapping_start.anchor); return 0; } flvmeta-1.2.2/src/libyaml/parser.c000066400000000000000000001302131346231570700170420ustar00rootroot00000000000000 /* * The parser implements the following grammar: * * stream ::= STREAM-START implicit_document? explicit_document* STREAM-END * implicit_document ::= block_node DOCUMENT-END* * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* * block_node_or_indentless_sequence ::= * ALIAS * | properties (block_content | indentless_block_sequence)? * | block_content * | indentless_block_sequence * block_node ::= ALIAS * | properties block_content? * | block_content * flow_node ::= ALIAS * | properties flow_content? * | flow_content * properties ::= TAG ANCHOR? | ANCHOR TAG? * block_content ::= block_collection | flow_collection | SCALAR * flow_content ::= flow_collection | SCALAR * block_collection ::= block_sequence | block_mapping * flow_collection ::= flow_sequence | flow_mapping * block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END * indentless_sequence ::= (BLOCK-ENTRY block_node?)+ * block_mapping ::= BLOCK-MAPPING_START * ((KEY block_node_or_indentless_sequence?)? * (VALUE block_node_or_indentless_sequence?)?)* * BLOCK-END * flow_sequence ::= FLOW-SEQUENCE-START * (flow_sequence_entry FLOW-ENTRY)* * flow_sequence_entry? * FLOW-SEQUENCE-END * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? * flow_mapping ::= FLOW-MAPPING-START * (flow_mapping_entry FLOW-ENTRY)* * flow_mapping_entry? * FLOW-MAPPING-END * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? */ #include "yaml_private.h" /* * Peek the next token in the token queue. */ #define PEEK_TOKEN(parser) \ ((parser->token_available || yaml_parser_fetch_more_tokens(parser)) ? \ parser->tokens.head : NULL) /* * Remove the next token from the queue (must be called after PEEK_TOKEN). */ #define SKIP_TOKEN(parser) \ (parser->token_available = 0, \ parser->tokens_parsed ++, \ parser->stream_end_produced = \ (parser->tokens.head->type == YAML_STREAM_END_TOKEN), \ parser->tokens.head ++) /* * Public API declarations. */ YAML_DECLARE(int) yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event); /* * Error handling. */ static int yaml_parser_set_parser_error(yaml_parser_t *parser, const char *problem, yaml_mark_t problem_mark); static int yaml_parser_set_parser_error_context(yaml_parser_t *parser, const char *context, yaml_mark_t context_mark, const char *problem, yaml_mark_t problem_mark); /* * State functions. */ static int yaml_parser_state_machine(yaml_parser_t *parser, yaml_event_t *event); static int yaml_parser_parse_stream_start(yaml_parser_t *parser, yaml_event_t *event); static int yaml_parser_parse_document_start(yaml_parser_t *parser, yaml_event_t *event, int implicit); static int yaml_parser_parse_document_content(yaml_parser_t *parser, yaml_event_t *event); static int yaml_parser_parse_document_end(yaml_parser_t *parser, yaml_event_t *event); static int yaml_parser_parse_node(yaml_parser_t *parser, yaml_event_t *event, int block, int indentless_sequence); static int yaml_parser_parse_block_sequence_entry(yaml_parser_t *parser, yaml_event_t *event, int first); static int yaml_parser_parse_indentless_sequence_entry(yaml_parser_t *parser, yaml_event_t *event); static int yaml_parser_parse_block_mapping_key(yaml_parser_t *parser, yaml_event_t *event, int first); static int yaml_parser_parse_block_mapping_value(yaml_parser_t *parser, yaml_event_t *event); static int yaml_parser_parse_flow_sequence_entry(yaml_parser_t *parser, yaml_event_t *event, int first); static int yaml_parser_parse_flow_sequence_entry_mapping_key(yaml_parser_t *parser, yaml_event_t *event); static int yaml_parser_parse_flow_sequence_entry_mapping_value(yaml_parser_t *parser, yaml_event_t *event); static int yaml_parser_parse_flow_sequence_entry_mapping_end(yaml_parser_t *parser, yaml_event_t *event); static int yaml_parser_parse_flow_mapping_key(yaml_parser_t *parser, yaml_event_t *event, int first); static int yaml_parser_parse_flow_mapping_value(yaml_parser_t *parser, yaml_event_t *event, int empty); /* * Utility functions. */ static int yaml_parser_process_empty_scalar(yaml_parser_t *parser, yaml_event_t *event, yaml_mark_t mark); static int yaml_parser_process_directives(yaml_parser_t *parser, yaml_version_directive_t **version_directive_ref, yaml_tag_directive_t **tag_directives_start_ref, yaml_tag_directive_t **tag_directives_end_ref); static int yaml_parser_append_tag_directive(yaml_parser_t *parser, yaml_tag_directive_t value, int allow_duplicates, yaml_mark_t mark); /* * Get the next event. */ YAML_DECLARE(int) yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event) { assert(parser); /* Non-NULL parser object is expected. */ assert(event); /* Non-NULL event object is expected. */ /* Erase the event object. */ memset(event, 0, sizeof(yaml_event_t)); /* No events after the end of the stream or error. */ if (parser->stream_end_produced || parser->error || parser->state == YAML_PARSE_END_STATE) { return 1; } /* Generate the next event. */ return yaml_parser_state_machine(parser, event); } /* * Set parser error. */ static int yaml_parser_set_parser_error(yaml_parser_t *parser, const char *problem, yaml_mark_t problem_mark) { parser->error = YAML_PARSER_ERROR; parser->problem = problem; parser->problem_mark = problem_mark; return 0; } static int yaml_parser_set_parser_error_context(yaml_parser_t *parser, const char *context, yaml_mark_t context_mark, const char *problem, yaml_mark_t problem_mark) { parser->error = YAML_PARSER_ERROR; parser->context = context; parser->context_mark = context_mark; parser->problem = problem; parser->problem_mark = problem_mark; return 0; } /* * State dispatcher. */ static int yaml_parser_state_machine(yaml_parser_t *parser, yaml_event_t *event) { switch (parser->state) { case YAML_PARSE_STREAM_START_STATE: return yaml_parser_parse_stream_start(parser, event); case YAML_PARSE_IMPLICIT_DOCUMENT_START_STATE: return yaml_parser_parse_document_start(parser, event, 1); case YAML_PARSE_DOCUMENT_START_STATE: return yaml_parser_parse_document_start(parser, event, 0); case YAML_PARSE_DOCUMENT_CONTENT_STATE: return yaml_parser_parse_document_content(parser, event); case YAML_PARSE_DOCUMENT_END_STATE: return yaml_parser_parse_document_end(parser, event); case YAML_PARSE_BLOCK_NODE_STATE: return yaml_parser_parse_node(parser, event, 1, 0); case YAML_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: return yaml_parser_parse_node(parser, event, 1, 1); case YAML_PARSE_FLOW_NODE_STATE: return yaml_parser_parse_node(parser, event, 0, 0); case YAML_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: return yaml_parser_parse_block_sequence_entry(parser, event, 1); case YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: return yaml_parser_parse_block_sequence_entry(parser, event, 0); case YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: return yaml_parser_parse_indentless_sequence_entry(parser, event); case YAML_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: return yaml_parser_parse_block_mapping_key(parser, event, 1); case YAML_PARSE_BLOCK_MAPPING_KEY_STATE: return yaml_parser_parse_block_mapping_key(parser, event, 0); case YAML_PARSE_BLOCK_MAPPING_VALUE_STATE: return yaml_parser_parse_block_mapping_value(parser, event); case YAML_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: return yaml_parser_parse_flow_sequence_entry(parser, event, 1); case YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE: return yaml_parser_parse_flow_sequence_entry(parser, event, 0); case YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event); case YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event); case YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event); case YAML_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: return yaml_parser_parse_flow_mapping_key(parser, event, 1); case YAML_PARSE_FLOW_MAPPING_KEY_STATE: return yaml_parser_parse_flow_mapping_key(parser, event, 0); case YAML_PARSE_FLOW_MAPPING_VALUE_STATE: return yaml_parser_parse_flow_mapping_value(parser, event, 0); case YAML_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: return yaml_parser_parse_flow_mapping_value(parser, event, 1); default: assert(1); /* Invalid state. */ } return 0; } /* * Parse the production: * stream ::= STREAM-START implicit_document? explicit_document* STREAM-END * ************ */ static int yaml_parser_parse_stream_start(yaml_parser_t *parser, yaml_event_t *event) { yaml_token_t *token; token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_STREAM_START_TOKEN) { return yaml_parser_set_parser_error(parser, "did not find expected ", token->start_mark); } parser->state = YAML_PARSE_IMPLICIT_DOCUMENT_START_STATE; STREAM_START_EVENT_INIT(*event, token->data.stream_start.encoding, token->start_mark, token->start_mark); SKIP_TOKEN(parser); return 1; } /* * Parse the productions: * implicit_document ::= block_node DOCUMENT-END* * * * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* * ************************* */ static int yaml_parser_parse_document_start(yaml_parser_t *parser, yaml_event_t *event, int implicit) { yaml_token_t *token; yaml_version_directive_t *version_directive = NULL; struct { yaml_tag_directive_t *start; yaml_tag_directive_t *end; } tag_directives = { NULL, NULL }; token = PEEK_TOKEN(parser); if (!token) return 0; /* Parse extra document end indicators. */ if (!implicit) { while (token->type == YAML_DOCUMENT_END_TOKEN) { SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) return 0; } } /* Parse an implicit document. */ if (implicit && token->type != YAML_VERSION_DIRECTIVE_TOKEN && token->type != YAML_TAG_DIRECTIVE_TOKEN && token->type != YAML_DOCUMENT_START_TOKEN && token->type != YAML_STREAM_END_TOKEN) { if (!yaml_parser_process_directives(parser, NULL, NULL, NULL)) return 0; if (!PUSH(parser, parser->states, YAML_PARSE_DOCUMENT_END_STATE)) return 0; parser->state = YAML_PARSE_BLOCK_NODE_STATE; DOCUMENT_START_EVENT_INIT(*event, NULL, NULL, NULL, 1, token->start_mark, token->start_mark); return 1; } /* Parse an explicit document. */ else if (token->type != YAML_STREAM_END_TOKEN) { yaml_mark_t start_mark, end_mark; start_mark = token->start_mark; if (!yaml_parser_process_directives(parser, &version_directive, &tag_directives.start, &tag_directives.end)) return 0; token = PEEK_TOKEN(parser); if (!token) goto error; if (token->type != YAML_DOCUMENT_START_TOKEN) { yaml_parser_set_parser_error(parser, "did not find expected ", token->start_mark); goto error; } if (!PUSH(parser, parser->states, YAML_PARSE_DOCUMENT_END_STATE)) goto error; parser->state = YAML_PARSE_DOCUMENT_CONTENT_STATE; end_mark = token->end_mark; DOCUMENT_START_EVENT_INIT(*event, version_directive, tag_directives.start, tag_directives.end, 0, start_mark, end_mark); SKIP_TOKEN(parser); version_directive = NULL; tag_directives.start = tag_directives.end = NULL; return 1; } /* Parse the stream end. */ else { parser->state = YAML_PARSE_END_STATE; STREAM_END_EVENT_INIT(*event, token->start_mark, token->end_mark); SKIP_TOKEN(parser); return 1; } error: yaml_free(version_directive); while (tag_directives.start != tag_directives.end) { yaml_free(tag_directives.end[-1].handle); yaml_free(tag_directives.end[-1].prefix); tag_directives.end --; } yaml_free(tag_directives.start); return 0; } /* * Parse the productions: * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* * *********** */ static int yaml_parser_parse_document_content(yaml_parser_t *parser, yaml_event_t *event) { yaml_token_t *token; token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type == YAML_VERSION_DIRECTIVE_TOKEN || token->type == YAML_TAG_DIRECTIVE_TOKEN || token->type == YAML_DOCUMENT_START_TOKEN || token->type == YAML_DOCUMENT_END_TOKEN || token->type == YAML_STREAM_END_TOKEN) { parser->state = POP(parser, parser->states); return yaml_parser_process_empty_scalar(parser, event, token->start_mark); } else { return yaml_parser_parse_node(parser, event, 1, 0); } } /* * Parse the productions: * implicit_document ::= block_node DOCUMENT-END* * ************* * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* * ************* */ static int yaml_parser_parse_document_end(yaml_parser_t *parser, yaml_event_t *event) { yaml_token_t *token; yaml_mark_t start_mark, end_mark; int implicit = 1; token = PEEK_TOKEN(parser); if (!token) return 0; start_mark = end_mark = token->start_mark; if (token->type == YAML_DOCUMENT_END_TOKEN) { end_mark = token->end_mark; SKIP_TOKEN(parser); implicit = 0; } while (!STACK_EMPTY(parser, parser->tag_directives)) { yaml_tag_directive_t tag_directive = POP(parser, parser->tag_directives); yaml_free(tag_directive.handle); yaml_free(tag_directive.prefix); } parser->state = YAML_PARSE_DOCUMENT_START_STATE; DOCUMENT_END_EVENT_INIT(*event, implicit, start_mark, end_mark); return 1; } /* * Parse the productions: * block_node_or_indentless_sequence ::= * ALIAS * ***** * | properties (block_content | indentless_block_sequence)? * ********** * * | block_content | indentless_block_sequence * * * block_node ::= ALIAS * ***** * | properties block_content? * ********** * * | block_content * * * flow_node ::= ALIAS * ***** * | properties flow_content? * ********** * * | flow_content * * * properties ::= TAG ANCHOR? | ANCHOR TAG? * ************************* * block_content ::= block_collection | flow_collection | SCALAR * ****** * flow_content ::= flow_collection | SCALAR * ****** */ static int yaml_parser_parse_node(yaml_parser_t *parser, yaml_event_t *event, int block, int indentless_sequence) { yaml_token_t *token; yaml_char_t *anchor = NULL; yaml_char_t *tag_handle = NULL; yaml_char_t *tag_suffix = NULL; yaml_char_t *tag = NULL; yaml_mark_t start_mark, end_mark, tag_mark; int implicit; token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type == YAML_ALIAS_TOKEN) { parser->state = POP(parser, parser->states); ALIAS_EVENT_INIT(*event, token->data.alias.value, token->start_mark, token->end_mark); SKIP_TOKEN(parser); return 1; } else { start_mark = end_mark = token->start_mark; if (token->type == YAML_ANCHOR_TOKEN) { anchor = token->data.anchor.value; start_mark = token->start_mark; end_mark = token->end_mark; SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) goto error; if (token->type == YAML_TAG_TOKEN) { tag_handle = token->data.tag.handle; tag_suffix = token->data.tag.suffix; tag_mark = token->start_mark; end_mark = token->end_mark; SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) goto error; } } else if (token->type == YAML_TAG_TOKEN) { tag_handle = token->data.tag.handle; tag_suffix = token->data.tag.suffix; start_mark = tag_mark = token->start_mark; end_mark = token->end_mark; SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) goto error; if (token->type == YAML_ANCHOR_TOKEN) { anchor = token->data.anchor.value; end_mark = token->end_mark; SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) goto error; } } if (tag_handle) { if (!*tag_handle) { tag = tag_suffix; yaml_free(tag_handle); tag_handle = tag_suffix = NULL; } else { yaml_tag_directive_t *tag_directive; for (tag_directive = parser->tag_directives.start; tag_directive != parser->tag_directives.top; tag_directive ++) { if (strcmp((char *)tag_directive->handle, (char *)tag_handle) == 0) { size_t prefix_len = strlen((char *)tag_directive->prefix); size_t suffix_len = strlen((char *)tag_suffix); tag = yaml_malloc(prefix_len+suffix_len+1); if (!tag) { parser->error = YAML_MEMORY_ERROR; goto error; } memcpy(tag, tag_directive->prefix, prefix_len); memcpy(tag+prefix_len, tag_suffix, suffix_len); tag[prefix_len+suffix_len] = '\0'; yaml_free(tag_handle); yaml_free(tag_suffix); tag_handle = tag_suffix = NULL; break; } } if (!tag) { yaml_parser_set_parser_error_context(parser, "while parsing a node", start_mark, "found undefined tag handle", tag_mark); goto error; } } } implicit = (!tag || !*tag); if (indentless_sequence && token->type == YAML_BLOCK_ENTRY_TOKEN) { end_mark = token->end_mark; parser->state = YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE; SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, YAML_BLOCK_SEQUENCE_STYLE, start_mark, end_mark); return 1; } else { if (token->type == YAML_SCALAR_TOKEN) { int plain_implicit = 0; int quoted_implicit = 0; end_mark = token->end_mark; if ((token->data.scalar.style == YAML_PLAIN_SCALAR_STYLE && !tag) || (tag && strcmp((char *)tag, "!") == 0)) { plain_implicit = 1; } else if (!tag) { quoted_implicit = 1; } parser->state = POP(parser, parser->states); SCALAR_EVENT_INIT(*event, anchor, tag, token->data.scalar.value, token->data.scalar.length, plain_implicit, quoted_implicit, token->data.scalar.style, start_mark, end_mark); SKIP_TOKEN(parser); return 1; } else if (token->type == YAML_FLOW_SEQUENCE_START_TOKEN) { end_mark = token->end_mark; parser->state = YAML_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE; SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, YAML_FLOW_SEQUENCE_STYLE, start_mark, end_mark); return 1; } else if (token->type == YAML_FLOW_MAPPING_START_TOKEN) { end_mark = token->end_mark; parser->state = YAML_PARSE_FLOW_MAPPING_FIRST_KEY_STATE; MAPPING_START_EVENT_INIT(*event, anchor, tag, implicit, YAML_FLOW_MAPPING_STYLE, start_mark, end_mark); return 1; } else if (block && token->type == YAML_BLOCK_SEQUENCE_START_TOKEN) { end_mark = token->end_mark; parser->state = YAML_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE; SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, YAML_BLOCK_SEQUENCE_STYLE, start_mark, end_mark); return 1; } else if (block && token->type == YAML_BLOCK_MAPPING_START_TOKEN) { end_mark = token->end_mark; parser->state = YAML_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE; MAPPING_START_EVENT_INIT(*event, anchor, tag, implicit, YAML_BLOCK_MAPPING_STYLE, start_mark, end_mark); return 1; } else if (anchor || tag) { yaml_char_t *value = yaml_malloc(1); if (!value) { parser->error = YAML_MEMORY_ERROR; goto error; } value[0] = '\0'; parser->state = POP(parser, parser->states); SCALAR_EVENT_INIT(*event, anchor, tag, value, 0, implicit, 0, YAML_PLAIN_SCALAR_STYLE, start_mark, end_mark); return 1; } else { yaml_parser_set_parser_error_context(parser, (block ? "while parsing a block node" : "while parsing a flow node"), start_mark, "did not find expected node content", token->start_mark); goto error; } } } error: yaml_free(anchor); yaml_free(tag_handle); yaml_free(tag_suffix); yaml_free(tag); return 0; } /* * Parse the productions: * block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END * ******************** *********** * ********* */ static int yaml_parser_parse_block_sequence_entry(yaml_parser_t *parser, yaml_event_t *event, int first) { yaml_token_t *token; if (first) { token = PEEK_TOKEN(parser); if (!PUSH(parser, parser->marks, token->start_mark)) return 0; SKIP_TOKEN(parser); } token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type == YAML_BLOCK_ENTRY_TOKEN) { yaml_mark_t mark = token->end_mark; SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_BLOCK_ENTRY_TOKEN && token->type != YAML_BLOCK_END_TOKEN) { if (!PUSH(parser, parser->states, YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE)) return 0; return yaml_parser_parse_node(parser, event, 1, 0); } else { parser->state = YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE; return yaml_parser_process_empty_scalar(parser, event, mark); } } else if (token->type == YAML_BLOCK_END_TOKEN) { yaml_mark_t dummy_mark; /* Used to eliminate a compiler warning. */ parser->state = POP(parser, parser->states); dummy_mark = POP(parser, parser->marks); SEQUENCE_END_EVENT_INIT(*event, token->start_mark, token->end_mark); SKIP_TOKEN(parser); return 1; } else { return yaml_parser_set_parser_error_context(parser, "while parsing a block collection", POP(parser, parser->marks), "did not find expected '-' indicator", token->start_mark); } } /* * Parse the productions: * indentless_sequence ::= (BLOCK-ENTRY block_node?)+ * *********** * */ static int yaml_parser_parse_indentless_sequence_entry(yaml_parser_t *parser, yaml_event_t *event) { yaml_token_t *token; token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type == YAML_BLOCK_ENTRY_TOKEN) { yaml_mark_t mark = token->end_mark; SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_BLOCK_ENTRY_TOKEN && token->type != YAML_KEY_TOKEN && token->type != YAML_VALUE_TOKEN && token->type != YAML_BLOCK_END_TOKEN) { if (!PUSH(parser, parser->states, YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE)) return 0; return yaml_parser_parse_node(parser, event, 1, 0); } else { parser->state = YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE; return yaml_parser_process_empty_scalar(parser, event, mark); } } else { parser->state = POP(parser, parser->states); SEQUENCE_END_EVENT_INIT(*event, token->start_mark, token->start_mark); return 1; } } /* * Parse the productions: * block_mapping ::= BLOCK-MAPPING_START * ******************* * ((KEY block_node_or_indentless_sequence?)? * *** * * (VALUE block_node_or_indentless_sequence?)?)* * * BLOCK-END * ********* */ static int yaml_parser_parse_block_mapping_key(yaml_parser_t *parser, yaml_event_t *event, int first) { yaml_token_t *token; if (first) { token = PEEK_TOKEN(parser); if (!PUSH(parser, parser->marks, token->start_mark)) return 0; SKIP_TOKEN(parser); } token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type == YAML_KEY_TOKEN) { yaml_mark_t mark = token->end_mark; SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_KEY_TOKEN && token->type != YAML_VALUE_TOKEN && token->type != YAML_BLOCK_END_TOKEN) { if (!PUSH(parser, parser->states, YAML_PARSE_BLOCK_MAPPING_VALUE_STATE)) return 0; return yaml_parser_parse_node(parser, event, 1, 1); } else { parser->state = YAML_PARSE_BLOCK_MAPPING_VALUE_STATE; return yaml_parser_process_empty_scalar(parser, event, mark); } } else if (token->type == YAML_BLOCK_END_TOKEN) { yaml_mark_t dummy_mark; /* Used to eliminate a compiler warning. */ parser->state = POP(parser, parser->states); dummy_mark = POP(parser, parser->marks); MAPPING_END_EVENT_INIT(*event, token->start_mark, token->end_mark); SKIP_TOKEN(parser); return 1; } else { return yaml_parser_set_parser_error_context(parser, "while parsing a block mapping", POP(parser, parser->marks), "did not find expected key", token->start_mark); } } /* * Parse the productions: * block_mapping ::= BLOCK-MAPPING_START * * ((KEY block_node_or_indentless_sequence?)? * * (VALUE block_node_or_indentless_sequence?)?)* * ***** * * BLOCK-END * */ static int yaml_parser_parse_block_mapping_value(yaml_parser_t *parser, yaml_event_t *event) { yaml_token_t *token; token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type == YAML_VALUE_TOKEN) { yaml_mark_t mark = token->end_mark; SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_KEY_TOKEN && token->type != YAML_VALUE_TOKEN && token->type != YAML_BLOCK_END_TOKEN) { if (!PUSH(parser, parser->states, YAML_PARSE_BLOCK_MAPPING_KEY_STATE)) return 0; return yaml_parser_parse_node(parser, event, 1, 1); } else { parser->state = YAML_PARSE_BLOCK_MAPPING_KEY_STATE; return yaml_parser_process_empty_scalar(parser, event, mark); } } else { parser->state = YAML_PARSE_BLOCK_MAPPING_KEY_STATE; return yaml_parser_process_empty_scalar(parser, event, token->start_mark); } } /* * Parse the productions: * flow_sequence ::= FLOW-SEQUENCE-START * ******************* * (flow_sequence_entry FLOW-ENTRY)* * * ********** * flow_sequence_entry? * * * FLOW-SEQUENCE-END * ***************** * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? * * */ static int yaml_parser_parse_flow_sequence_entry(yaml_parser_t *parser, yaml_event_t *event, int first) { yaml_token_t *token; yaml_mark_t dummy_mark; /* Used to eliminate a compiler warning. */ if (first) { token = PEEK_TOKEN(parser); if (!PUSH(parser, parser->marks, token->start_mark)) return 0; SKIP_TOKEN(parser); } token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_FLOW_SEQUENCE_END_TOKEN) { if (!first) { if (token->type == YAML_FLOW_ENTRY_TOKEN) { SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) return 0; } else { return yaml_parser_set_parser_error_context(parser, "while parsing a flow sequence", POP(parser, parser->marks), "did not find expected ',' or ']'", token->start_mark); } } if (token->type == YAML_KEY_TOKEN) { parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE; MAPPING_START_EVENT_INIT(*event, NULL, NULL, 1, YAML_FLOW_MAPPING_STYLE, token->start_mark, token->end_mark); SKIP_TOKEN(parser); return 1; } else if (token->type != YAML_FLOW_SEQUENCE_END_TOKEN) { if (!PUSH(parser, parser->states, YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE)) return 0; return yaml_parser_parse_node(parser, event, 0, 0); } } parser->state = POP(parser, parser->states); dummy_mark = POP(parser, parser->marks); SEQUENCE_END_EVENT_INIT(*event, token->start_mark, token->end_mark); SKIP_TOKEN(parser); return 1; } /* * Parse the productions: * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? * *** * */ static int yaml_parser_parse_flow_sequence_entry_mapping_key(yaml_parser_t *parser, yaml_event_t *event) { yaml_token_t *token; token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_VALUE_TOKEN && token->type != YAML_FLOW_ENTRY_TOKEN && token->type != YAML_FLOW_SEQUENCE_END_TOKEN) { if (!PUSH(parser, parser->states, YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE)) return 0; return yaml_parser_parse_node(parser, event, 0, 0); } else { yaml_mark_t mark = token->end_mark; SKIP_TOKEN(parser); parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE; return yaml_parser_process_empty_scalar(parser, event, mark); } } /* * Parse the productions: * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? * ***** * */ static int yaml_parser_parse_flow_sequence_entry_mapping_value(yaml_parser_t *parser, yaml_event_t *event) { yaml_token_t *token; token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type == YAML_VALUE_TOKEN) { SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_FLOW_ENTRY_TOKEN && token->type != YAML_FLOW_SEQUENCE_END_TOKEN) { if (!PUSH(parser, parser->states, YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE)) return 0; return yaml_parser_parse_node(parser, event, 0, 0); } } parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE; return yaml_parser_process_empty_scalar(parser, event, token->start_mark); } /* * Parse the productions: * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? * * */ static int yaml_parser_parse_flow_sequence_entry_mapping_end(yaml_parser_t *parser, yaml_event_t *event) { yaml_token_t *token; token = PEEK_TOKEN(parser); if (!token) return 0; parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE; MAPPING_END_EVENT_INIT(*event, token->start_mark, token->start_mark); return 1; } /* * Parse the productions: * flow_mapping ::= FLOW-MAPPING-START * ****************** * (flow_mapping_entry FLOW-ENTRY)* * * ********** * flow_mapping_entry? * ****************** * FLOW-MAPPING-END * **************** * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? * * *** * */ static int yaml_parser_parse_flow_mapping_key(yaml_parser_t *parser, yaml_event_t *event, int first) { yaml_token_t *token; yaml_mark_t dummy_mark; /* Used to eliminate a compiler warning. */ if (first) { token = PEEK_TOKEN(parser); if (!PUSH(parser, parser->marks, token->start_mark)) return 0; SKIP_TOKEN(parser); } token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_FLOW_MAPPING_END_TOKEN) { if (!first) { if (token->type == YAML_FLOW_ENTRY_TOKEN) { SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) return 0; } else { return yaml_parser_set_parser_error_context(parser, "while parsing a flow mapping", POP(parser, parser->marks), "did not find expected ',' or '}'", token->start_mark); } } if (token->type == YAML_KEY_TOKEN) { SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_VALUE_TOKEN && token->type != YAML_FLOW_ENTRY_TOKEN && token->type != YAML_FLOW_MAPPING_END_TOKEN) { if (!PUSH(parser, parser->states, YAML_PARSE_FLOW_MAPPING_VALUE_STATE)) return 0; return yaml_parser_parse_node(parser, event, 0, 0); } else { parser->state = YAML_PARSE_FLOW_MAPPING_VALUE_STATE; return yaml_parser_process_empty_scalar(parser, event, token->start_mark); } } else if (token->type != YAML_FLOW_MAPPING_END_TOKEN) { if (!PUSH(parser, parser->states, YAML_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE)) return 0; return yaml_parser_parse_node(parser, event, 0, 0); } } parser->state = POP(parser, parser->states); dummy_mark = POP(parser, parser->marks); MAPPING_END_EVENT_INIT(*event, token->start_mark, token->end_mark); SKIP_TOKEN(parser); return 1; } /* * Parse the productions: * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? * * ***** * */ static int yaml_parser_parse_flow_mapping_value(yaml_parser_t *parser, yaml_event_t *event, int empty) { yaml_token_t *token; token = PEEK_TOKEN(parser); if (!token) return 0; if (empty) { parser->state = YAML_PARSE_FLOW_MAPPING_KEY_STATE; return yaml_parser_process_empty_scalar(parser, event, token->start_mark); } if (token->type == YAML_VALUE_TOKEN) { SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) return 0; if (token->type != YAML_FLOW_ENTRY_TOKEN && token->type != YAML_FLOW_MAPPING_END_TOKEN) { if (!PUSH(parser, parser->states, YAML_PARSE_FLOW_MAPPING_KEY_STATE)) return 0; return yaml_parser_parse_node(parser, event, 0, 0); } } parser->state = YAML_PARSE_FLOW_MAPPING_KEY_STATE; return yaml_parser_process_empty_scalar(parser, event, token->start_mark); } /* * Generate an empty scalar event. */ static int yaml_parser_process_empty_scalar(yaml_parser_t *parser, yaml_event_t *event, yaml_mark_t mark) { yaml_char_t *value; value = yaml_malloc(1); if (!value) { parser->error = YAML_MEMORY_ERROR; return 0; } value[0] = '\0'; SCALAR_EVENT_INIT(*event, NULL, NULL, value, 0, 1, 0, YAML_PLAIN_SCALAR_STYLE, mark, mark); return 1; } /* * Parse directives. */ static int yaml_parser_process_directives(yaml_parser_t *parser, yaml_version_directive_t **version_directive_ref, yaml_tag_directive_t **tag_directives_start_ref, yaml_tag_directive_t **tag_directives_end_ref) { yaml_tag_directive_t default_tag_directives[] = { {(yaml_char_t *)"!", (yaml_char_t *)"!"}, {(yaml_char_t *)"!!", (yaml_char_t *)"tag:yaml.org,2002:"}, {NULL, NULL} }; yaml_tag_directive_t *default_tag_directive; yaml_version_directive_t *version_directive = NULL; struct { yaml_tag_directive_t *start; yaml_tag_directive_t *end; yaml_tag_directive_t *top; } tag_directives = { NULL, NULL, NULL }; yaml_token_t *token; if (!STACK_INIT(parser, tag_directives, INITIAL_STACK_SIZE)) goto error; token = PEEK_TOKEN(parser); if (!token) goto error; while (token->type == YAML_VERSION_DIRECTIVE_TOKEN || token->type == YAML_TAG_DIRECTIVE_TOKEN) { if (token->type == YAML_VERSION_DIRECTIVE_TOKEN) { if (version_directive) { yaml_parser_set_parser_error(parser, "found duplicate %YAML directive", token->start_mark); goto error; } if (token->data.version_directive.major != 1 || token->data.version_directive.minor != 1) { yaml_parser_set_parser_error(parser, "found incompatible YAML document", token->start_mark); goto error; } version_directive = yaml_malloc(sizeof(yaml_version_directive_t)); if (!version_directive) { parser->error = YAML_MEMORY_ERROR; goto error; } version_directive->major = token->data.version_directive.major; version_directive->minor = token->data.version_directive.minor; } else if (token->type == YAML_TAG_DIRECTIVE_TOKEN) { yaml_tag_directive_t value; value.handle = token->data.tag_directive.handle; value.prefix = token->data.tag_directive.prefix; if (!yaml_parser_append_tag_directive(parser, value, 0, token->start_mark)) goto error; if (!PUSH(parser, tag_directives, value)) goto error; } SKIP_TOKEN(parser); token = PEEK_TOKEN(parser); if (!token) goto error; } for (default_tag_directive = default_tag_directives; default_tag_directive->handle; default_tag_directive++) { if (!yaml_parser_append_tag_directive(parser, *default_tag_directive, 1, token->start_mark)) goto error; } if (version_directive_ref) { *version_directive_ref = version_directive; } if (tag_directives_start_ref) { if (STACK_EMPTY(parser, tag_directives)) { *tag_directives_start_ref = *tag_directives_end_ref = NULL; STACK_DEL(parser, tag_directives); } else { *tag_directives_start_ref = tag_directives.start; *tag_directives_end_ref = tag_directives.top; } } else { STACK_DEL(parser, tag_directives); } return 1; error: yaml_free(version_directive); while (!STACK_EMPTY(parser, tag_directives)) { yaml_tag_directive_t tag_directive = POP(parser, tag_directives); yaml_free(tag_directive.handle); yaml_free(tag_directive.prefix); } STACK_DEL(parser, tag_directives); return 0; } /* * Append a tag directive to the directives stack. */ static int yaml_parser_append_tag_directive(yaml_parser_t *parser, yaml_tag_directive_t value, int allow_duplicates, yaml_mark_t mark) { yaml_tag_directive_t *tag_directive; yaml_tag_directive_t copy = { NULL, NULL }; for (tag_directive = parser->tag_directives.start; tag_directive != parser->tag_directives.top; tag_directive ++) { if (strcmp((char *)value.handle, (char *)tag_directive->handle) == 0) { if (allow_duplicates) return 1; return yaml_parser_set_parser_error(parser, "found duplicate %TAG directive", mark); } } copy.handle = yaml_strdup(value.handle); copy.prefix = yaml_strdup(value.prefix); if (!copy.handle || !copy.prefix) { parser->error = YAML_MEMORY_ERROR; goto error; } if (!PUSH(parser, parser->tag_directives, copy)) goto error; return 1; error: yaml_free(copy.handle); yaml_free(copy.prefix); return 0; } flvmeta-1.2.2/src/libyaml/reader.c000066400000000000000000000313121346231570700170100ustar00rootroot00000000000000 #include "yaml_private.h" /* * Declarations. */ static int yaml_parser_set_reader_error(yaml_parser_t *parser, const char *problem, size_t offset, int value); static int yaml_parser_update_raw_buffer(yaml_parser_t *parser); static int yaml_parser_determine_encoding(yaml_parser_t *parser); YAML_DECLARE(int) yaml_parser_update_buffer(yaml_parser_t *parser, size_t length); /* * Set the reader error and return 0. */ static int yaml_parser_set_reader_error(yaml_parser_t *parser, const char *problem, size_t offset, int value) { parser->error = YAML_READER_ERROR; parser->problem = problem; parser->problem_offset = offset; parser->problem_value = value; return 0; } /* * Byte order marks. */ #define BOM_UTF8 "\xef\xbb\xbf" #define BOM_UTF16LE "\xff\xfe" #define BOM_UTF16BE "\xfe\xff" /* * Determine the input stream encoding by checking the BOM symbol. If no BOM is * found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. */ static int yaml_parser_determine_encoding(yaml_parser_t *parser) { /* Ensure that we had enough bytes in the raw buffer. */ while (!parser->eof && parser->raw_buffer.last - parser->raw_buffer.pointer < 3) { if (!yaml_parser_update_raw_buffer(parser)) { return 0; } } /* Determine the encoding. */ if (parser->raw_buffer.last - parser->raw_buffer.pointer >= 2 && !memcmp(parser->raw_buffer.pointer, BOM_UTF16LE, 2)) { parser->encoding = YAML_UTF16LE_ENCODING; parser->raw_buffer.pointer += 2; parser->offset += 2; } else if (parser->raw_buffer.last - parser->raw_buffer.pointer >= 2 && !memcmp(parser->raw_buffer.pointer, BOM_UTF16BE, 2)) { parser->encoding = YAML_UTF16BE_ENCODING; parser->raw_buffer.pointer += 2; parser->offset += 2; } else if (parser->raw_buffer.last - parser->raw_buffer.pointer >= 3 && !memcmp(parser->raw_buffer.pointer, BOM_UTF8, 3)) { parser->encoding = YAML_UTF8_ENCODING; parser->raw_buffer.pointer += 3; parser->offset += 3; } else { parser->encoding = YAML_UTF8_ENCODING; } return 1; } /* * Update the raw buffer. */ static int yaml_parser_update_raw_buffer(yaml_parser_t *parser) { size_t size_read = 0; /* Return if the raw buffer is full. */ if (parser->raw_buffer.start == parser->raw_buffer.pointer && parser->raw_buffer.last == parser->raw_buffer.end) return 1; /* Return on EOF. */ if (parser->eof) return 1; /* Move the remaining bytes in the raw buffer to the beginning. */ if (parser->raw_buffer.start < parser->raw_buffer.pointer && parser->raw_buffer.pointer < parser->raw_buffer.last) { memmove(parser->raw_buffer.start, parser->raw_buffer.pointer, parser->raw_buffer.last - parser->raw_buffer.pointer); } parser->raw_buffer.last -= parser->raw_buffer.pointer - parser->raw_buffer.start; parser->raw_buffer.pointer = parser->raw_buffer.start; /* Call the read handler to fill the buffer. */ if (!parser->read_handler(parser->read_handler_data, parser->raw_buffer.last, parser->raw_buffer.end - parser->raw_buffer.last, &size_read)) { return yaml_parser_set_reader_error(parser, "input error", parser->offset, -1); } parser->raw_buffer.last += size_read; if (!size_read) { parser->eof = 1; } return 1; } /* * Ensure that the buffer contains at least `length` characters. * Return 1 on success, 0 on failure. * * The length is supposed to be significantly less that the buffer size. */ YAML_DECLARE(int) yaml_parser_update_buffer(yaml_parser_t *parser, size_t length) { int first = 1; assert(parser->read_handler); /* Read handler must be set. */ /* If the EOF flag is set and the raw buffer is empty, do nothing. */ if (parser->eof && parser->raw_buffer.pointer == parser->raw_buffer.last) return 1; /* Return if the buffer contains enough characters. */ if (parser->unread >= length) return 1; /* Determine the input encoding if it is not known yet. */ if (!parser->encoding) { if (!yaml_parser_determine_encoding(parser)) return 0; } /* Move the unread characters to the beginning of the buffer. */ if (parser->buffer.start < parser->buffer.pointer && parser->buffer.pointer < parser->buffer.last) { size_t size = parser->buffer.last - parser->buffer.pointer; memmove(parser->buffer.start, parser->buffer.pointer, size); parser->buffer.pointer = parser->buffer.start; parser->buffer.last = parser->buffer.start + size; } else if (parser->buffer.pointer == parser->buffer.last) { parser->buffer.pointer = parser->buffer.start; parser->buffer.last = parser->buffer.start; } /* Fill the buffer until it has enough characters. */ while (parser->unread < length) { /* Fill the raw buffer if necessary. */ if (!first || parser->raw_buffer.pointer == parser->raw_buffer.last) { if (!yaml_parser_update_raw_buffer(parser)) return 0; } first = 0; /* Decode the raw buffer. */ while (parser->raw_buffer.pointer != parser->raw_buffer.last) { unsigned int value = 0, value2 = 0; unsigned char octet; unsigned int width = 0; int low, high; size_t raw_unread = parser->raw_buffer.last - parser->raw_buffer.pointer; /* Decode the next character. */ switch (parser->encoding) { case YAML_UTF8_ENCODING: octet = parser->raw_buffer.pointer[0]; /* We can only disallow characters without the high bit set. Characters with the high bit set are required for invalid UTF-8 strings to be encoded, as we cannot rely on any backslash sequences working. */ if (! (octet == 0x09 || octet == 0x0A || octet == 0x0D || (octet >= 0x20 && octet != 0x7F) )) return yaml_parser_set_reader_error(parser, "Control characters are not allowed", parser->offset, value); parser->raw_buffer.pointer++; parser->offset++; *(parser->buffer.last++) = octet; break; case YAML_UTF16LE_ENCODING: case YAML_UTF16BE_ENCODING: low = (parser->encoding == YAML_UTF16LE_ENCODING ? 0 : 1); high = (parser->encoding == YAML_UTF16LE_ENCODING ? 1 : 0); /* * The UTF-16 encoding is not as simple as one might * naively think. Check RFC 2781 * (http://www.ietf.org/rfc/rfc2781.txt). * * Normally, two subsequent bytes describe a Unicode * character. However a special technique (called a * surrogate pair) is used for specifying character * values larger than 0xFFFF. * * A surrogate pair consists of two pseudo-characters: * high surrogate area (0xD800-0xDBFF) * low surrogate area (0xDC00-0xDFFF) * * The following formulas are used for decoding * and encoding characters using surrogate pairs: * * U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) * U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) * W1 = 110110yyyyyyyyyy * W2 = 110111xxxxxxxxxx * * where U is the character value, W1 is the high surrogate * area, W2 is the low surrogate area. */ /* Check for incomplete UTF-16 character. */ if (raw_unread < 2) { if (parser->eof) { return yaml_parser_set_reader_error(parser, "incomplete UTF-16 character", parser->offset, -1); } break; } /* Get the character. */ value = parser->raw_buffer.pointer[low] + (parser->raw_buffer.pointer[high] << 8); width = 2; /* Check for a high surrogate area. */ if ((value & 0xFC00) == 0xD800) { /* Check for incomplete surrogate pair. */ if (raw_unread < 4) { if (parser->eof) { /* trailing high surrogate */ width = 2; } else { break; /* Can't tell until we have more raw characters */ } } else { /* Get the next character. */ value2 = parser->raw_buffer.pointer[low+2] + (parser->raw_buffer.pointer[high+2] << 8); /* Check for a low surrogate area. */ if ((value2 & 0xFC00) == 0xDC00) { width = 4; /* Generate the value of the surrogate pair. */ value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF); } } } /* Check if the raw buffer contains enough bytes to form a character. */ /* * Check if the character is in the allowed range: * #x9 | #xA | #xD | [#x20-#x7E] (8 bit) * | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) * | [#x10000-#x10FFFF] (32 bit) */ /* Modified to allow all 16-bit values as \uNNNN may not work for some parsers for these values. */ if (! (value == 0x09 || value == 0x0A || value == 0x0D || (value >= 0x20 && value <= 0x7E) || (value == 0x85) || value >= 0xA0)) return yaml_parser_set_reader_error(parser, "Control characters are not allowed", parser->offset, value); /* Move the raw pointers. */ parser->raw_buffer.pointer += width; parser->offset += width; /* Finally put the character into the buffer. */ /* 0000 0000-0000 007F -> 0xxxxxxx */ if (value <= 0x7F) { *(parser->buffer.last++) = value; } /* 0000 0080-0000 07FF -> 110xxxxx 10xxxxxx */ else if (value <= 0x7FF) { *(parser->buffer.last++) = 0xC0 + (value >> 6); *(parser->buffer.last++) = 0x80 + (value & 0x3F); } /* 0000 0800-0000 FFFF -> 1110xxxx 10xxxxxx 10xxxxxx */ else if (value <= 0xFFFF) { *(parser->buffer.last++) = 0xE0 + (value >> 12); *(parser->buffer.last++) = 0x80 + ((value >> 6) & 0x3F); *(parser->buffer.last++) = 0x80 + (value & 0x3F); } /* 0001 0000-0010 FFFF -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ else { *(parser->buffer.last++) = 0xF0 + (value >> 18); *(parser->buffer.last++) = 0x80 + ((value >> 12) & 0x3F); *(parser->buffer.last++) = 0x80 + ((value >> 6) & 0x3F); *(parser->buffer.last++) = 0x80 + (value & 0x3F); } break; default: assert(1); /* Impossible. */ } parser->unread ++; } /* On EOF, put NUL into the buffer and return. */ if (parser->eof) { *(parser->buffer.last++) = '\0'; parser->unread ++; return 1; } } return 1; } flvmeta-1.2.2/src/libyaml/scanner.c000066400000000000000000002774521346231570700172200ustar00rootroot00000000000000 /* * Introduction * ************ * * The following notes assume that you are familiar with the YAML specification * (http://yaml.org/spec/cvs/current.html). We mostly follow it, although in * some cases we are less restrictive that it requires. * * The process of transforming a YAML stream into a sequence of events is * divided on two steps: Scanning and Parsing. * * The Scanner transforms the input stream into a sequence of tokens, while the * parser transform the sequence of tokens produced by the Scanner into a * sequence of parsing events. * * The Scanner is rather clever and complicated. The Parser, on the contrary, * is a straightforward implementation of a recursive-descendant parser (or, * LL(1) parser, as it is usually called). * * Actually there are two issues of Scanning that might be called "clever", the * rest is quite straightforward. The issues are "block collection start" and * "simple keys". Both issues are explained below in details. * * Here the Scanning step is explained and implemented. We start with the list * of all the tokens produced by the Scanner together with short descriptions. * * Now, tokens: * * STREAM-START(encoding) # The stream start. * STREAM-END # The stream end. * VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. * TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. * DOCUMENT-START # '---' * DOCUMENT-END # '...' * BLOCK-SEQUENCE-START # Indentation increase denoting a block * BLOCK-MAPPING-START # sequence or a block mapping. * BLOCK-END # Indentation decrease. * FLOW-SEQUENCE-START # '[' * FLOW-SEQUENCE-END # ']' * BLOCK-SEQUENCE-START # '{' * BLOCK-SEQUENCE-END # '}' * BLOCK-ENTRY # '-' * FLOW-ENTRY # ',' * KEY # '?' or nothing (simple keys). * VALUE # ':' * ALIAS(anchor) # '*anchor' * ANCHOR(anchor) # '&anchor' * TAG(handle,suffix) # '!handle!suffix' * SCALAR(value,style) # A scalar. * * The following two tokens are "virtual" tokens denoting the beginning and the * end of the stream: * * STREAM-START(encoding) * STREAM-END * * We pass the information about the input stream encoding with the * STREAM-START token. * * The next two tokens are responsible for tags: * * VERSION-DIRECTIVE(major,minor) * TAG-DIRECTIVE(handle,prefix) * * Example: * * %YAML 1.1 * %TAG ! !foo * %TAG !yaml! tag:yaml.org,2002: * --- * * The correspoding sequence of tokens: * * STREAM-START(utf-8) * VERSION-DIRECTIVE(1,1) * TAG-DIRECTIVE("!","!foo") * TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") * DOCUMENT-START * STREAM-END * * Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole * line. * * The document start and end indicators are represented by: * * DOCUMENT-START * DOCUMENT-END * * Note that if a YAML stream contains an implicit document (without '---' * and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be * produced. * * In the following examples, we present whole documents together with the * produced tokens. * * 1. An implicit document: * * 'a scalar' * * Tokens: * * STREAM-START(utf-8) * SCALAR("a scalar",single-quoted) * STREAM-END * * 2. An explicit document: * * --- * 'a scalar' * ... * * Tokens: * * STREAM-START(utf-8) * DOCUMENT-START * SCALAR("a scalar",single-quoted) * DOCUMENT-END * STREAM-END * * 3. Several documents in a stream: * * 'a scalar' * --- * 'another scalar' * --- * 'yet another scalar' * * Tokens: * * STREAM-START(utf-8) * SCALAR("a scalar",single-quoted) * DOCUMENT-START * SCALAR("another scalar",single-quoted) * DOCUMENT-START * SCALAR("yet another scalar",single-quoted) * STREAM-END * * We have already introduced the SCALAR token above. The following tokens are * used to describe aliases, anchors, tag, and scalars: * * ALIAS(anchor) * ANCHOR(anchor) * TAG(handle,suffix) * SCALAR(value,style) * * The following series of examples illustrate the usage of these tokens: * * 1. A recursive sequence: * * &A [ *A ] * * Tokens: * * STREAM-START(utf-8) * ANCHOR("A") * FLOW-SEQUENCE-START * ALIAS("A") * FLOW-SEQUENCE-END * STREAM-END * * 2. A tagged scalar: * * !!float "3.14" # A good approximation. * * Tokens: * * STREAM-START(utf-8) * TAG("!!","float") * SCALAR("3.14",double-quoted) * STREAM-END * * 3. Various scalar styles: * * --- # Implicit empty plain scalars do not produce tokens. * --- a plain scalar * --- 'a single-quoted scalar' * --- "a double-quoted scalar" * --- |- * a literal scalar * --- >- * a folded * scalar * * Tokens: * * STREAM-START(utf-8) * DOCUMENT-START * DOCUMENT-START * SCALAR("a plain scalar",plain) * DOCUMENT-START * SCALAR("a single-quoted scalar",single-quoted) * DOCUMENT-START * SCALAR("a double-quoted scalar",double-quoted) * DOCUMENT-START * SCALAR("a literal scalar",literal) * DOCUMENT-START * SCALAR("a folded scalar",folded) * STREAM-END * * Now it's time to review collection-related tokens. We will start with * flow collections: * * FLOW-SEQUENCE-START * FLOW-SEQUENCE-END * FLOW-MAPPING-START * FLOW-MAPPING-END * FLOW-ENTRY * KEY * VALUE * * The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and * FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' * correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the * indicators '?' and ':', which are used for denoting mapping keys and values, * are represented by the KEY and VALUE tokens. * * The following examples show flow collections: * * 1. A flow sequence: * * [item 1, item 2, item 3] * * Tokens: * * STREAM-START(utf-8) * FLOW-SEQUENCE-START * SCALAR("item 1",plain) * FLOW-ENTRY * SCALAR("item 2",plain) * FLOW-ENTRY * SCALAR("item 3",plain) * FLOW-SEQUENCE-END * STREAM-END * * 2. A flow mapping: * * { * a simple key: a value, # Note that the KEY token is produced. * ? a complex key: another value, * } * * Tokens: * * STREAM-START(utf-8) * FLOW-MAPPING-START * KEY * SCALAR("a simple key",plain) * VALUE * SCALAR("a value",plain) * FLOW-ENTRY * KEY * SCALAR("a complex key",plain) * VALUE * SCALAR("another value",plain) * FLOW-ENTRY * FLOW-MAPPING-END * STREAM-END * * A simple key is a key which is not denoted by the '?' indicator. Note that * the Scanner still produce the KEY token whenever it encounters a simple key. * * For scanning block collections, the following tokens are used (note that we * repeat KEY and VALUE here): * * BLOCK-SEQUENCE-START * BLOCK-MAPPING-START * BLOCK-END * BLOCK-ENTRY * KEY * VALUE * * The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation * increase that precedes a block collection (cf. the INDENT token in Python). * The token BLOCK-END denote indentation decrease that ends a block collection * (cf. the DEDENT token in Python). However YAML has some syntax pecularities * that makes detections of these tokens more complex. * * The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators * '-', '?', and ':' correspondingly. * * The following examples show how the tokens BLOCK-SEQUENCE-START, * BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: * * 1. Block sequences: * * - item 1 * - item 2 * - * - item 3.1 * - item 3.2 * - * key 1: value 1 * key 2: value 2 * * Tokens: * * STREAM-START(utf-8) * BLOCK-SEQUENCE-START * BLOCK-ENTRY * SCALAR("item 1",plain) * BLOCK-ENTRY * SCALAR("item 2",plain) * BLOCK-ENTRY * BLOCK-SEQUENCE-START * BLOCK-ENTRY * SCALAR("item 3.1",plain) * BLOCK-ENTRY * SCALAR("item 3.2",plain) * BLOCK-END * BLOCK-ENTRY * BLOCK-MAPPING-START * KEY * SCALAR("key 1",plain) * VALUE * SCALAR("value 1",plain) * KEY * SCALAR("key 2",plain) * VALUE * SCALAR("value 2",plain) * BLOCK-END * BLOCK-END * STREAM-END * * 2. Block mappings: * * a simple key: a value # The KEY token is produced here. * ? a complex key * : another value * a mapping: * key 1: value 1 * key 2: value 2 * a sequence: * - item 1 * - item 2 * * Tokens: * * STREAM-START(utf-8) * BLOCK-MAPPING-START * KEY * SCALAR("a simple key",plain) * VALUE * SCALAR("a value",plain) * KEY * SCALAR("a complex key",plain) * VALUE * SCALAR("another value",plain) * KEY * SCALAR("a mapping",plain) * BLOCK-MAPPING-START * KEY * SCALAR("key 1",plain) * VALUE * SCALAR("value 1",plain) * KEY * SCALAR("key 2",plain) * VALUE * SCALAR("value 2",plain) * BLOCK-END * KEY * SCALAR("a sequence",plain) * VALUE * BLOCK-SEQUENCE-START * BLOCK-ENTRY * SCALAR("item 1",plain) * BLOCK-ENTRY * SCALAR("item 2",plain) * BLOCK-END * BLOCK-END * STREAM-END * * YAML does not always require to start a new block collection from a new * line. If the current line contains only '-', '?', and ':' indicators, a new * block collection may start at the current line. The following examples * illustrate this case: * * 1. Collections in a sequence: * * - - item 1 * - item 2 * - key 1: value 1 * key 2: value 2 * - ? complex key * : complex value * * Tokens: * * STREAM-START(utf-8) * BLOCK-SEQUENCE-START * BLOCK-ENTRY * BLOCK-SEQUENCE-START * BLOCK-ENTRY * SCALAR("item 1",plain) * BLOCK-ENTRY * SCALAR("item 2",plain) * BLOCK-END * BLOCK-ENTRY * BLOCK-MAPPING-START * KEY * SCALAR("key 1",plain) * VALUE * SCALAR("value 1",plain) * KEY * SCALAR("key 2",plain) * VALUE * SCALAR("value 2",plain) * BLOCK-END * BLOCK-ENTRY * BLOCK-MAPPING-START * KEY * SCALAR("complex key") * VALUE * SCALAR("complex value") * BLOCK-END * BLOCK-END * STREAM-END * * 2. Collections in a mapping: * * ? a sequence * : - item 1 * - item 2 * ? a mapping * : key 1: value 1 * key 2: value 2 * * Tokens: * * STREAM-START(utf-8) * BLOCK-MAPPING-START * KEY * SCALAR("a sequence",plain) * VALUE * BLOCK-SEQUENCE-START * BLOCK-ENTRY * SCALAR("item 1",plain) * BLOCK-ENTRY * SCALAR("item 2",plain) * BLOCK-END * KEY * SCALAR("a mapping",plain) * VALUE * BLOCK-MAPPING-START * KEY * SCALAR("key 1",plain) * VALUE * SCALAR("value 1",plain) * KEY * SCALAR("key 2",plain) * VALUE * SCALAR("value 2",plain) * BLOCK-END * BLOCK-END * STREAM-END * * YAML also permits non-indented sequences if they are included into a block * mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: * * key: * - item 1 # BLOCK-SEQUENCE-START is NOT produced here. * - item 2 * * Tokens: * * STREAM-START(utf-8) * BLOCK-MAPPING-START * KEY * SCALAR("key",plain) * VALUE * BLOCK-ENTRY * SCALAR("item 1",plain) * BLOCK-ENTRY * SCALAR("item 2",plain) * BLOCK-END */ #include "yaml_private.h" /* * Ensure that the buffer contains the required number of characters. * Return 1 on success, 0 on failure (reader error or memory error). */ #define CACHE(parser,length) \ (parser->unread >= (length) \ ? 1 \ : yaml_parser_update_buffer(parser, (length))) /* * Advance the buffer pointer. */ #define SKIP(parser) \ (parser->mark.index ++, \ parser->mark.column ++, \ parser->unread --, \ parser->buffer.pointer ++) #define SKIPN(parser,n) \ (parser->mark.index ++, \ parser->mark.column ++, \ parser->unread --, \ parser->buffer.pointer += (n)) #define SKIP_LINE(parser) \ (IS_CRLF(parser->buffer) ? \ (parser->mark.index += 2, \ parser->mark.column = 0, \ parser->mark.line ++, \ parser->unread -= 2, \ parser->buffer.pointer += 2) : \ IS_BREAK(parser->buffer) ? \ (parser->mark.index ++, \ parser->mark.column = 0, \ parser->mark.line ++, \ parser->unread --, \ parser->buffer.pointer += WIDTH(parser->buffer)) : 0) /* * Copy a character to a string buffer and advance pointers. */ #define READ(parser,string) \ (STRING_EXTEND(parser,string) ? \ (COPY(string,parser->buffer), \ parser->mark.index ++, \ parser->mark.column ++, \ parser->unread --, \ 1) : 0) #define READN(parser,string,n) \ (STRING_EXTEND(parser,string) ? \ (COPYN(string,parser->buffer,n), \ parser->mark.index ++, \ parser->mark.column ++, \ parser->unread --, \ 1) : 0) /* * Copy a line break character to a string buffer and advance pointers. */ #define READ_LINE(parser,string) \ (STRING_EXTEND(parser,string) ? \ (((CHECK_AT(parser->buffer,'\r',0) \ && CHECK_AT(parser->buffer,'\n',1)) ? /* CR LF -> LF */ \ (*((string).pointer++) = (yaml_char_t) '\n', \ parser->buffer.pointer += 2, \ parser->mark.index += 2, \ parser->mark.column = 0, \ parser->mark.line ++, \ parser->unread -= 2) : \ (CHECK_AT(parser->buffer,'\r',0) \ || CHECK_AT(parser->buffer,'\n',0)) ? /* CR|LF -> LF */ \ (*((string).pointer++) = (yaml_char_t) '\n', \ parser->buffer.pointer ++, \ parser->mark.index ++, \ parser->mark.column = 0, \ parser->mark.line ++, \ parser->unread --) : \ (CHECK_AT(parser->buffer,'\xC2',0) \ && CHECK_AT(parser->buffer,'\x85',1)) ? /* NEL -> LF */ \ (*((string).pointer++) = (yaml_char_t) '\n', \ parser->buffer.pointer += 2, \ parser->mark.index ++, \ parser->mark.column = 0, \ parser->mark.line ++, \ parser->unread --) : \ (CHECK_AT(parser->buffer,'\xE2',0) && \ CHECK_AT(parser->buffer,'\x80',1) && \ (CHECK_AT(parser->buffer,'\xA8',2) || \ CHECK_AT(parser->buffer,'\xA9',2))) ? /* LS|PS -> LS|PS */ \ (*((string).pointer++) = *(parser->buffer.pointer++), \ *((string).pointer++) = *(parser->buffer.pointer++), \ *((string).pointer++) = *(parser->buffer.pointer++), \ parser->mark.index ++, \ parser->mark.column = 0, \ parser->mark.line ++, \ parser->unread --) : 0), \ 1) : 0) /* * Public API declarations. */ YAML_DECLARE(int) yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token); /* * Error handling. */ static int yaml_parser_set_scanner_error(yaml_parser_t *parser, const char *context, yaml_mark_t context_mark, const char *problem); /* * High-level token API. */ YAML_DECLARE(int) yaml_parser_fetch_more_tokens(yaml_parser_t *parser); static int yaml_parser_fetch_next_token(yaml_parser_t *parser); /* * Potential simple keys. */ static int yaml_parser_stale_simple_keys(yaml_parser_t *parser); static int yaml_parser_save_simple_key(yaml_parser_t *parser); static int yaml_parser_remove_simple_key(yaml_parser_t *parser); static int yaml_parser_increase_flow_level(yaml_parser_t *parser); static int yaml_parser_decrease_flow_level(yaml_parser_t *parser); /* * Indentation treatment. */ static int yaml_parser_roll_indent(yaml_parser_t *parser, int column, int number, yaml_token_type_t type, yaml_mark_t mark); static int yaml_parser_unroll_indent(yaml_parser_t *parser, int column); /* * Token fetchers. */ static int yaml_parser_fetch_stream_start(yaml_parser_t *parser); static int yaml_parser_fetch_stream_end(yaml_parser_t *parser); static int yaml_parser_fetch_directive(yaml_parser_t *parser); static int yaml_parser_fetch_document_indicator(yaml_parser_t *parser, yaml_token_type_t type); static int yaml_parser_fetch_flow_collection_start(yaml_parser_t *parser, yaml_token_type_t type); static int yaml_parser_fetch_flow_collection_end(yaml_parser_t *parser, yaml_token_type_t type); static int yaml_parser_fetch_flow_entry(yaml_parser_t *parser); static int yaml_parser_fetch_block_entry(yaml_parser_t *parser); static int yaml_parser_fetch_key(yaml_parser_t *parser); static int yaml_parser_fetch_value(yaml_parser_t *parser); static int yaml_parser_fetch_anchor(yaml_parser_t *parser, yaml_token_type_t type); static int yaml_parser_fetch_tag(yaml_parser_t *parser); static int yaml_parser_fetch_block_scalar(yaml_parser_t *parser, int literal); static int yaml_parser_fetch_flow_scalar(yaml_parser_t *parser, int single); static int yaml_parser_fetch_plain_scalar(yaml_parser_t *parser); /* * Token scanners. */ static int yaml_parser_scan_to_next_token(yaml_parser_t *parser); static int yaml_parser_scan_directive(yaml_parser_t *parser, yaml_token_t *token); static int yaml_parser_scan_directive_name(yaml_parser_t *parser, yaml_mark_t start_mark, yaml_char_t **name); static int yaml_parser_scan_version_directive_value(yaml_parser_t *parser, yaml_mark_t start_mark, int *major, int *minor); static int yaml_parser_scan_version_directive_number(yaml_parser_t *parser, yaml_mark_t start_mark, int *number); static int yaml_parser_scan_tag_directive_value(yaml_parser_t *parser, yaml_mark_t mark, yaml_char_t **handle, yaml_char_t **prefix); static int yaml_parser_scan_anchor(yaml_parser_t *parser, yaml_token_t *token, yaml_token_type_t type); static int yaml_parser_scan_tag(yaml_parser_t *parser, yaml_token_t *token); static int yaml_parser_scan_tag_handle(yaml_parser_t *parser, int directive, yaml_mark_t start_mark, yaml_char_t **handle); static int yaml_parser_scan_tag_uri(yaml_parser_t *parser, int directive, yaml_char_t *head, yaml_mark_t start_mark, yaml_char_t **uri); static int yaml_parser_scan_uri_escapes(yaml_parser_t *parser, int directive, yaml_mark_t start_mark, yaml_string_t *string); static int yaml_parser_scan_block_scalar(yaml_parser_t *parser, yaml_token_t *token, int literal); static int yaml_parser_scan_block_scalar_breaks(yaml_parser_t *parser, int *indent, yaml_string_t *breaks, yaml_mark_t start_mark, yaml_mark_t *end_mark); static int yaml_parser_scan_flow_scalar(yaml_parser_t *parser, yaml_token_t *token, int single); static int yaml_parser_scan_plain_scalar(yaml_parser_t *parser, yaml_token_t *token); /* * Get the next token. */ YAML_DECLARE(int) yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token) { assert(parser); /* Non-NULL parser object is expected. */ assert(token); /* Non-NULL token object is expected. */ /* Erase the token object. */ memset(token, 0, sizeof(yaml_token_t)); /* No tokens after STREAM-END or error. */ if (parser->stream_end_produced || parser->error) { return 1; } /* Ensure that the tokens queue contains enough tokens. */ if (!parser->token_available) { if (!yaml_parser_fetch_more_tokens(parser)) return 0; } /* Fetch the next token from the queue. */ *token = DEQUEUE(parser, parser->tokens); parser->token_available = 0; parser->tokens_parsed ++; if (token->type == YAML_STREAM_END_TOKEN) { parser->stream_end_produced = 1; } return 1; } /* * Set the scanner error and return 0. */ static int yaml_parser_set_scanner_error(yaml_parser_t *parser, const char *context, yaml_mark_t context_mark, const char *problem) { parser->error = YAML_SCANNER_ERROR; parser->context = context; parser->context_mark = context_mark; parser->problem = problem; parser->problem_mark = parser->mark; return 0; } /* * Ensure that the tokens queue contains at least one token which can be * returned to the Parser. */ YAML_DECLARE(int) yaml_parser_fetch_more_tokens(yaml_parser_t *parser) { int need_more_tokens; /* While we need more tokens to fetch, do it. */ while (1) { /* * Check if we really need to fetch more tokens. */ need_more_tokens = 0; if (parser->tokens.head == parser->tokens.tail) { /* Queue is empty. */ need_more_tokens = 1; } else { yaml_simple_key_t *simple_key; /* Check if any potential simple key may occupy the head position. */ if (!yaml_parser_stale_simple_keys(parser)) return 0; for (simple_key = parser->simple_keys.start; simple_key != parser->simple_keys.top; simple_key++) { if (simple_key->possible && simple_key->token_number == parser->tokens_parsed) { need_more_tokens = 1; break; } } } /* We are finished. */ if (!need_more_tokens) break; /* Fetch the next token. */ if (!yaml_parser_fetch_next_token(parser)) return 0; } parser->token_available = 1; return 1; } /* * The dispatcher for token fetchers. */ static int yaml_parser_fetch_next_token(yaml_parser_t *parser) { /* Ensure that the buffer is initialized. */ if (!CACHE(parser, 1)) return 0; /* Check if we just started scanning. Fetch STREAM-START then. */ if (!parser->stream_start_produced) return yaml_parser_fetch_stream_start(parser); /* Eat whitespaces and comments until we reach the next token. */ if (!yaml_parser_scan_to_next_token(parser)) return 0; /* Remove obsolete potential simple keys. */ if (!yaml_parser_stale_simple_keys(parser)) return 0; /* Check the indentation level against the current column. */ if (!yaml_parser_unroll_indent(parser, parser->mark.column)) return 0; /* * Ensure that the buffer contains at least 4 characters. 4 is the length * of the longest indicators ('--- ' and '... '). */ if (!CACHE(parser, 4)) return 0; /* Is it the end of the stream? */ if (IS_Z(parser->buffer)) return yaml_parser_fetch_stream_end(parser); /* Is it a directive? */ if (parser->mark.column == 0 && CHECK(parser->buffer, '%')) return yaml_parser_fetch_directive(parser); /* Is it the document start indicator? */ if (parser->mark.column == 0 && CHECK_AT(parser->buffer, '-', 0) && CHECK_AT(parser->buffer, '-', 1) && CHECK_AT(parser->buffer, '-', 2) && IS_BLANKZ_AT(parser->buffer, 3)) return yaml_parser_fetch_document_indicator(parser, YAML_DOCUMENT_START_TOKEN); /* Is it the document end indicator? */ if (parser->mark.column == 0 && CHECK_AT(parser->buffer, '.', 0) && CHECK_AT(parser->buffer, '.', 1) && CHECK_AT(parser->buffer, '.', 2) && IS_BLANKZ_AT(parser->buffer, 3)) return yaml_parser_fetch_document_indicator(parser, YAML_DOCUMENT_END_TOKEN); /* Is it the flow sequence start indicator? */ if (CHECK(parser->buffer, '[')) return yaml_parser_fetch_flow_collection_start(parser, YAML_FLOW_SEQUENCE_START_TOKEN); /* Is it the flow mapping start indicator? */ if (CHECK(parser->buffer, '{')) return yaml_parser_fetch_flow_collection_start(parser, YAML_FLOW_MAPPING_START_TOKEN); /* Is it the flow sequence end indicator? */ if (CHECK(parser->buffer, ']')) return yaml_parser_fetch_flow_collection_end(parser, YAML_FLOW_SEQUENCE_END_TOKEN); /* Is it the flow mapping end indicator? */ if (CHECK(parser->buffer, '}')) return yaml_parser_fetch_flow_collection_end(parser, YAML_FLOW_MAPPING_END_TOKEN); /* Is it the flow entry indicator? */ if (CHECK(parser->buffer, ',')) return yaml_parser_fetch_flow_entry(parser); /* Is it the block entry indicator? */ if (CHECK(parser->buffer, '-') && IS_BLANKZ_AT(parser->buffer, 1)) return yaml_parser_fetch_block_entry(parser); /* Is it the key indicator? */ if (CHECK(parser->buffer, '?') && (parser->flow_level || IS_BLANKZ_AT(parser->buffer, 1))) return yaml_parser_fetch_key(parser); /* Is it the value indicator? */ if (CHECK(parser->buffer, ':') && (parser->flow_level || IS_BLANKZ_AT(parser->buffer, 1))) return yaml_parser_fetch_value(parser); /* Is it an alias? */ if (CHECK(parser->buffer, '*')) return yaml_parser_fetch_anchor(parser, YAML_ALIAS_TOKEN); /* Is it an anchor? */ if (CHECK(parser->buffer, '&')) return yaml_parser_fetch_anchor(parser, YAML_ANCHOR_TOKEN); /* Is it a tag? */ if (CHECK(parser->buffer, '!')) return yaml_parser_fetch_tag(parser); /* Is it a literal scalar? */ if (CHECK(parser->buffer, '|') && !parser->flow_level) return yaml_parser_fetch_block_scalar(parser, 1); /* Is it a folded scalar? */ if (CHECK(parser->buffer, '>') && !parser->flow_level) return yaml_parser_fetch_block_scalar(parser, 0); /* Is it a single-quoted scalar? */ if (CHECK(parser->buffer, '\'')) return yaml_parser_fetch_flow_scalar(parser, 1); /* Is it a double-quoted scalar? */ if (CHECK(parser->buffer, '"')) return yaml_parser_fetch_flow_scalar(parser, 0); /* * Is it a plain scalar? * * A plain scalar may start with any non-blank characters except * * '-', '?', ':', ',', '[', ']', '{', '}', * '#', '&', '*', '!', '|', '>', '\'', '\"', * '%', '@', '`'. * * In the block context (and, for the '-' indicator, in the flow context * too), it may also start with the characters * * '-', '?', ':' * * if it is followed by a non-space character. * * The last rule is more restrictive than the specification requires. */ if (!(IS_BLANKZ(parser->buffer) || CHECK(parser->buffer, '-') || CHECK(parser->buffer, '?') || CHECK(parser->buffer, ':') || CHECK(parser->buffer, ',') || CHECK(parser->buffer, '[') || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '{') || CHECK(parser->buffer, '}') || CHECK(parser->buffer, '#') || CHECK(parser->buffer, '&') || CHECK(parser->buffer, '*') || CHECK(parser->buffer, '!') || CHECK(parser->buffer, '|') || CHECK(parser->buffer, '>') || CHECK(parser->buffer, '\'') || CHECK(parser->buffer, '"') || CHECK(parser->buffer, '%') || CHECK(parser->buffer, '@') || CHECK(parser->buffer, '`')) || (CHECK(parser->buffer, '-') && !IS_BLANK_AT(parser->buffer, 1)) || (!parser->flow_level && (CHECK(parser->buffer, '?') || CHECK(parser->buffer, ':')) && !IS_BLANKZ_AT(parser->buffer, 1))) return yaml_parser_fetch_plain_scalar(parser); /* * If we don't determine the token type so far, it is an error. */ return yaml_parser_set_scanner_error(parser, "while scanning for the next token", parser->mark, "found character that cannot start any token"); } /* * Check the list of potential simple keys and remove the positions that * cannot contain simple keys anymore. */ static int yaml_parser_stale_simple_keys(yaml_parser_t *parser) { yaml_simple_key_t *simple_key; /* Check for a potential simple key for each flow level. */ for (simple_key = parser->simple_keys.start; simple_key != parser->simple_keys.top; simple_key ++) { /* * The specification requires that a simple key * * - is limited to a single line, * - is shorter than 1024 characters. */ if (simple_key->possible && (simple_key->mark.line < parser->mark.line || simple_key->mark.index+1024 < parser->mark.index)) { /* Check if the potential simple key to be removed is required. */ if (simple_key->required) { return yaml_parser_set_scanner_error(parser, "while scanning a simple key", simple_key->mark, "could not find expected ':'"); } simple_key->possible = 0; } } return 1; } /* * Check if a simple key may start at the current position and add it if * needed. */ static int yaml_parser_save_simple_key(yaml_parser_t *parser) { /* * A simple key is required at the current position if the scanner is in * the block context and the current column coincides with the indentation * level. */ int required = (!parser->flow_level && parser->indent == (int)parser->mark.column); /* * A simple key is required only when it is the first token in the current * line. Therefore it is always allowed. But we add a check anyway. */ assert(parser->simple_key_allowed || !required); /* Impossible. */ /* * If the current position may start a simple key, save it. */ if (parser->simple_key_allowed) { yaml_simple_key_t simple_key; simple_key.possible = 1; simple_key.required = required; simple_key.token_number = parser->tokens_parsed + parser->tokens.tail - parser->tokens.head; simple_key.mark = parser->mark; if (!yaml_parser_remove_simple_key(parser)) return 0; *(parser->simple_keys.top-1) = simple_key; } return 1; } /* * Remove a potential simple key at the current flow level. */ static int yaml_parser_remove_simple_key(yaml_parser_t *parser) { yaml_simple_key_t *simple_key = parser->simple_keys.top-1; if (simple_key->possible) { /* If the key is required, it is an error. */ if (simple_key->required) { return yaml_parser_set_scanner_error(parser, "while scanning a simple key", simple_key->mark, "could not find expected ':'"); } } /* Remove the key from the stack. */ simple_key->possible = 0; return 1; } /* * Increase the flow level and resize the simple key list if needed. */ static int yaml_parser_increase_flow_level(yaml_parser_t *parser) { yaml_simple_key_t empty_simple_key = { 0, 0, 0, { 0, 0, 0 } }; /* Reset the simple key on the next level. */ if (!PUSH(parser, parser->simple_keys, empty_simple_key)) return 0; /* Increase the flow level. */ parser->flow_level++; return 1; } /* * Decrease the flow level. */ static int yaml_parser_decrease_flow_level(yaml_parser_t *parser) { yaml_simple_key_t dummy_key; /* Used to eliminate a compiler warning. */ if (parser->flow_level) { parser->flow_level --; dummy_key = POP(parser, parser->simple_keys); } return 1; } /* * Push the current indentation level to the stack and set the new level * the current column is greater than the indentation level. In this case, * append or insert the specified token into the token queue. * */ static int yaml_parser_roll_indent(yaml_parser_t *parser, int column, int number, yaml_token_type_t type, yaml_mark_t mark) { yaml_token_t token; /* In the flow context, do nothing. */ if (parser->flow_level) return 1; if (parser->indent < column) { /* * Push the current indentation level to the stack and set the new * indentation level. */ if (!PUSH(parser, parser->indents, parser->indent)) return 0; parser->indent = column; /* Create a token and insert it into the queue. */ TOKEN_INIT(token, type, mark, mark); if (number == -1) { if (!ENQUEUE(parser, parser->tokens, token)) return 0; } else { if (!QUEUE_INSERT(parser, parser->tokens, number - parser->tokens_parsed, token)) return 0; } } return 1; } /* * Pop indentation levels from the indents stack until the current level * becomes less or equal to the column. For each intendation level, append * the BLOCK-END token. */ static int yaml_parser_unroll_indent(yaml_parser_t *parser, int column) { yaml_token_t token; /* In the flow context, do nothing. */ if (parser->flow_level) return 1; /* Loop through the intendation levels in the stack. */ while (parser->indent > column) { /* Create a token and append it to the queue. */ TOKEN_INIT(token, YAML_BLOCK_END_TOKEN, parser->mark, parser->mark); if (!ENQUEUE(parser, parser->tokens, token)) return 0; /* Pop the indentation level. */ parser->indent = POP(parser, parser->indents); } return 1; } /* * Initialize the scanner and produce the STREAM-START token. */ static int yaml_parser_fetch_stream_start(yaml_parser_t *parser) { yaml_simple_key_t simple_key = { 0, 0, 0, { 0, 0, 0 } }; yaml_token_t token; /* Set the initial indentation. */ parser->indent = -1; /* Initialize the simple key stack. */ if (!PUSH(parser, parser->simple_keys, simple_key)) return 0; /* A simple key is allowed at the beginning of the stream. */ parser->simple_key_allowed = 1; /* We have started. */ parser->stream_start_produced = 1; /* Create the STREAM-START token and append it to the queue. */ STREAM_START_TOKEN_INIT(token, parser->encoding, parser->mark, parser->mark); if (!ENQUEUE(parser, parser->tokens, token)) return 0; return 1; } /* * Produce the STREAM-END token and shut down the scanner. */ static int yaml_parser_fetch_stream_end(yaml_parser_t *parser) { yaml_token_t token; /* Force new line. */ if (parser->mark.column != 0) { parser->mark.column = 0; parser->mark.line ++; } /* Reset the indentation level. */ if (!yaml_parser_unroll_indent(parser, -1)) return 0; /* Reset simple keys. */ if (!yaml_parser_remove_simple_key(parser)) return 0; parser->simple_key_allowed = 0; /* Create the STREAM-END token and append it to the queue. */ STREAM_END_TOKEN_INIT(token, parser->mark, parser->mark); if (!ENQUEUE(parser, parser->tokens, token)) return 0; return 1; } /* * Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. */ static int yaml_parser_fetch_directive(yaml_parser_t *parser) { yaml_token_t token; /* Reset the indentation level. */ if (!yaml_parser_unroll_indent(parser, -1)) return 0; /* Reset simple keys. */ if (!yaml_parser_remove_simple_key(parser)) return 0; parser->simple_key_allowed = 0; /* Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. */ if (!yaml_parser_scan_directive(parser, &token)) return 0; /* Append the token to the queue. */ if (!ENQUEUE(parser, parser->tokens, token)) { yaml_token_delete(&token); return 0; } return 1; } /* * Produce the DOCUMENT-START or DOCUMENT-END token. */ static int yaml_parser_fetch_document_indicator(yaml_parser_t *parser, yaml_token_type_t type) { yaml_mark_t start_mark, end_mark; yaml_token_t token; /* Reset the indentation level. */ if (!yaml_parser_unroll_indent(parser, -1)) return 0; /* Reset simple keys. */ if (!yaml_parser_remove_simple_key(parser)) return 0; parser->simple_key_allowed = 0; /* Consume the token. */ start_mark = parser->mark; SKIP(parser); SKIP(parser); SKIP(parser); end_mark = parser->mark; /* Create the DOCUMENT-START or DOCUMENT-END token. */ TOKEN_INIT(token, type, start_mark, end_mark); /* Append the token to the queue. */ if (!ENQUEUE(parser, parser->tokens, token)) return 0; return 1; } /* * Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. */ static int yaml_parser_fetch_flow_collection_start(yaml_parser_t *parser, yaml_token_type_t type) { yaml_mark_t start_mark, end_mark; yaml_token_t token; /* The indicators '[' and '{' may start a simple key. */ if (!yaml_parser_save_simple_key(parser)) return 0; /* Increase the flow level. */ if (!yaml_parser_increase_flow_level(parser)) return 0; /* A simple key may follow the indicators '[' and '{'. */ parser->simple_key_allowed = 1; /* Consume the token. */ start_mark = parser->mark; SKIP(parser); end_mark = parser->mark; /* Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. */ TOKEN_INIT(token, type, start_mark, end_mark); /* Append the token to the queue. */ if (!ENQUEUE(parser, parser->tokens, token)) return 0; return 1; } /* * Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. */ static int yaml_parser_fetch_flow_collection_end(yaml_parser_t *parser, yaml_token_type_t type) { yaml_mark_t start_mark, end_mark; yaml_token_t token; /* Reset any potential simple key on the current flow level. */ if (!yaml_parser_remove_simple_key(parser)) return 0; /* Decrease the flow level. */ if (!yaml_parser_decrease_flow_level(parser)) return 0; /* No simple keys after the indicators ']' and '}'. */ parser->simple_key_allowed = 0; /* Consume the token. */ start_mark = parser->mark; SKIP(parser); end_mark = parser->mark; /* Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. */ TOKEN_INIT(token, type, start_mark, end_mark); /* Append the token to the queue. */ if (!ENQUEUE(parser, parser->tokens, token)) return 0; return 1; } /* * Produce the FLOW-ENTRY token. */ static int yaml_parser_fetch_flow_entry(yaml_parser_t *parser) { yaml_mark_t start_mark, end_mark; yaml_token_t token; /* Reset any potential simple keys on the current flow level. */ if (!yaml_parser_remove_simple_key(parser)) return 0; /* Simple keys are allowed after ','. */ parser->simple_key_allowed = 1; /* Consume the token. */ start_mark = parser->mark; SKIP(parser); end_mark = parser->mark; /* Create the FLOW-ENTRY token and append it to the queue. */ TOKEN_INIT(token, YAML_FLOW_ENTRY_TOKEN, start_mark, end_mark); if (!ENQUEUE(parser, parser->tokens, token)) return 0; return 1; } /* * Produce the BLOCK-ENTRY token. */ static int yaml_parser_fetch_block_entry(yaml_parser_t *parser) { yaml_mark_t start_mark, end_mark; yaml_token_t token; /* Check if the scanner is in the block context. */ if (!parser->flow_level) { /* Check if we are allowed to start a new entry. */ if (!parser->simple_key_allowed) { return yaml_parser_set_scanner_error(parser, NULL, parser->mark, "block sequence entries are not allowed in this context"); } /* Add the BLOCK-SEQUENCE-START token if needed. */ if (!yaml_parser_roll_indent(parser, parser->mark.column, -1, YAML_BLOCK_SEQUENCE_START_TOKEN, parser->mark)) return 0; } else { /* * It is an error for the '-' indicator to occur in the flow context, * but we let the Parser detect and report about it because the Parser * is able to point to the context. */ } /* Reset any potential simple keys on the current flow level. */ if (!yaml_parser_remove_simple_key(parser)) return 0; /* Simple keys are allowed after '-'. */ parser->simple_key_allowed = 1; /* Consume the token. */ start_mark = parser->mark; SKIP(parser); end_mark = parser->mark; /* Create the BLOCK-ENTRY token and append it to the queue. */ TOKEN_INIT(token, YAML_BLOCK_ENTRY_TOKEN, start_mark, end_mark); if (!ENQUEUE(parser, parser->tokens, token)) return 0; return 1; } /* * Produce the KEY token. */ static int yaml_parser_fetch_key(yaml_parser_t *parser) { yaml_mark_t start_mark, end_mark; yaml_token_t token; /* In the block context, additional checks are required. */ if (!parser->flow_level) { /* Check if we are allowed to start a new key (not nessesary simple). */ if (!parser->simple_key_allowed) { return yaml_parser_set_scanner_error(parser, NULL, parser->mark, "mapping keys are not allowed in this context"); } /* Add the BLOCK-MAPPING-START token if needed. */ if (!yaml_parser_roll_indent(parser, parser->mark.column, -1, YAML_BLOCK_MAPPING_START_TOKEN, parser->mark)) return 0; } /* Reset any potential simple keys on the current flow level. */ if (!yaml_parser_remove_simple_key(parser)) return 0; /* Simple keys are allowed after '?' in the block context. */ parser->simple_key_allowed = (!parser->flow_level); /* Consume the token. */ start_mark = parser->mark; SKIP(parser); end_mark = parser->mark; /* Create the KEY token and append it to the queue. */ TOKEN_INIT(token, YAML_KEY_TOKEN, start_mark, end_mark); if (!ENQUEUE(parser, parser->tokens, token)) return 0; return 1; } /* * Produce the VALUE token. */ static int yaml_parser_fetch_value(yaml_parser_t *parser) { yaml_mark_t start_mark, end_mark; yaml_token_t token; yaml_simple_key_t *simple_key = parser->simple_keys.top-1; /* Have we found a simple key? */ if (simple_key->possible) { /* Create the KEY token and insert it into the queue. */ TOKEN_INIT(token, YAML_KEY_TOKEN, simple_key->mark, simple_key->mark); if (!QUEUE_INSERT(parser, parser->tokens, simple_key->token_number - parser->tokens_parsed, token)) return 0; /* In the block context, we may need to add the BLOCK-MAPPING-START token. */ if (!yaml_parser_roll_indent(parser, simple_key->mark.column, simple_key->token_number, YAML_BLOCK_MAPPING_START_TOKEN, simple_key->mark)) return 0; /* Remove the simple key. */ simple_key->possible = 0; /* A simple key cannot follow another simple key. */ parser->simple_key_allowed = 0; } else { /* The ':' indicator follows a complex key. */ /* In the block context, extra checks are required. */ if (!parser->flow_level) { /* Check if we are allowed to start a complex value. */ if (!parser->simple_key_allowed) { return yaml_parser_set_scanner_error(parser, NULL, parser->mark, "mapping values are not allowed in this context"); } /* Add the BLOCK-MAPPING-START token if needed. */ if (!yaml_parser_roll_indent(parser, parser->mark.column, -1, YAML_BLOCK_MAPPING_START_TOKEN, parser->mark)) return 0; } /* Simple keys after ':' are allowed in the block context. */ parser->simple_key_allowed = (!parser->flow_level); } /* Consume the token. */ start_mark = parser->mark; SKIP(parser); end_mark = parser->mark; /* Create the VALUE token and append it to the queue. */ TOKEN_INIT(token, YAML_VALUE_TOKEN, start_mark, end_mark); if (!ENQUEUE(parser, parser->tokens, token)) return 0; return 1; } /* * Produce the ALIAS or ANCHOR token. */ static int yaml_parser_fetch_anchor(yaml_parser_t *parser, yaml_token_type_t type) { yaml_token_t token; /* An anchor or an alias could be a simple key. */ if (!yaml_parser_save_simple_key(parser)) return 0; /* A simple key cannot follow an anchor or an alias. */ parser->simple_key_allowed = 0; /* Create the ALIAS or ANCHOR token and append it to the queue. */ if (!yaml_parser_scan_anchor(parser, &token, type)) return 0; if (!ENQUEUE(parser, parser->tokens, token)) { yaml_token_delete(&token); return 0; } return 1; } /* * Produce the TAG token. */ static int yaml_parser_fetch_tag(yaml_parser_t *parser) { yaml_token_t token; /* A tag could be a simple key. */ if (!yaml_parser_save_simple_key(parser)) return 0; /* A simple key cannot follow a tag. */ parser->simple_key_allowed = 0; /* Create the TAG token and append it to the queue. */ if (!yaml_parser_scan_tag(parser, &token)) return 0; if (!ENQUEUE(parser, parser->tokens, token)) { yaml_token_delete(&token); return 0; } return 1; } /* * Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. */ static int yaml_parser_fetch_block_scalar(yaml_parser_t *parser, int literal) { yaml_token_t token; /* Remove any potential simple keys. */ if (!yaml_parser_remove_simple_key(parser)) return 0; /* A simple key may follow a block scalar. */ parser->simple_key_allowed = 1; /* Create the SCALAR token and append it to the queue. */ if (!yaml_parser_scan_block_scalar(parser, &token, literal)) return 0; if (!ENQUEUE(parser, parser->tokens, token)) { yaml_token_delete(&token); return 0; } return 1; } /* * Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. */ static int yaml_parser_fetch_flow_scalar(yaml_parser_t *parser, int single) { yaml_token_t token; /* A plain scalar could be a simple key. */ if (!yaml_parser_save_simple_key(parser)) return 0; /* A simple key cannot follow a flow scalar. */ parser->simple_key_allowed = 0; /* Create the SCALAR token and append it to the queue. */ if (!yaml_parser_scan_flow_scalar(parser, &token, single)) return 0; if (!ENQUEUE(parser, parser->tokens, token)) { yaml_token_delete(&token); return 0; } return 1; } /* * Produce the SCALAR(...,plain) token. */ static int yaml_parser_fetch_plain_scalar(yaml_parser_t *parser) { yaml_token_t token; /* A plain scalar could be a simple key. */ if (!yaml_parser_save_simple_key(parser)) return 0; /* A simple key cannot follow a flow scalar. */ parser->simple_key_allowed = 0; /* Create the SCALAR token and append it to the queue. */ if (!yaml_parser_scan_plain_scalar(parser, &token)) return 0; if (!ENQUEUE(parser, parser->tokens, token)) { yaml_token_delete(&token); return 0; } return 1; } /* * Eat whitespaces and comments until the next token is found. */ static int yaml_parser_scan_to_next_token(yaml_parser_t *parser) { /* Until the next token is not found. */ while (1) { /* Allow the BOM mark to start a line. */ if (!CACHE(parser, 1)) return 0; if (parser->mark.column == 0 && IS_BOM(parser->buffer)) SKIPN(parser,3); /* UTF-8 BOM is 3 bytes */ /* * Eat whitespaces. * * Tabs are allowed: * * - in the flow context; * - in the block context, but not at the beginning of the line or * after '-', '?', or ':' (complex value). */ if (!CACHE(parser, 1)) return 0; while (CHECK(parser->buffer,' ') || ((parser->flow_level || !parser->simple_key_allowed) && CHECK(parser->buffer, '\t'))) { SKIP(parser); if (!CACHE(parser, 1)) return 0; } /* Eat a comment until a line break. */ if (CHECK(parser->buffer, '#')) { while (!IS_BREAKZ(parser->buffer)) { SKIP(parser); if (!CACHE(parser, 1)) return 0; } } /* If it is a line break, eat it. */ if (IS_BREAK(parser->buffer)) { if (!CACHE(parser, 2)) return 0; SKIP_LINE(parser); /* In the block context, a new line may start a simple key. */ if (!parser->flow_level) { parser->simple_key_allowed = 1; } } else { /* We have found a token. */ break; } } return 1; } /* * Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. * * Scope: * %YAML 1.1 # a comment \n * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * %TAG !yaml! tag:yaml.org,2002: \n * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ int yaml_parser_scan_directive(yaml_parser_t *parser, yaml_token_t *token) { yaml_mark_t start_mark, end_mark; yaml_char_t *name = NULL; int major, minor; yaml_char_t *handle = NULL, *prefix = NULL; /* Eat '%'. */ start_mark = parser->mark; SKIP(parser); /* Scan the directive name. */ if (!yaml_parser_scan_directive_name(parser, start_mark, &name)) goto error; /* Is it a YAML directive? */ if (strcmp((char *)name, "YAML") == 0) { /* Scan the VERSION directive value. */ if (!yaml_parser_scan_version_directive_value(parser, start_mark, &major, &minor)) goto error; end_mark = parser->mark; /* Create a VERSION-DIRECTIVE token. */ VERSION_DIRECTIVE_TOKEN_INIT(*token, major, minor, start_mark, end_mark); } /* Is it a TAG directive? */ else if (strcmp((char *)name, "TAG") == 0) { /* Scan the TAG directive value. */ if (!yaml_parser_scan_tag_directive_value(parser, start_mark, &handle, &prefix)) goto error; end_mark = parser->mark; /* Create a TAG-DIRECTIVE token. */ TAG_DIRECTIVE_TOKEN_INIT(*token, handle, prefix, start_mark, end_mark); } /* Unknown directive. */ else { yaml_parser_set_scanner_error(parser, "while scanning a directive", start_mark, "found uknown directive name"); goto error; } /* Eat the rest of the line including any comments. */ if (!CACHE(parser, 1)) goto error; while (IS_BLANK(parser->buffer)) { SKIP(parser); if (!CACHE(parser, 1)) goto error; } if (CHECK(parser->buffer, '#')) { while (!IS_BREAKZ(parser->buffer)) { SKIP(parser); if (!CACHE(parser, 1)) goto error; } } /* Check if we are at the end of the line. */ if (!IS_BREAKZ(parser->buffer)) { yaml_parser_set_scanner_error(parser, "while scanning a directive", start_mark, "did not find expected comment or line break"); goto error; } /* Eat a line break. */ if (IS_BREAK(parser->buffer)) { if (!CACHE(parser, 2)) goto error; SKIP_LINE(parser); } yaml_free(name); return 1; error: yaml_free(prefix); yaml_free(handle); yaml_free(name); return 0; } /* * Scan the directive name. * * Scope: * %YAML 1.1 # a comment \n * ^^^^ * %TAG !yaml! tag:yaml.org,2002: \n * ^^^ */ static int yaml_parser_scan_directive_name(yaml_parser_t *parser, yaml_mark_t start_mark, yaml_char_t **name) { yaml_string_t string = NULL_STRING; if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; /* Consume the directive name. */ if (!CACHE(parser, 1)) goto error; while (IS_ALPHA(parser->buffer)) { if (!READ(parser, string)) goto error; if (!CACHE(parser, 1)) goto error; } /* Check if the name is empty. */ if (string.start == string.pointer) { yaml_parser_set_scanner_error(parser, "while scanning a directive", start_mark, "could not find expected directive name"); goto error; } /* Check for an blank character after the name. */ if (!IS_BLANKZ(parser->buffer)) { yaml_parser_set_scanner_error(parser, "while scanning a directive", start_mark, "found unexpected non-alphabetical character"); goto error; } *name = string.start; return 1; error: STRING_DEL(parser, string); return 0; } /* * Scan the value of VERSION-DIRECTIVE. * * Scope: * %YAML 1.1 # a comment \n * ^^^^^^ */ static int yaml_parser_scan_version_directive_value(yaml_parser_t *parser, yaml_mark_t start_mark, int *major, int *minor) { /* Eat whitespaces. */ if (!CACHE(parser, 1)) return 0; while (IS_BLANK(parser->buffer)) { SKIP(parser); if (!CACHE(parser, 1)) return 0; } /* Consume the major version number. */ if (!yaml_parser_scan_version_directive_number(parser, start_mark, major)) return 0; /* Eat '.'. */ if (!CHECK(parser->buffer, '.')) { return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", start_mark, "did not find expected digit or '.' character"); } SKIP(parser); /* Consume the minor version number. */ if (!yaml_parser_scan_version_directive_number(parser, start_mark, minor)) return 0; return 1; } #define MAX_NUMBER_LENGTH 9 /* * Scan the version number of VERSION-DIRECTIVE. * * Scope: * %YAML 1.1 # a comment \n * ^ * %YAML 1.1 # a comment \n * ^ */ static int yaml_parser_scan_version_directive_number(yaml_parser_t *parser, yaml_mark_t start_mark, int *number) { int value = 0; size_t length = 0; /* Repeat while the next character is digit. */ if (!CACHE(parser, 1)) return 0; while (IS_DIGIT(parser->buffer)) { /* Check if the number is too long. */ if (++length > MAX_NUMBER_LENGTH) { return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", start_mark, "found extremely long version number"); } value = value*10 + AS_DIGIT(parser->buffer); SKIP(parser); if (!CACHE(parser, 1)) return 0; } /* Check if the number was present. */ if (!length) { return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", start_mark, "did not find expected version number"); } *number = value; return 1; } /* * Scan the value of a TAG-DIRECTIVE token. * * Scope: * %TAG !yaml! tag:yaml.org,2002: \n * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ static int yaml_parser_scan_tag_directive_value(yaml_parser_t *parser, yaml_mark_t start_mark, yaml_char_t **handle, yaml_char_t **prefix) { yaml_char_t *handle_value = NULL; yaml_char_t *prefix_value = NULL; /* Eat whitespaces. */ if (!CACHE(parser, 1)) goto error; while (IS_BLANK(parser->buffer)) { SKIP(parser); if (!CACHE(parser, 1)) goto error; } /* Scan a handle. */ if (!yaml_parser_scan_tag_handle(parser, 1, start_mark, &handle_value)) goto error; /* Expect a whitespace. */ if (!CACHE(parser, 1)) goto error; if (!IS_BLANK(parser->buffer)) { yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", start_mark, "did not find expected whitespace"); goto error; } /* Eat whitespaces. */ while (IS_BLANK(parser->buffer)) { SKIP(parser); if (!CACHE(parser, 1)) goto error; } /* Scan a prefix. */ if (!yaml_parser_scan_tag_uri(parser, 1, NULL, start_mark, &prefix_value)) goto error; /* Expect a whitespace or line break. */ if (!CACHE(parser, 1)) goto error; if (!IS_BLANKZ(parser->buffer)) { yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", start_mark, "did not find expected whitespace or line break"); goto error; } *handle = handle_value; *prefix = prefix_value; return 1; error: yaml_free(handle_value); yaml_free(prefix_value); return 0; } static int yaml_parser_scan_anchor(yaml_parser_t *parser, yaml_token_t *token, yaml_token_type_t type) { int length = 0; yaml_mark_t start_mark, end_mark; yaml_string_t string = NULL_STRING; if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; /* Eat the indicator character. */ start_mark = parser->mark; SKIP(parser); /* Consume the value. */ if (!CACHE(parser, 1)) goto error; while (IS_ALPHA(parser->buffer)) { if (!READ(parser, string)) goto error; if (!CACHE(parser, 1)) goto error; length ++; } end_mark = parser->mark; /* * Check if length of the anchor is greater than 0 and it is followed by * a whitespace character or one of the indicators: * * '?', ':', ',', ']', '}', '%', '@', '`'. */ if (!length || !(IS_BLANKZ(parser->buffer) || CHECK(parser->buffer, '?') || CHECK(parser->buffer, ':') || CHECK(parser->buffer, ',') || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '}') || CHECK(parser->buffer, '%') || CHECK(parser->buffer, '@') || CHECK(parser->buffer, '`'))) { yaml_parser_set_scanner_error(parser, type == YAML_ANCHOR_TOKEN ? "while scanning an anchor" : "while scanning an alias", start_mark, "did not find expected alphabetic or numeric character"); goto error; } /* Create a token. */ if (type == YAML_ANCHOR_TOKEN) { ANCHOR_TOKEN_INIT(*token, string.start, start_mark, end_mark); } else { ALIAS_TOKEN_INIT(*token, string.start, start_mark, end_mark); } return 1; error: STRING_DEL(parser, string); return 0; } /* * Scan a TAG token. */ static int yaml_parser_scan_tag(yaml_parser_t *parser, yaml_token_t *token) { yaml_char_t *handle = NULL; yaml_char_t *suffix = NULL; yaml_mark_t start_mark, end_mark; start_mark = parser->mark; /* Check if the tag is in the canonical form. */ if (!CACHE(parser, 2)) goto error; if (CHECK_AT(parser->buffer, '<', 1)) { /* Set the handle to '' */ handle = yaml_malloc(1); if (!handle) goto error; handle[0] = '\0'; /* Eat '!<' */ SKIP(parser); SKIP(parser); /* Consume the tag value. */ if (!yaml_parser_scan_tag_uri(parser, 0, NULL, start_mark, &suffix)) goto error; /* Check for '>' and eat it. */ if (!CHECK(parser->buffer, '>')) { yaml_parser_set_scanner_error(parser, "while scanning a tag", start_mark, "did not find the expected '>'"); goto error; } SKIP(parser); } else { /* The tag has either the '!suffix' or the '!handle!suffix' form. */ /* First, try to scan a handle. */ if (!yaml_parser_scan_tag_handle(parser, 0, start_mark, &handle)) goto error; /* Check if it is, indeed, handle. */ if (handle[0] == '!' && handle[1] != '\0' && handle[strlen((char *)handle)-1] == '!') { /* Scan the suffix now. */ if (!yaml_parser_scan_tag_uri(parser, 0, NULL, start_mark, &suffix)) goto error; } else { /* It wasn't a handle after all. Scan the rest of the tag. */ if (!yaml_parser_scan_tag_uri(parser, 0, handle, start_mark, &suffix)) goto error; /* Set the handle to '!'. */ yaml_free(handle); handle = yaml_malloc(2); if (!handle) goto error; handle[0] = '!'; handle[1] = '\0'; /* * A special case: the '!' tag. Set the handle to '' and the * suffix to '!'. */ if (suffix[0] == '\0') { yaml_char_t *tmp = handle; handle = suffix; suffix = tmp; } } } /* Check the character which ends the tag. */ if (!CACHE(parser, 1)) goto error; if (!IS_BLANKZ(parser->buffer)) { yaml_parser_set_scanner_error(parser, "while scanning a tag", start_mark, "did not find expected whitespace or line break"); goto error; } end_mark = parser->mark; /* Create a token. */ TAG_TOKEN_INIT(*token, handle, suffix, start_mark, end_mark); return 1; error: yaml_free(handle); yaml_free(suffix); return 0; } /* * Scan a tag handle. */ static int yaml_parser_scan_tag_handle(yaml_parser_t *parser, int directive, yaml_mark_t start_mark, yaml_char_t **handle) { yaml_string_t string = NULL_STRING; if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; /* Check the initial '!' character. */ if (!CACHE(parser, 1)) goto error; if (!CHECK(parser->buffer, '!')) { yaml_parser_set_scanner_error(parser, directive ? "while scanning a tag directive" : "while scanning a tag", start_mark, "did not find expected '!'"); goto error; } /* Copy the '!' character. */ if (!READ(parser, string)) goto error; /* Copy all subsequent alphabetical and numerical characters. */ if (!CACHE(parser, 1)) goto error; while (IS_ALPHA(parser->buffer)) { if (!READ(parser, string)) goto error; if (!CACHE(parser, 1)) goto error; } /* Check if the trailing character is '!' and copy it. */ if (CHECK(parser->buffer, '!')) { if (!READ(parser, string)) goto error; } else { /* * It's either the '!' tag or not really a tag handle. If it's a %TAG * directive, it's an error. If it's a tag token, it must be a part of * URI. */ if (directive && !(string.start[0] == '!' && string.start[1] == '\0')) { yaml_parser_set_scanner_error(parser, "while parsing a tag directive", start_mark, "did not find expected '!'"); goto error; } } *handle = string.start; return 1; error: STRING_DEL(parser, string); return 0; } /* * Scan a tag. */ static int yaml_parser_scan_tag_uri(yaml_parser_t *parser, int directive, yaml_char_t *head, yaml_mark_t start_mark, yaml_char_t **uri) { size_t length = head ? strlen((char *)head) : 0; yaml_string_t string = NULL_STRING; if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; /* Resize the string to include the head. */ while (string.end - string.start <= (int)length) { if (!yaml_string_extend(&string.start, &string.pointer, &string.end)) { parser->error = YAML_MEMORY_ERROR; goto error; } } /* * Copy the head if needed. * * Note that we don't copy the leading '!' character. */ if (length > 1) { memcpy(string.start, head+1, length-1); string.pointer += length-1; } /* Scan the tag. */ if (!CACHE(parser, 1)) goto error; /* * The set of characters that may appear in URI is as follows: * * '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', * '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']', * '%'. */ while (IS_ALPHA(parser->buffer) || CHECK(parser->buffer, ';') || CHECK(parser->buffer, '/') || CHECK(parser->buffer, '?') || CHECK(parser->buffer, ':') || CHECK(parser->buffer, '@') || CHECK(parser->buffer, '&') || CHECK(parser->buffer, '=') || CHECK(parser->buffer, '+') || CHECK(parser->buffer, '$') || CHECK(parser->buffer, ',') || CHECK(parser->buffer, '.') || CHECK(parser->buffer, '!') || CHECK(parser->buffer, '~') || CHECK(parser->buffer, '*') || CHECK(parser->buffer, '\'') || CHECK(parser->buffer, '(') || CHECK(parser->buffer, ')') || CHECK(parser->buffer, '[') || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '%')) { /* Check if it is a URI-escape sequence. */ if (CHECK(parser->buffer, '%')) { if (!yaml_parser_scan_uri_escapes(parser, directive, start_mark, &string)) goto error; } else { if (!READ(parser, string)) goto error; } length ++; if (!CACHE(parser, 1)) goto error; } /* Check if the tag is non-empty. */ if (!length) { if (!STRING_EXTEND(parser, string)) goto error; yaml_parser_set_scanner_error(parser, directive ? "while parsing a %TAG directive" : "while parsing a tag", start_mark, "did not find expected tag URI"); goto error; } *uri = string.start; return 1; error: STRING_DEL(parser, string); return 0; } /* * Decode an URI-escape sequence corresponding to a single UTF-8 character. */ static int yaml_parser_scan_uri_escapes(yaml_parser_t *parser, int directive, yaml_mark_t start_mark, yaml_string_t *string) { unsigned char octet = 0; /* Check for a URI-escaped octet. */ if (!CACHE(parser, 3)) return 0; if (!(CHECK(parser->buffer, '%') && IS_HEX_AT(parser->buffer, 1) && IS_HEX_AT(parser->buffer, 2))) { return yaml_parser_set_scanner_error(parser, directive ? "while parsing a %TAG directive" : "while parsing a tag", start_mark, "did not find URI escaped octet"); } /* Get the octet. */ octet = (AS_HEX_AT(parser->buffer, 1) << 4) + AS_HEX_AT(parser->buffer, 2); /* Copy the octet and move the pointers. */ *(string->pointer++) = octet; SKIP(parser); SKIP(parser); SKIP(parser); return 1; } /* * Scan a block scalar. */ static int yaml_parser_scan_block_scalar(yaml_parser_t *parser, yaml_token_t *token, int literal) { yaml_mark_t start_mark; yaml_mark_t end_mark; yaml_string_t string = NULL_STRING; yaml_string_t leading_break = NULL_STRING; yaml_string_t trailing_breaks = NULL_STRING; int chomping = 0; int increment = 0; int indent = 0; int leading_blank = 0; int trailing_blank = 0; if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error; if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error; /* Eat the indicator '|' or '>'. */ start_mark = parser->mark; SKIP(parser); /* Scan the additional block scalar indicators. */ if (!CACHE(parser, 1)) goto error; /* Check for a chomping indicator. */ if (CHECK(parser->buffer, '+') || CHECK(parser->buffer, '-')) { /* Set the chomping method and eat the indicator. */ chomping = CHECK(parser->buffer, '+') ? +1 : -1; SKIP(parser); /* Check for an indentation indicator. */ if (!CACHE(parser, 1)) goto error; if (IS_DIGIT(parser->buffer)) { /* Check that the intendation is greater than 0. */ if (CHECK(parser->buffer, '0')) { yaml_parser_set_scanner_error(parser, "while scanning a block scalar", start_mark, "found an intendation indicator equal to 0"); goto error; } /* Get the intendation level and eat the indicator. */ increment = AS_DIGIT(parser->buffer); SKIP(parser); } } /* Do the same as above, but in the opposite order. */ else if (IS_DIGIT(parser->buffer)) { if (CHECK(parser->buffer, '0')) { yaml_parser_set_scanner_error(parser, "while scanning a block scalar", start_mark, "found an intendation indicator equal to 0"); goto error; } increment = AS_DIGIT(parser->buffer); SKIP(parser); if (!CACHE(parser, 1)) goto error; if (CHECK(parser->buffer, '+') || CHECK(parser->buffer, '-')) { chomping = CHECK(parser->buffer, '+') ? +1 : -1; SKIP(parser); } } /* Eat whitespaces and comments to the end of the line. */ if (!CACHE(parser, 1)) goto error; while (IS_BLANK(parser->buffer)) { SKIP(parser); if (!CACHE(parser, 1)) goto error; } if (CHECK(parser->buffer, '#')) { while (!IS_BREAKZ(parser->buffer)) { SKIP(parser); if (!CACHE(parser, 1)) goto error; } } /* Check if we are at the end of the line. */ if (!IS_BREAKZ(parser->buffer)) { yaml_parser_set_scanner_error(parser, "while scanning a block scalar", start_mark, "did not find expected comment or line break"); goto error; } /* Eat a line break. */ if (IS_BREAK(parser->buffer)) { if (!CACHE(parser, 2)) goto error; SKIP_LINE(parser); } end_mark = parser->mark; /* Set the intendation level if it was specified. */ if (increment) { indent = parser->indent >= 0 ? parser->indent+increment : increment; } /* Scan the leading line breaks and determine the indentation level if needed. */ if (!yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark)) goto error; /* Scan the block scalar content. */ if (!CACHE(parser, 1)) goto error; while ((int)parser->mark.column == indent && !IS_Z(parser->buffer)) { /* * We are at the beginning of a non-empty line. */ /* Is it a trailing whitespace? */ trailing_blank = IS_BLANK(parser->buffer); /* Check if we need to fold the leading line break. */ if (!literal && (*leading_break.start == '\n') && !leading_blank && !trailing_blank) { /* Do we need to join the lines by space? */ if (*trailing_breaks.start == '\0') { if (!STRING_EXTEND(parser, string)) goto error; *(string.pointer ++) = ' '; } CLEAR(parser, leading_break); } else { if (!JOIN(parser, string, leading_break)) goto error; CLEAR(parser, leading_break); } /* Append the remaining line breaks. */ if (!JOIN(parser, string, trailing_breaks)) goto error; CLEAR(parser, trailing_breaks); /* Is it a leading whitespace? */ leading_blank = IS_BLANK(parser->buffer); /* Consume the current line. */ while (!IS_BREAKZ(parser->buffer)) { if (!READ(parser, string)) goto error; if (!CACHE(parser, 1)) goto error; } /* Consume the line break. */ if (!CACHE(parser, 2)) goto error; if (!READ_LINE(parser, leading_break)) goto error; /* Eat the following intendation spaces and line breaks. */ if (!yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark)) goto error; } /* Chomp the tail. */ if (chomping != -1) { if (!JOIN(parser, string, leading_break)) goto error; } if (chomping == 1) { if (!JOIN(parser, string, trailing_breaks)) goto error; } /* Create a token. */ SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start, literal ? YAML_LITERAL_SCALAR_STYLE : YAML_FOLDED_SCALAR_STYLE, start_mark, end_mark); STRING_DEL(parser, leading_break); STRING_DEL(parser, trailing_breaks); return 1; error: STRING_DEL(parser, string); STRING_DEL(parser, leading_break); STRING_DEL(parser, trailing_breaks); return 0; } /* * Scan intendation spaces and line breaks for a block scalar. Determine the * intendation level if needed. */ static int yaml_parser_scan_block_scalar_breaks(yaml_parser_t *parser, int *indent, yaml_string_t *breaks, yaml_mark_t start_mark, yaml_mark_t *end_mark) { int max_indent = 0; *end_mark = parser->mark; /* Eat the intendation spaces and line breaks. */ while (1) { /* Eat the intendation spaces. */ if (!CACHE(parser, 1)) return 0; while ((!*indent || (int)parser->mark.column < *indent) && IS_SPACE(parser->buffer)) { SKIP(parser); if (!CACHE(parser, 1)) return 0; } if ((int)parser->mark.column > max_indent) max_indent = (int)parser->mark.column; /* Check for a tab character messing the intendation. */ if ((!*indent || (int)parser->mark.column < *indent) && IS_TAB(parser->buffer)) { return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", start_mark, "found a tab character where an intendation space is expected"); } /* Have we found a non-empty line? */ if (!IS_BREAK(parser->buffer)) break; /* Consume the line break. */ if (!CACHE(parser, 2)) return 0; if (!READ_LINE(parser, *breaks)) return 0; *end_mark = parser->mark; } /* Determine the indentation level if needed. */ if (!*indent) { *indent = max_indent; if (*indent < parser->indent + 1) *indent = parser->indent + 1; if (*indent < 1) *indent = 1; } return 1; } /* * Scan a quoted scalar. */ static int yaml_parser_scan_flow_scalar(yaml_parser_t *parser, yaml_token_t *token, int single) { yaml_mark_t start_mark; yaml_mark_t end_mark; yaml_string_t string = NULL_STRING; yaml_string_t leading_break = NULL_STRING; yaml_string_t trailing_breaks = NULL_STRING; yaml_string_t whitespaces = NULL_STRING; int leading_blanks; if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error; if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error; if (!STRING_INIT(parser, whitespaces, INITIAL_STRING_SIZE)) goto error; /* Eat the left quote. */ start_mark = parser->mark; SKIP(parser); /* Consume the content of the quoted scalar. */ while (1) { /* Check that there are no document indicators at the beginning of the line. */ if (!CACHE(parser, 4)) goto error; if (parser->mark.column == 0 && ((CHECK_AT(parser->buffer, '-', 0) && CHECK_AT(parser->buffer, '-', 1) && CHECK_AT(parser->buffer, '-', 2)) || (CHECK_AT(parser->buffer, '.', 0) && CHECK_AT(parser->buffer, '.', 1) && CHECK_AT(parser->buffer, '.', 2))) && IS_BLANKZ_AT(parser->buffer, 3)) { yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", start_mark, "found unexpected document indicator"); goto error; } /* Check for EOF. */ if (IS_Z(parser->buffer)) { yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", start_mark, "found unexpected end of stream"); goto error; } /* Consume non-blank characters. */ if (!CACHE(parser, 2)) goto error; leading_blanks = 0; while (!IS_BLANKZ(parser->buffer)) { /* Check for an escaped single quote. */ if (single && CHECK_AT(parser->buffer, '\'', 0) && CHECK_AT(parser->buffer, '\'', 1)) { if (!STRING_EXTEND(parser, string)) goto error; *(string.pointer++) = '\''; SKIP(parser); SKIP(parser); } /* Check for the right quote. */ else if (CHECK(parser->buffer, single ? '\'' : '"')) { break; } /* Check for an escaped line break. */ else if (!single && CHECK(parser->buffer, '\\') && IS_BREAK_AT(parser->buffer, 1)) { if (!CACHE(parser, 3)) goto error; SKIP(parser); SKIP_LINE(parser); leading_blanks = 1; break; } /* Check for an escape sequence. */ else if (!single && CHECK(parser->buffer, '\\')) { size_t code_length = 0; int raw_code = 0; if (!STRING_EXTEND(parser, string)) goto error; /* Check the escape character. */ switch (parser->buffer.pointer[1]) { case '0': *(string.pointer++) = '\0'; break; case 'a': *(string.pointer++) = '\x07'; break; case 'b': *(string.pointer++) = '\x08'; break; case 't': case '\t': *(string.pointer++) = '\x09'; break; case 'n': *(string.pointer++) = '\x0A'; break; case 'v': *(string.pointer++) = '\x0B'; break; case 'f': *(string.pointer++) = '\x0C'; break; case 'r': *(string.pointer++) = '\x0D'; break; case 'e': *(string.pointer++) = '\x1B'; break; case ' ': *(string.pointer++) = '\x20'; break; case '"': *(string.pointer++) = '"'; break; case '\'': *(string.pointer++) = '\''; break; case '\\': *(string.pointer++) = '\\'; break; case 'N': /* NEL (#x85) */ *(string.pointer++) = '\xC2'; *(string.pointer++) = '\x85'; break; case '_': /* #xA0 */ *(string.pointer++) = '\xC2'; *(string.pointer++) = '\xA0'; break; case 'L': /* LS (#x2028) */ *(string.pointer++) = '\xE2'; *(string.pointer++) = '\x80'; *(string.pointer++) = '\xA8'; break; case 'P': /* PS (#x2029) */ *(string.pointer++) = '\xE2'; *(string.pointer++) = '\x80'; *(string.pointer++) = '\xA9'; break; case 'x': code_length = 2; break; case 'X': code_length = 2; raw_code = 1; break; case 'u': code_length = 4; break; case 'U': code_length = 8; break; default: yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", start_mark, "found unknown escape character"); goto error; } SKIP(parser); SKIP(parser); /* Consume an arbitrary escape code. */ if (code_length) { unsigned int value = 0; size_t k; /* Scan the character value. */ if (!CACHE(parser, code_length)) goto error; for (k = 0; k < code_length; k ++) { if (!IS_HEX_AT(parser->buffer, k)) { yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", start_mark, "did not find expected hexdecimal number"); goto error; } value = (value << 4) + AS_HEX_AT(parser->buffer, k); } /* Check the value and write the character. */ if ((value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF) { yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", start_mark, "found invalid Unicode character escape code"); goto error; } if (value <= 0x7F || raw_code) { *(string.pointer++) = value; } else if (value <= 0x7FF) { *(string.pointer++) = 0xC0 + (value >> 6); *(string.pointer++) = 0x80 + (value & 0x3F); } else if (value <= 0xFFFF) { *(string.pointer++) = 0xE0 + (value >> 12); *(string.pointer++) = 0x80 + ((value >> 6) & 0x3F); *(string.pointer++) = 0x80 + (value & 0x3F); } else { *(string.pointer++) = 0xF0 + (value >> 18); *(string.pointer++) = 0x80 + ((value >> 12) & 0x3F); *(string.pointer++) = 0x80 + ((value >> 6) & 0x3F); *(string.pointer++) = 0x80 + (value & 0x3F); } /* Advance the pointer. */ for (k = 0; k < code_length; k ++) { SKIP(parser); } } } else { /* It is a non-escaped non-blank byte. */ if (!READ(parser, string)) goto error; } if (!CACHE(parser, 2)) goto error; } /* Check if we are at the end of the scalar. */ if (CHECK(parser->buffer, single ? '\'' : '"')) break; /* Consume blank characters. */ if (!CACHE(parser, 1)) goto error; while (IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer)) { if (IS_BLANK(parser->buffer)) { /* Consume a space or a tab character. */ if (!leading_blanks) { if (!READ(parser, whitespaces)) goto error; } else { SKIP(parser); } } else { if (!CACHE(parser, 2)) goto error; /* Check if it is a first line break. */ if (!leading_blanks) { CLEAR(parser, whitespaces); if (!READ_LINE(parser, leading_break)) goto error; leading_blanks = 1; } else { if (!READ_LINE(parser, trailing_breaks)) goto error; } } if (!CACHE(parser, 1)) goto error; } /* Join the whitespaces or fold line breaks. */ if (leading_blanks) { /* Do we need to fold line breaks? */ if (leading_break.start[0] == '\n') { if (trailing_breaks.start[0] == '\0') { if (!STRING_EXTEND(parser, string)) goto error; *(string.pointer++) = ' '; } else { if (!JOIN(parser, string, trailing_breaks)) goto error; CLEAR(parser, trailing_breaks); } CLEAR(parser, leading_break); } else { if (!JOIN(parser, string, leading_break)) goto error; if (!JOIN(parser, string, trailing_breaks)) goto error; CLEAR(parser, leading_break); CLEAR(parser, trailing_breaks); } } else { if (!JOIN(parser, string, whitespaces)) goto error; CLEAR(parser, whitespaces); } } /* Eat the right quote. */ SKIP(parser); end_mark = parser->mark; /* Create a token. */ SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start, single ? YAML_SINGLE_QUOTED_SCALAR_STYLE : YAML_DOUBLE_QUOTED_SCALAR_STYLE, start_mark, end_mark); STRING_DEL(parser, leading_break); STRING_DEL(parser, trailing_breaks); STRING_DEL(parser, whitespaces); return 1; error: STRING_DEL(parser, string); STRING_DEL(parser, leading_break); STRING_DEL(parser, trailing_breaks); STRING_DEL(parser, whitespaces); return 0; } /* * Scan a plain scalar. */ static int yaml_parser_scan_plain_scalar(yaml_parser_t *parser, yaml_token_t *token) { yaml_mark_t start_mark; yaml_mark_t end_mark; yaml_string_t string = NULL_STRING; yaml_string_t leading_break = NULL_STRING; yaml_string_t trailing_breaks = NULL_STRING; yaml_string_t whitespaces = NULL_STRING; int leading_blanks = 0; int indent = parser->indent+1; if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error; if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error; if (!STRING_INIT(parser, whitespaces, INITIAL_STRING_SIZE)) goto error; start_mark = end_mark = parser->mark; /* Consume the content of the plain scalar. */ while (1) { /* Check for a document indicator. */ if (!CACHE(parser, 4)) goto error; if (parser->mark.column == 0 && ((CHECK_AT(parser->buffer, '-', 0) && CHECK_AT(parser->buffer, '-', 1) && CHECK_AT(parser->buffer, '-', 2)) || (CHECK_AT(parser->buffer, '.', 0) && CHECK_AT(parser->buffer, '.', 1) && CHECK_AT(parser->buffer, '.', 2))) && IS_BLANKZ_AT(parser->buffer, 3)) break; /* Check for a comment. */ if (CHECK(parser->buffer, '#')) break; /* Consume non-blank characters. */ while (!IS_BLANKZ(parser->buffer)) { /* Check for 'x:x' in the flow context. TODO: Fix the test "spec-08-13". */ if (parser->flow_level && CHECK(parser->buffer, ':') && !IS_BLANKZ_AT(parser->buffer, 1)) { yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", start_mark, "found unexpected ':'"); goto error; } /* Check for indicators that may end a plain scalar. */ if ((CHECK(parser->buffer, ':') && IS_BLANKZ_AT(parser->buffer, 1)) || (parser->flow_level && (CHECK(parser->buffer, ',') || CHECK(parser->buffer, ':') || CHECK(parser->buffer, '?') || CHECK(parser->buffer, '[') || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '{') || CHECK(parser->buffer, '}')))) break; /* Check if we need to join whitespaces and breaks. */ if (leading_blanks || whitespaces.start != whitespaces.pointer) { if (leading_blanks) { /* Do we need to fold line breaks? */ if (leading_break.start[0] == '\n') { if (trailing_breaks.start[0] == '\0') { if (!STRING_EXTEND(parser, string)) goto error; *(string.pointer++) = ' '; } else { if (!JOIN(parser, string, trailing_breaks)) goto error; CLEAR(parser, trailing_breaks); } CLEAR(parser, leading_break); } else { if (!JOIN(parser, string, leading_break)) goto error; if (!JOIN(parser, string, trailing_breaks)) goto error; CLEAR(parser, leading_break); CLEAR(parser, trailing_breaks); } leading_blanks = 0; } else { if (!JOIN(parser, string, whitespaces)) goto error; CLEAR(parser, whitespaces); } } /* Copy the byte. */ if (!READ(parser, string)) goto error; end_mark = parser->mark; if (!CACHE(parser, 2)) goto error; } /* Is it the end? */ if (!(IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer))) break; /* Consume blank characters. */ if (!CACHE(parser, 1)) goto error; while (IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer)) { if (IS_BLANK(parser->buffer)) { /* Check for tab character that abuse intendation. */ if (leading_blanks && (int)parser->mark.column < indent && IS_TAB(parser->buffer)) { yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", start_mark, "found a tab character that violate intendation"); goto error; } /* Consume a space or a tab character. */ if (!leading_blanks) { if (!READ(parser, whitespaces)) goto error; } else { SKIP(parser); } } else { if (!CACHE(parser, 2)) goto error; /* Check if it is a first line break. */ if (!leading_blanks) { CLEAR(parser, whitespaces); if (!READ_LINE(parser, leading_break)) goto error; leading_blanks = 1; } else { if (!READ_LINE(parser, trailing_breaks)) goto error; } } if (!CACHE(parser, 1)) goto error; } /* Check intendation level. */ if (!parser->flow_level && (int)parser->mark.column < indent) break; } /* Create a token. */ SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start, YAML_PLAIN_SCALAR_STYLE, start_mark, end_mark); /* Note that we change the 'simple_key_allowed' flag. */ if (leading_blanks) { parser->simple_key_allowed = 1; } STRING_DEL(parser, leading_break); STRING_DEL(parser, trailing_breaks); STRING_DEL(parser, whitespaces); return 1; error: STRING_DEL(parser, string); STRING_DEL(parser, leading_break); STRING_DEL(parser, trailing_breaks); STRING_DEL(parser, whitespaces); return 0; } flvmeta-1.2.2/src/libyaml/writer.c000066400000000000000000000077151346231570700170740ustar00rootroot00000000000000 #include "yaml_private.h" /* * Declarations. */ static int yaml_emitter_set_writer_error(yaml_emitter_t *emitter, const char *problem); YAML_DECLARE(int) yaml_emitter_flush(yaml_emitter_t *emitter); /* * Set the writer error and return 0. */ static int yaml_emitter_set_writer_error(yaml_emitter_t *emitter, const char *problem) { emitter->error = YAML_WRITER_ERROR; emitter->problem = problem; return 0; } /* * Flush the output buffer. */ YAML_DECLARE(int) yaml_emitter_flush(yaml_emitter_t *emitter) { int low, high; assert(emitter); /* Non-NULL emitter object is expected. */ assert(emitter->write_handler); /* Write handler must be set. */ assert(emitter->encoding); /* Output encoding must be set. */ emitter->buffer.last = emitter->buffer.pointer; emitter->buffer.pointer = emitter->buffer.start; /* Check if the buffer is empty. */ if (emitter->buffer.start == emitter->buffer.last) { return 1; } /* If the output encoding is UTF-8, we don't need to recode the buffer. */ if (emitter->encoding == YAML_UTF8_ENCODING) { if (emitter->write_handler(emitter->write_handler_data, emitter->buffer.start, emitter->buffer.last - emitter->buffer.start)) { emitter->buffer.last = emitter->buffer.start; emitter->buffer.pointer = emitter->buffer.start; return 1; } else { return yaml_emitter_set_writer_error(emitter, "write error"); } } /* Recode the buffer into the raw buffer. */ low = (emitter->encoding == YAML_UTF16LE_ENCODING ? 0 : 1); high = (emitter->encoding == YAML_UTF16LE_ENCODING ? 1 : 0); while (emitter->buffer.pointer != emitter->buffer.last) { unsigned char octet; unsigned int width; unsigned int value; size_t k; /* * See the "reader.c" code for more details on UTF-8 encoding. Note * that we assume that the buffer contains a valid UTF-8 sequence. */ /* Read the next UTF-8 character. */ octet = emitter->buffer.pointer[0]; width = (octet & 0x80) == 0x00 ? 1 : (octet & 0xE0) == 0xC0 ? 2 : (octet & 0xF0) == 0xE0 ? 3 : (octet & 0xF8) == 0xF0 ? 4 : 0; value = (octet & 0x80) == 0x00 ? octet & 0x7F : (octet & 0xE0) == 0xC0 ? octet & 0x1F : (octet & 0xF0) == 0xE0 ? octet & 0x0F : (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; for (k = 1; k < width; k ++) { octet = emitter->buffer.pointer[k]; value = (value << 6) + (octet & 0x3F); } emitter->buffer.pointer += width; /* Write the character. */ if (value < 0x10000) { emitter->raw_buffer.last[high] = value >> 8; emitter->raw_buffer.last[low] = value & 0xFF; emitter->raw_buffer.last += 2; } else { /* Write the character using a surrogate pair (check "reader.c"). */ value -= 0x10000; emitter->raw_buffer.last[high] = 0xD8 + (value >> 18); emitter->raw_buffer.last[low] = (value >> 10) & 0xFF; emitter->raw_buffer.last[high+2] = 0xDC + ((value >> 8) & 0xFF); emitter->raw_buffer.last[low+2] = value & 0xFF; emitter->raw_buffer.last += 4; } } /* Write the raw buffer. */ if (emitter->write_handler(emitter->write_handler_data, emitter->raw_buffer.start, emitter->raw_buffer.last - emitter->raw_buffer.start)) { emitter->buffer.last = emitter->buffer.start; emitter->buffer.pointer = emitter->buffer.start; emitter->raw_buffer.last = emitter->raw_buffer.start; emitter->raw_buffer.pointer = emitter->raw_buffer.start; return 1; } else { return yaml_emitter_set_writer_error(emitter, "write error"); } } flvmeta-1.2.2/src/libyaml/yaml.h000066400000000000000000001517201346231570700165230ustar00rootroot00000000000000/** * @file yaml.h * @brief Public interface for libyaml. * * Include the header file with the code: * @code * #include * @endcode */ #ifndef YAML_H #define YAML_H #ifdef __cplusplus extern "C" { #endif #include #include #include /** * @defgroup export Export Definitions * @{ */ /** The public API declaration. */ #ifdef WIN32 # if defined(YAML_DECLARE_STATIC) # define YAML_DECLARE(type) type # elif defined(YAML_DECLARE_EXPORT) # define YAML_DECLARE(type) __declspec(dllexport) type # else # define YAML_DECLARE(type) __declspec(dllimport) type # endif #else # define YAML_DECLARE(type) type #endif /** @} */ /** * @defgroup version Version Information * @{ */ /** * Get the library version as a string. * * @returns The function returns the pointer to a static string of the form * @c "X.Y.Z", where @c X is the major version number, @c Y is a minor version * number, and @c Z is the patch version number. */ YAML_DECLARE(const char *) yaml_get_version_string(void); /** * Get the library version numbers. * * @param[out] major Major version number. * @param[out] minor Minor version number. * @param[out] patch Patch version number. */ YAML_DECLARE(void) yaml_get_version(int *major, int *minor, int *patch); /** @} */ /** * @defgroup basic Basic Types * @{ */ /** The character type (UTF-8 octet). */ typedef unsigned char yaml_char_t; /** The version directive data. */ typedef struct yaml_version_directive_s { /** The major version number. */ int major; /** The minor version number. */ int minor; } yaml_version_directive_t; /** The tag directive data. */ typedef struct yaml_tag_directive_s { /** The tag handle. */ yaml_char_t *handle; /** The tag prefix. */ yaml_char_t *prefix; } yaml_tag_directive_t; /** The stream encoding. */ typedef enum yaml_encoding_e { /** Let the parser choose the encoding. */ YAML_ANY_ENCODING, /** The default UTF-8 encoding. */ YAML_UTF8_ENCODING, /** The UTF-16-LE encoding with BOM. */ YAML_UTF16LE_ENCODING, /** The UTF-16-BE encoding with BOM. */ YAML_UTF16BE_ENCODING } yaml_encoding_t; /** Line break types. */ typedef enum yaml_break_e { /** Let the parser choose the break type. */ YAML_ANY_BREAK, /** Use CR for line breaks (Mac style). */ YAML_CR_BREAK, /** Use LN for line breaks (Unix style). */ YAML_LN_BREAK, /** Use CR LN for line breaks (DOS style). */ YAML_CRLN_BREAK } yaml_break_t; /** Many bad things could happen with the parser and emitter. */ typedef enum yaml_error_type_e { /** No error is produced. */ YAML_NO_ERROR, /** Cannot allocate or reallocate a block of memory. */ YAML_MEMORY_ERROR, /** Cannot read or decode the input stream. */ YAML_READER_ERROR, /** Cannot scan the input stream. */ YAML_SCANNER_ERROR, /** Cannot parse the input stream. */ YAML_PARSER_ERROR, /** Cannot compose a YAML document. */ YAML_COMPOSER_ERROR, /** Cannot write to the output stream. */ YAML_WRITER_ERROR, /** Cannot emit a YAML stream. */ YAML_EMITTER_ERROR } yaml_error_type_t; /** The pointer position. */ typedef struct yaml_mark_s { /** The position index. */ size_t index; /** The position line. */ size_t line; /** The position column. */ size_t column; } yaml_mark_t; /** @} */ /** * @defgroup styles Node Styles * @{ */ /** Scalar styles. */ typedef enum yaml_scalar_style_e { /** Let the emitter choose the style. */ YAML_ANY_SCALAR_STYLE, /** The plain scalar style. */ YAML_PLAIN_SCALAR_STYLE, /** The single-quoted scalar style. */ YAML_SINGLE_QUOTED_SCALAR_STYLE, /** The double-quoted scalar style. */ YAML_DOUBLE_QUOTED_SCALAR_STYLE, /** The literal scalar style. */ YAML_LITERAL_SCALAR_STYLE, /** The folded scalar style. */ YAML_FOLDED_SCALAR_STYLE } yaml_scalar_style_t; /** Sequence styles. */ typedef enum yaml_sequence_style_e { /** Let the emitter choose the style. */ YAML_ANY_SEQUENCE_STYLE, /** The block sequence style. */ YAML_BLOCK_SEQUENCE_STYLE, /** The flow sequence style. */ YAML_FLOW_SEQUENCE_STYLE } yaml_sequence_style_t; /** Mapping styles. */ typedef enum yaml_mapping_style_e { /** Let the emitter choose the style. */ YAML_ANY_MAPPING_STYLE, /** The block mapping style. */ YAML_BLOCK_MAPPING_STYLE, /** The flow mapping style. */ YAML_FLOW_MAPPING_STYLE /* YAML_FLOW_SET_MAPPING_STYLE */ } yaml_mapping_style_t; /** @} */ /** * @defgroup tokens Tokens * @{ */ /** Token types. */ typedef enum yaml_token_type_e { /** An empty token. */ YAML_NO_TOKEN, /** A STREAM-START token. */ YAML_STREAM_START_TOKEN, /** A STREAM-END token. */ YAML_STREAM_END_TOKEN, /** A VERSION-DIRECTIVE token. */ YAML_VERSION_DIRECTIVE_TOKEN, /** A TAG-DIRECTIVE token. */ YAML_TAG_DIRECTIVE_TOKEN, /** A DOCUMENT-START token. */ YAML_DOCUMENT_START_TOKEN, /** A DOCUMENT-END token. */ YAML_DOCUMENT_END_TOKEN, /** A BLOCK-SEQUENCE-START token. */ YAML_BLOCK_SEQUENCE_START_TOKEN, /** A BLOCK-SEQUENCE-END token. */ YAML_BLOCK_MAPPING_START_TOKEN, /** A BLOCK-END token. */ YAML_BLOCK_END_TOKEN, /** A FLOW-SEQUENCE-START token. */ YAML_FLOW_SEQUENCE_START_TOKEN, /** A FLOW-SEQUENCE-END token. */ YAML_FLOW_SEQUENCE_END_TOKEN, /** A FLOW-MAPPING-START token. */ YAML_FLOW_MAPPING_START_TOKEN, /** A FLOW-MAPPING-END token. */ YAML_FLOW_MAPPING_END_TOKEN, /** A BLOCK-ENTRY token. */ YAML_BLOCK_ENTRY_TOKEN, /** A FLOW-ENTRY token. */ YAML_FLOW_ENTRY_TOKEN, /** A KEY token. */ YAML_KEY_TOKEN, /** A VALUE token. */ YAML_VALUE_TOKEN, /** An ALIAS token. */ YAML_ALIAS_TOKEN, /** An ANCHOR token. */ YAML_ANCHOR_TOKEN, /** A TAG token. */ YAML_TAG_TOKEN, /** A SCALAR token. */ YAML_SCALAR_TOKEN } yaml_token_type_t; /** The token structure. */ typedef struct yaml_token_s { /** The token type. */ yaml_token_type_t type; /** The token data. */ union { /** The stream start (for @c YAML_STREAM_START_TOKEN). */ struct { /** The stream encoding. */ yaml_encoding_t encoding; } stream_start; /** The alias (for @c YAML_ALIAS_TOKEN). */ struct { /** The alias value. */ yaml_char_t *value; } alias; /** The anchor (for @c YAML_ANCHOR_TOKEN). */ struct { /** The anchor value. */ yaml_char_t *value; } anchor; /** The tag (for @c YAML_TAG_TOKEN). */ struct { /** The tag handle. */ yaml_char_t *handle; /** The tag suffix. */ yaml_char_t *suffix; } tag; /** The scalar value (for @c YAML_SCALAR_TOKEN). */ struct { /** The scalar value. */ yaml_char_t *value; /** The length of the scalar value. */ size_t length; /** The scalar style. */ yaml_scalar_style_t style; } scalar; /** The version directive (for @c YAML_VERSION_DIRECTIVE_TOKEN). */ struct { /** The major version number. */ int major; /** The minor version number. */ int minor; } version_directive; /** The tag directive (for @c YAML_TAG_DIRECTIVE_TOKEN). */ struct { /** The tag handle. */ yaml_char_t *handle; /** The tag prefix. */ yaml_char_t *prefix; } tag_directive; } data; /** The beginning of the token. */ yaml_mark_t start_mark; /** The end of the token. */ yaml_mark_t end_mark; } yaml_token_t; /** * Free any memory allocated for a token object. * * @param[in,out] token A token object. */ YAML_DECLARE(void) yaml_token_delete(yaml_token_t *token); /** @} */ /** * @defgroup events Events * @{ */ /** Event types. */ typedef enum yaml_event_type_e { /** An empty event. */ YAML_NO_EVENT, /** A STREAM-START event. */ YAML_STREAM_START_EVENT, /** A STREAM-END event. */ YAML_STREAM_END_EVENT, /** A DOCUMENT-START event. */ YAML_DOCUMENT_START_EVENT, /** A DOCUMENT-END event. */ YAML_DOCUMENT_END_EVENT, /** An ALIAS event. */ YAML_ALIAS_EVENT, /** A SCALAR event. */ YAML_SCALAR_EVENT, /** A SEQUENCE-START event. */ YAML_SEQUENCE_START_EVENT, /** A SEQUENCE-END event. */ YAML_SEQUENCE_END_EVENT, /** A MAPPING-START event. */ YAML_MAPPING_START_EVENT, /** A MAPPING-END event. */ YAML_MAPPING_END_EVENT } yaml_event_type_t; /** The event structure. */ typedef struct yaml_event_s { /** The event type. */ yaml_event_type_t type; /** The event data. */ union { /** The stream parameters (for @c YAML_STREAM_START_EVENT). */ struct { /** The document encoding. */ yaml_encoding_t encoding; } stream_start; /** The document parameters (for @c YAML_DOCUMENT_START_EVENT). */ struct { /** The version directive. */ yaml_version_directive_t *version_directive; /** The list of tag directives. */ struct { /** The beginning of the tag directives list. */ yaml_tag_directive_t *start; /** The end of the tag directives list. */ yaml_tag_directive_t *end; } tag_directives; /** Is the document indicator implicit? */ int implicit; } document_start; /** The document end parameters (for @c YAML_DOCUMENT_END_EVENT). */ struct { /** Is the document end indicator implicit? */ int implicit; } document_end; /** The alias parameters (for @c YAML_ALIAS_EVENT). */ struct { /** The anchor. */ yaml_char_t *anchor; } alias; /** The scalar parameters (for @c YAML_SCALAR_EVENT). */ struct { /** The anchor. */ yaml_char_t *anchor; /** The tag. */ yaml_char_t *tag; /** The scalar value. */ yaml_char_t *value; /** The length of the scalar value. */ size_t length; /** Is the tag optional for the plain style? */ int plain_implicit; /** Is the tag optional for any non-plain style? */ int quoted_implicit; /** The scalar style. */ yaml_scalar_style_t style; } scalar; /** The sequence parameters (for @c YAML_SEQUENCE_START_EVENT). */ struct { /** The anchor. */ yaml_char_t *anchor; /** The tag. */ yaml_char_t *tag; /** Is the tag optional? */ int implicit; /** The sequence style. */ yaml_sequence_style_t style; } sequence_start; /** The mapping parameters (for @c YAML_MAPPING_START_EVENT). */ struct { /** The anchor. */ yaml_char_t *anchor; /** The tag. */ yaml_char_t *tag; /** Is the tag optional? */ int implicit; /** The mapping style. */ yaml_mapping_style_t style; } mapping_start; } data; /** The beginning of the event. */ yaml_mark_t start_mark; /** The end of the event. */ yaml_mark_t end_mark; } yaml_event_t; /** * Create the STREAM-START event. * * @param[out] event An empty event object. * @param[in] encoding The stream encoding. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_stream_start_event_initialize(yaml_event_t *event, yaml_encoding_t encoding); /** * Create the STREAM-END event. * * @param[out] event An empty event object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_stream_end_event_initialize(yaml_event_t *event); /** * Create the DOCUMENT-START event. * * The @a implicit argument is considered as a stylistic parameter and may be * ignored by the emitter. * * @param[out] event An empty event object. * @param[in] version_directive The %YAML directive value or * @c NULL. * @param[in] tag_directives_start The beginning of the %TAG * directives list. * @param[in] tag_directives_end The end of the %TAG directives * list. * @param[in] implicit If the document start indicator is * implicit. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_document_start_event_initialize(yaml_event_t *event, yaml_version_directive_t *version_directive, yaml_tag_directive_t *tag_directives_start, yaml_tag_directive_t *tag_directives_end, int implicit); /** * Create the DOCUMENT-END event. * * The @a implicit argument is considered as a stylistic parameter and may be * ignored by the emitter. * * @param[out] event An empty event object. * @param[in] implicit If the document end indicator is implicit. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_document_end_event_initialize(yaml_event_t *event, int implicit); /** * Create an ALIAS event. * * @param[out] event An empty event object. * @param[in] anchor The anchor value. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_alias_event_initialize(yaml_event_t *event, yaml_char_t *anchor); /** * Create a SCALAR event. * * The @a style argument may be ignored by the emitter. * * Either the @a tag attribute or one of the @a plain_implicit and * @a quoted_implicit flags must be set. * * @param[out] event An empty event object. * @param[in] anchor The scalar anchor or @c NULL. * @param[in] tag The scalar tag or @c NULL. * @param[in] value The scalar value. * @param[in] length The length of the scalar value. * @param[in] plain_implicit If the tag may be omitted for the plain * style. * @param[in] quoted_implicit If the tag may be omitted for any * non-plain style. * @param[in] style The scalar style. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_scalar_event_initialize(yaml_event_t *event, yaml_char_t *anchor, yaml_char_t *tag, yaml_char_t *value, int length, int plain_implicit, int quoted_implicit, yaml_scalar_style_t style); /** * Create a SEQUENCE-START event. * * The @a style argument may be ignored by the emitter. * * Either the @a tag attribute or the @a implicit flag must be set. * * @param[out] event An empty event object. * @param[in] anchor The sequence anchor or @c NULL. * @param[in] tag The sequence tag or @c NULL. * @param[in] implicit If the tag may be omitted. * @param[in] style The sequence style. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_sequence_start_event_initialize(yaml_event_t *event, yaml_char_t *anchor, yaml_char_t *tag, int implicit, yaml_sequence_style_t style); /** * Create a SEQUENCE-END event. * * @param[out] event An empty event object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_sequence_end_event_initialize(yaml_event_t *event); /** * Create a MAPPING-START event. * * The @a style argument may be ignored by the emitter. * * Either the @a tag attribute or the @a implicit flag must be set. * * @param[out] event An empty event object. * @param[in] anchor The mapping anchor or @c NULL. * @param[in] tag The mapping tag or @c NULL. * @param[in] implicit If the tag may be omitted. * @param[in] style The mapping style. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_mapping_start_event_initialize(yaml_event_t *event, yaml_char_t *anchor, yaml_char_t *tag, int implicit, yaml_mapping_style_t style); /** * Create a MAPPING-END event. * * @param[out] event An empty event object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_mapping_end_event_initialize(yaml_event_t *event); /** * Free any memory allocated for an event object. * * @param[in,out] event An event object. */ YAML_DECLARE(void) yaml_event_delete(yaml_event_t *event); /** @} */ /** * @defgroup nodes Nodes * @{ */ /** The tag @c !!null with the only possible value: @c null. */ #define YAML_NULL_TAG "tag:yaml.org,2002:null" /** The tag @c !!bool with the values: @c true and @c falce. */ #define YAML_BOOL_TAG "tag:yaml.org,2002:bool" /** The tag @c !!str for string values. */ #define YAML_STR_TAG "tag:yaml.org,2002:str" /** The tag @c !!int for integer values. */ #define YAML_INT_TAG "tag:yaml.org,2002:int" /** The tag @c !!float for float values. */ #define YAML_FLOAT_TAG "tag:yaml.org,2002:float" /** The tag @c !!timestamp for date and time values. */ #define YAML_TIMESTAMP_TAG "tag:yaml.org,2002:timestamp" /** The tag @c !!seq is used to denote sequences. */ #define YAML_SEQ_TAG "tag:yaml.org,2002:seq" /** The tag @c !!map is used to denote mapping. */ #define YAML_MAP_TAG "tag:yaml.org,2002:map" /** The default scalar tag is @c !!str. */ #define YAML_DEFAULT_SCALAR_TAG YAML_STR_TAG /** The default sequence tag is @c !!seq. */ #define YAML_DEFAULT_SEQUENCE_TAG YAML_SEQ_TAG /** The default mapping tag is @c !!map. */ #define YAML_DEFAULT_MAPPING_TAG YAML_MAP_TAG /** Node types. */ typedef enum yaml_node_type_e { /** An empty node. */ YAML_NO_NODE, /** A scalar node. */ YAML_SCALAR_NODE, /** A sequence node. */ YAML_SEQUENCE_NODE, /** A mapping node. */ YAML_MAPPING_NODE } yaml_node_type_t; /** The forward definition of a document node structure. */ typedef struct yaml_node_s yaml_node_t; /** An element of a sequence node. */ typedef int yaml_node_item_t; /** An element of a mapping node. */ typedef struct yaml_node_pair_s { /** The key of the element. */ int key; /** The value of the element. */ int value; } yaml_node_pair_t; /** The node structure. */ struct yaml_node_s { /** The node type. */ yaml_node_type_t type; /** The node tag. */ yaml_char_t *tag; /** The node data. */ union { /** The scalar parameters (for @c YAML_SCALAR_NODE). */ struct { /** The scalar value. */ yaml_char_t *value; /** The length of the scalar value. */ size_t length; /** The scalar style. */ yaml_scalar_style_t style; } scalar; /** The sequence parameters (for @c YAML_SEQUENCE_NODE). */ struct { /** The stack of sequence items. */ struct { /** The beginning of the stack. */ yaml_node_item_t *start; /** The end of the stack. */ yaml_node_item_t *end; /** The top of the stack. */ yaml_node_item_t *top; } items; /** The sequence style. */ yaml_sequence_style_t style; } sequence; /** The mapping parameters (for @c YAML_MAPPING_NODE). */ struct { /** The stack of mapping pairs (key, value). */ struct { /** The beginning of the stack. */ yaml_node_pair_t *start; /** The end of the stack. */ yaml_node_pair_t *end; /** The top of the stack. */ yaml_node_pair_t *top; } pairs; /** The mapping style. */ yaml_mapping_style_t style; } mapping; } data; /** The beginning of the node. */ yaml_mark_t start_mark; /** The end of the node. */ yaml_mark_t end_mark; }; /** The document structure. */ typedef struct yaml_document_s { /** The document nodes. */ struct { /** The beginning of the stack. */ yaml_node_t *start; /** The end of the stack. */ yaml_node_t *end; /** The top of the stack. */ yaml_node_t *top; } nodes; /** The version directive. */ yaml_version_directive_t *version_directive; /** The list of tag directives. */ struct { /** The beginning of the tag directives list. */ yaml_tag_directive_t *start; /** The end of the tag directives list. */ yaml_tag_directive_t *end; } tag_directives; /** Is the document start indicator implicit? */ int start_implicit; /** Is the document end indicator implicit? */ int end_implicit; /** The beginning of the document. */ yaml_mark_t start_mark; /** The end of the document. */ yaml_mark_t end_mark; } yaml_document_t; /** * Create a YAML document. * * @param[out] document An empty document object. * @param[in] version_directive The %YAML directive value or * @c NULL. * @param[in] tag_directives_start The beginning of the %TAG * directives list. * @param[in] tag_directives_end The end of the %TAG directives * list. * @param[in] start_implicit If the document start indicator is * implicit. * @param[in] end_implicit If the document end indicator is * implicit. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_document_initialize(yaml_document_t *document, yaml_version_directive_t *version_directive, yaml_tag_directive_t *tag_directives_start, yaml_tag_directive_t *tag_directives_end, int start_implicit, int end_implicit); /** * Delete a YAML document and all its nodes. * * @param[in,out] document A document object. */ YAML_DECLARE(void) yaml_document_delete(yaml_document_t *document); /** * Get a node of a YAML document. * * The pointer returned by this function is valid until any of the functions * modifying the documents are called. * * @param[in] document A document object. * @param[in] index The node id. * * @returns the node objct or @c NULL if @c node_id is out of range. */ YAML_DECLARE(yaml_node_t *) yaml_document_get_node(yaml_document_t *document, int index); /** * Get the root of a YAML document node. * * The root object is the first object added to the document. * * The pointer returned by this function is valid until any of the functions * modifying the documents are called. * * An empty document produced by the parser signifies the end of a YAML * stream. * * @param[in] document A document object. * * @returns the node object or @c NULL if the document is empty. */ YAML_DECLARE(yaml_node_t *) yaml_document_get_root_node(yaml_document_t *document); /** * Create a SCALAR node and attach it to the document. * * The @a style argument may be ignored by the emitter. * * @param[in,out] document A document object. * @param[in] tag The scalar tag. * @param[in] value The scalar value. * @param[in] length The length of the scalar value. * @param[in] style The scalar style. * * @returns the node id or @c 0 on error. */ YAML_DECLARE(int) yaml_document_add_scalar(yaml_document_t *document, yaml_char_t *tag, yaml_char_t *value, int length, yaml_scalar_style_t style); /** * Create a SEQUENCE node and attach it to the document. * * The @a style argument may be ignored by the emitter. * * @param[in,out] document A document object. * @param[in] tag The sequence tag. * @param[in] style The sequence style. * * @returns the node id or @c 0 on error. */ YAML_DECLARE(int) yaml_document_add_sequence(yaml_document_t *document, yaml_char_t *tag, yaml_sequence_style_t style); /** * Create a MAPPING node and attach it to the document. * * The @a style argument may be ignored by the emitter. * * @param[in,out] document A document object. * @param[in] tag The sequence tag. * @param[in] style The sequence style. * * @returns the node id or @c 0 on error. */ YAML_DECLARE(int) yaml_document_add_mapping(yaml_document_t *document, yaml_char_t *tag, yaml_mapping_style_t style); /** * Add an item to a SEQUENCE node. * * @param[in,out] document A document object. * @param[in] sequence The sequence node id. * @param[in] item The item node id. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_document_append_sequence_item(yaml_document_t *document, int sequence, int item); /** * Add a pair of a key and a value to a MAPPING node. * * @param[in,out] document A document object. * @param[in] mapping The mapping node id. * @param[in] key The key node id. * @param[in] value The value node id. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_document_append_mapping_pair(yaml_document_t *document, int mapping, int key, int value); /** @} */ /** * @defgroup parser Parser Definitions * @{ */ /** * The prototype of a read handler. * * The read handler is called when the parser needs to read more bytes from the * source. The handler should write not more than @a size bytes to the @a * buffer. The number of written bytes should be set to the @a length variable. * * @param[in,out] data A pointer to an application data specified by * yaml_parser_set_input(). * @param[out] buffer The buffer to write the data from the source. * @param[in] size The size of the buffer. * @param[out] size_read The actual number of bytes read from the source. * * @returns On success, the handler should return @c 1. If the handler failed, * the returned value should be @c 0. On EOF, the handler should set the * @a size_read to @c 0 and return @c 1. */ typedef int yaml_read_handler_t(void *data, unsigned char *buffer, size_t size, size_t *size_read); /** * This structure holds information about a potential simple key. */ typedef struct yaml_simple_key_s { /** Is a simple key possible? */ int possible; /** Is a simple key required? */ int required; /** The number of the token. */ size_t token_number; /** The position mark. */ yaml_mark_t mark; } yaml_simple_key_t; /** * The states of the parser. */ typedef enum yaml_parser_state_e { /** Expect STREAM-START. */ YAML_PARSE_STREAM_START_STATE, /** Expect the beginning of an implicit document. */ YAML_PARSE_IMPLICIT_DOCUMENT_START_STATE, /** Expect DOCUMENT-START. */ YAML_PARSE_DOCUMENT_START_STATE, /** Expect the content of a document. */ YAML_PARSE_DOCUMENT_CONTENT_STATE, /** Expect DOCUMENT-END. */ YAML_PARSE_DOCUMENT_END_STATE, /** Expect a block node. */ YAML_PARSE_BLOCK_NODE_STATE, /** Expect a block node or indentless sequence. */ YAML_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE, /** Expect a flow node. */ YAML_PARSE_FLOW_NODE_STATE, /** Expect the first entry of a block sequence. */ YAML_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE, /** Expect an entry of a block sequence. */ YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE, /** Expect an entry of an indentless sequence. */ YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE, /** Expect the first key of a block mapping. */ YAML_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE, /** Expect a block mapping key. */ YAML_PARSE_BLOCK_MAPPING_KEY_STATE, /** Expect a block mapping value. */ YAML_PARSE_BLOCK_MAPPING_VALUE_STATE, /** Expect the first entry of a flow sequence. */ YAML_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE, /** Expect an entry of a flow sequence. */ YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE, /** Expect a key of an ordered mapping. */ YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE, /** Expect a value of an ordered mapping. */ YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE, /** Expect the and of an ordered mapping entry. */ YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE, /** Expect the first key of a flow mapping. */ YAML_PARSE_FLOW_MAPPING_FIRST_KEY_STATE, /** Expect a key of a flow mapping. */ YAML_PARSE_FLOW_MAPPING_KEY_STATE, /** Expect a value of a flow mapping. */ YAML_PARSE_FLOW_MAPPING_VALUE_STATE, /** Expect an empty value of a flow mapping. */ YAML_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE, /** Expect nothing. */ YAML_PARSE_END_STATE } yaml_parser_state_t; /** * This structure holds aliases data. */ typedef struct yaml_alias_data_s { /** The anchor. */ yaml_char_t *anchor; /** The node id. */ int index; /** The anchor mark. */ yaml_mark_t mark; } yaml_alias_data_t; /** * The parser structure. * * All members are internal. Manage the structure using the @c yaml_parser_ * family of functions. */ typedef struct yaml_parser_s { /** * @name Error handling * @{ */ /** Error type. */ yaml_error_type_t error; /** Error description. */ const char *problem; /** The byte about which the problem occured. */ size_t problem_offset; /** The problematic value (@c -1 is none). */ int problem_value; /** The problem position. */ yaml_mark_t problem_mark; /** The error context. */ const char *context; /** The context position. */ yaml_mark_t context_mark; /** * @} */ /** * @name Reader stuff * @{ */ /** Read handler. */ yaml_read_handler_t *read_handler; /** A pointer for passing to the read handler. */ void *read_handler_data; /** Standard (string or file) input data. */ union { /** String input data. */ struct { /** The string start pointer. */ const unsigned char *start; /** The string end pointer. */ const unsigned char *end; /** The string current position. */ const unsigned char *current; } string; /** File input data. */ FILE *file; } input; /** EOF flag */ int eof; /** The working buffer. */ struct { /** The beginning of the buffer. */ yaml_char_t *start; /** The end of the buffer. */ yaml_char_t *end; /** The current position of the buffer. */ yaml_char_t *pointer; /** The last filled position of the buffer. */ yaml_char_t *last; } buffer; /* The number of unread characters in the buffer. */ size_t unread; /** The raw buffer. */ struct { /** The beginning of the buffer. */ unsigned char *start; /** The end of the buffer. */ unsigned char *end; /** The current position of the buffer. */ unsigned char *pointer; /** The last filled position of the buffer. */ unsigned char *last; } raw_buffer; /** The input encoding. */ yaml_encoding_t encoding; /** The offset of the current position (in bytes). */ size_t offset; /** The mark of the current position. */ yaml_mark_t mark; /** * @} */ /** * @name Scanner stuff * @{ */ /** Have we started to scan the input stream? */ int stream_start_produced; /** Have we reached the end of the input stream? */ int stream_end_produced; /** The number of unclosed '[' and '{' indicators. */ int flow_level; /** The tokens queue. */ struct { /** The beginning of the tokens queue. */ yaml_token_t *start; /** The end of the tokens queue. */ yaml_token_t *end; /** The head of the tokens queue. */ yaml_token_t *head; /** The tail of the tokens queue. */ yaml_token_t *tail; } tokens; /** The number of tokens fetched from the queue. */ size_t tokens_parsed; /* Does the tokens queue contain a token ready for dequeueing. */ int token_available; /** The indentation levels stack. */ struct { /** The beginning of the stack. */ int *start; /** The end of the stack. */ int *end; /** The top of the stack. */ int *top; } indents; /** The current indentation level. */ int indent; /** May a simple key occur at the current position? */ int simple_key_allowed; /** The stack of simple keys. */ struct { /** The beginning of the stack. */ yaml_simple_key_t *start; /** The end of the stack. */ yaml_simple_key_t *end; /** The top of the stack. */ yaml_simple_key_t *top; } simple_keys; /** * @} */ /** * @name Parser stuff * @{ */ /** The parser states stack. */ struct { /** The beginning of the stack. */ yaml_parser_state_t *start; /** The end of the stack. */ yaml_parser_state_t *end; /** The top of the stack. */ yaml_parser_state_t *top; } states; /** The current parser state. */ yaml_parser_state_t state; /** The stack of marks. */ struct { /** The beginning of the stack. */ yaml_mark_t *start; /** The end of the stack. */ yaml_mark_t *end; /** The top of the stack. */ yaml_mark_t *top; } marks; /** The list of TAG directives. */ struct { /** The beginning of the list. */ yaml_tag_directive_t *start; /** The end of the list. */ yaml_tag_directive_t *end; /** The top of the list. */ yaml_tag_directive_t *top; } tag_directives; /** * @} */ /** * @name Dumper stuff * @{ */ /** The alias data. */ struct { /** The beginning of the list. */ yaml_alias_data_t *start; /** The end of the list. */ yaml_alias_data_t *end; /** The top of the list. */ yaml_alias_data_t *top; } aliases; /** The currently parsed document. */ yaml_document_t *document; /** * @} */ } yaml_parser_t; /** * Initialize a parser. * * This function creates a new parser object. An application is responsible * for destroying the object using the yaml_parser_delete() function. * * @param[out] parser An empty parser object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_parser_initialize(yaml_parser_t *parser); /** * Destroy a parser. * * @param[in,out] parser A parser object. */ YAML_DECLARE(void) yaml_parser_delete(yaml_parser_t *parser); /** * Set a string input. * * Note that the @a input pointer must be valid while the @a parser object * exists. The application is responsible for destroing @a input after * destroying the @a parser. * * @param[in,out] parser A parser object. * @param[in] input A source data. * @param[in] size The length of the source data in bytes. */ YAML_DECLARE(void) yaml_parser_set_input_string(yaml_parser_t *parser, const unsigned char *input, size_t size); /** * Set a file input. * * @a file should be a file object open for reading. The application is * responsible for closing the @a file. * * @param[in,out] parser A parser object. * @param[in] file An open file. */ YAML_DECLARE(void) yaml_parser_set_input_file(yaml_parser_t *parser, FILE *file); /** * Set a generic input handler. * * @param[in,out] parser A parser object. * @param[in] handler A read handler. * @param[in] data Any application data for passing to the read * handler. */ YAML_DECLARE(void) yaml_parser_set_input(yaml_parser_t *parser, yaml_read_handler_t *handler, void *data); /** * Set the source encoding. * * @param[in,out] parser A parser object. * @param[in] encoding The source encoding. */ YAML_DECLARE(void) yaml_parser_set_encoding(yaml_parser_t *parser, yaml_encoding_t encoding); /** * Scan the input stream and produce the next token. * * Call the function subsequently to produce a sequence of tokens corresponding * to the input stream. The initial token has the type * @c YAML_STREAM_START_TOKEN while the ending token has the type * @c YAML_STREAM_END_TOKEN. * * An application is responsible for freeing any buffers associated with the * produced token object using the @c yaml_token_delete function. * * An application must not alternate the calls of yaml_parser_scan() with the * calls of yaml_parser_parse() or yaml_parser_load(). Doing this will break * the parser. * * @param[in,out] parser A parser object. * @param[out] token An empty token object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token); /** * Parse the input stream and produce the next parsing event. * * Call the function subsequently to produce a sequence of events corresponding * to the input stream. The initial event has the type * @c YAML_STREAM_START_EVENT while the ending event has the type * @c YAML_STREAM_END_EVENT. * * An application is responsible for freeing any buffers associated with the * produced event object using the yaml_event_delete() function. * * An application must not alternate the calls of yaml_parser_parse() with the * calls of yaml_parser_scan() or yaml_parser_load(). Doing this will break the * parser. * * @param[in,out] parser A parser object. * @param[out] event An empty event object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event); /** * Parse the input stream and produce the next YAML document. * * Call this function subsequently to produce a sequence of documents * constituting the input stream. * * If the produced document has no root node, it means that the document * end has been reached. * * An application is responsible for freeing any data associated with the * produced document object using the yaml_document_delete() function. * * An application must not alternate the calls of yaml_parser_load() with the * calls of yaml_parser_scan() or yaml_parser_parse(). Doing this will break * the parser. * * @param[in,out] parser A parser object. * @param[out] document An empty document object. * * @return @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_parser_load(yaml_parser_t *parser, yaml_document_t *document); /** @} */ /** * @defgroup emitter Emitter Definitions * @{ */ /** * The prototype of a write handler. * * The write handler is called when the emitter needs to flush the accumulated * characters to the output. The handler should write @a size bytes of the * @a buffer to the output. * * @param[in,out] data A pointer to an application data specified by * yaml_emitter_set_output(). * @param[in] buffer The buffer with bytes to be written. * @param[in] size The size of the buffer. * * @returns On success, the handler should return @c 1. If the handler failed, * the returned value should be @c 0. */ typedef int yaml_write_handler_t(void *data, unsigned char *buffer, size_t size); /** The emitter states. */ typedef enum yaml_emitter_state_e { /** Expect STREAM-START. */ YAML_EMIT_STREAM_START_STATE, /** Expect the first DOCUMENT-START or STREAM-END. */ YAML_EMIT_FIRST_DOCUMENT_START_STATE, /** Expect DOCUMENT-START or STREAM-END. */ YAML_EMIT_DOCUMENT_START_STATE, /** Expect the content of a document. */ YAML_EMIT_DOCUMENT_CONTENT_STATE, /** Expect DOCUMENT-END. */ YAML_EMIT_DOCUMENT_END_STATE, /** Expect the first item of a flow sequence. */ YAML_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE, /** Expect an item of a flow sequence. */ YAML_EMIT_FLOW_SEQUENCE_ITEM_STATE, /** Expect the first key of a flow mapping. */ YAML_EMIT_FLOW_MAPPING_FIRST_KEY_STATE, /** Expect a key of a flow mapping. */ YAML_EMIT_FLOW_MAPPING_KEY_STATE, /** Expect a value for a simple key of a flow mapping. */ YAML_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE, /** Expect a value of a flow mapping. */ YAML_EMIT_FLOW_MAPPING_VALUE_STATE, /** Expect the first item of a block sequence. */ YAML_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE, /** Expect an item of a block sequence. */ YAML_EMIT_BLOCK_SEQUENCE_ITEM_STATE, /** Expect the first key of a block mapping. */ YAML_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE, /** Expect the key of a block mapping. */ YAML_EMIT_BLOCK_MAPPING_KEY_STATE, /** Expect a value for a simple key of a block mapping. */ YAML_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE, /** Expect a value of a block mapping. */ YAML_EMIT_BLOCK_MAPPING_VALUE_STATE, /** Expect nothing. */ YAML_EMIT_END_STATE } yaml_emitter_state_t; /** * The emitter structure. * * All members are internal. Manage the structure using the @c yaml_emitter_ * family of functions. */ typedef struct yaml_emitter_s { /** * @name Error handling * @{ */ /** Error type. */ yaml_error_type_t error; /** Error description. */ const char *problem; /** * @} */ /** * @name Writer stuff * @{ */ /** Write handler. */ yaml_write_handler_t *write_handler; /** A pointer for passing to the white handler. */ void *write_handler_data; /** Standard (string or file) output data. */ union { /** String output data. */ struct { /** The buffer pointer. */ unsigned char *buffer; /** The buffer size. */ size_t size; /** The number of written bytes. */ size_t *size_written; } string; /** File output data. */ FILE *file; } output; /** The working buffer. */ struct { /** The beginning of the buffer. */ yaml_char_t *start; /** The end of the buffer. */ yaml_char_t *end; /** The current position of the buffer. */ yaml_char_t *pointer; /** The last filled position of the buffer. */ yaml_char_t *last; } buffer; /** The raw buffer. */ struct { /** The beginning of the buffer. */ unsigned char *start; /** The end of the buffer. */ unsigned char *end; /** The current position of the buffer. */ unsigned char *pointer; /** The last filled position of the buffer. */ unsigned char *last; } raw_buffer; /** The stream encoding. */ yaml_encoding_t encoding; /** * @} */ /** * @name Emitter stuff * @{ */ /** If the output is in the canonical style? */ int canonical; /** The number of indentation spaces. */ int best_indent; /** The preferred width of the output lines. */ int best_width; /** Allow unescaped non-ASCII characters? */ int unicode; /** The preferred line break. */ yaml_break_t line_break; /** The stack of states. */ struct { /** The beginning of the stack. */ yaml_emitter_state_t *start; /** The end of the stack. */ yaml_emitter_state_t *end; /** The top of the stack. */ yaml_emitter_state_t *top; } states; /** The current emitter state. */ yaml_emitter_state_t state; /** The event queue. */ struct { /** The beginning of the event queue. */ yaml_event_t *start; /** The end of the event queue. */ yaml_event_t *end; /** The head of the event queue. */ yaml_event_t *head; /** The tail of the event queue. */ yaml_event_t *tail; } events; /** The stack of indentation levels. */ struct { /** The beginning of the stack. */ int *start; /** The end of the stack. */ int *end; /** The top of the stack. */ int *top; } indents; /** The list of tag directives. */ struct { /** The beginning of the list. */ yaml_tag_directive_t *start; /** The end of the list. */ yaml_tag_directive_t *end; /** The top of the list. */ yaml_tag_directive_t *top; } tag_directives; /** The current indentation level. */ int indent; /** The current flow level. */ int flow_level; /** Is it the document root context? */ int root_context; /** Is it a sequence context? */ int sequence_context; /** Is it a mapping context? */ int mapping_context; /** Is it a simple mapping key context? */ int simple_key_context; /** The current line. */ int line; /** The current column. */ int column; /** If the last character was a whitespace? */ int whitespace; /** If the last character was an indentation character (' ', '-', '?', ':')? */ int indention; /** If an explicit document end is required? */ int open_ended; /** Anchor analysis. */ struct { /** The anchor value. */ yaml_char_t *anchor; /** The anchor length. */ size_t anchor_length; /** Is it an alias? */ int alias; } anchor_data; /** Tag analysis. */ struct { /** The tag handle. */ yaml_char_t *handle; /** The tag handle length. */ size_t handle_length; /** The tag suffix. */ yaml_char_t *suffix; /** The tag suffix length. */ size_t suffix_length; } tag_data; /** Scalar analysis. */ struct { /** The scalar value. */ yaml_char_t *value; /** The scalar length. */ size_t length; /** Does the scalar contain line breaks? */ int multiline; /** Can the scalar be expessed in the flow plain style? */ int flow_plain_allowed; /** Can the scalar be expressed in the block plain style? */ int block_plain_allowed; /** Can the scalar be expressed in the single quoted style? */ int single_quoted_allowed; /** Can the scalar be expressed in the literal or folded styles? */ int block_allowed; /** The output style. */ yaml_scalar_style_t style; } scalar_data; /** * @} */ /** * @name Dumper stuff * @{ */ /** If the stream was already opened? */ int opened; /** If the stream was already closed? */ int closed; /** The information associated with the document nodes. */ struct { /** The number of references. */ int references; /** The anchor id. */ int anchor; /** If the node has been emitted? */ int serialized; } *anchors; /** The last assigned anchor id. */ int last_anchor_id; /** The currently emitted document. */ yaml_document_t *document; /** * @} */ } yaml_emitter_t; /** * Initialize an emitter. * * This function creates a new emitter object. An application is responsible * for destroying the object using the yaml_emitter_delete() function. * * @param[out] emitter An empty parser object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_emitter_initialize(yaml_emitter_t *emitter); /** * Destroy an emitter. * * @param[in,out] emitter An emitter object. */ YAML_DECLARE(void) yaml_emitter_delete(yaml_emitter_t *emitter); /** * Set a string output. * * The emitter will write the output characters to the @a output buffer of the * size @a size. The emitter will set @a size_written to the number of written * bytes. If the buffer is smaller than required, the emitter produces the * YAML_WRITE_ERROR error. * * @param[in,out] emitter An emitter object. * @param[in] output An output buffer. * @param[in] size The buffer size. * @param[in] size_written The pointer to save the number of written * bytes. */ YAML_DECLARE(void) yaml_emitter_set_output_string(yaml_emitter_t *emitter, unsigned char *output, size_t size, size_t *size_written); /** * Set a file output. * * @a file should be a file object open for writing. The application is * responsible for closing the @a file. * * @param[in,out] emitter An emitter object. * @param[in] file An open file. */ YAML_DECLARE(void) yaml_emitter_set_output_file(yaml_emitter_t *emitter, FILE *file); /** * Set a generic output handler. * * @param[in,out] emitter An emitter object. * @param[in] handler A write handler. * @param[in] data Any application data for passing to the write * handler. */ YAML_DECLARE(void) yaml_emitter_set_output(yaml_emitter_t *emitter, yaml_write_handler_t *handler, void *data); /** * Set the output encoding. * * @param[in,out] emitter An emitter object. * @param[in] encoding The output encoding. */ YAML_DECLARE(void) yaml_emitter_set_encoding(yaml_emitter_t *emitter, yaml_encoding_t encoding); /** * Set if the output should be in the "canonical" format as in the YAML * specification. * * @param[in,out] emitter An emitter object. * @param[in] canonical If the output is canonical. */ YAML_DECLARE(void) yaml_emitter_set_canonical(yaml_emitter_t *emitter, int canonical); /** * Set the intendation increment. * * @param[in,out] emitter An emitter object. * @param[in] indent The indentation increment (1 < . < 10). */ YAML_DECLARE(void) yaml_emitter_set_indent(yaml_emitter_t *emitter, int indent); /** * Set the preferred line width. @c -1 means unlimited. * * @param[in,out] emitter An emitter object. * @param[in] width The preferred line width. */ YAML_DECLARE(void) yaml_emitter_set_width(yaml_emitter_t *emitter, int width); /** * Set if unescaped non-ASCII characters are allowed. * * @param[in,out] emitter An emitter object. * @param[in] unicode If unescaped Unicode characters are allowed. */ YAML_DECLARE(void) yaml_emitter_set_unicode(yaml_emitter_t *emitter, int unicode); /** * Set the preferred line break. * * @param[in,out] emitter An emitter object. * @param[in] line_break The preferred line break. */ YAML_DECLARE(void) yaml_emitter_set_break(yaml_emitter_t *emitter, yaml_break_t line_break); /** * Emit an event. * * The event object may be generated using the yaml_parser_parse() function. * The emitter takes the responsibility for the event object and destroys its * content after it is emitted. The event object is destroyed even if the * function fails. * * @param[in,out] emitter An emitter object. * @param[in,out] event An event object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_emitter_emit(yaml_emitter_t *emitter, yaml_event_t *event); /** * Start a YAML stream. * * This function should be used before yaml_emitter_dump() is called. * * @param[in,out] emitter An emitter object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_emitter_open(yaml_emitter_t *emitter); /** * Finish a YAML stream. * * This function should be used after yaml_emitter_dump() is called. * * @param[in,out] emitter An emitter object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_emitter_close(yaml_emitter_t *emitter); /** * Emit a YAML document. * * The documen object may be generated using the yaml_parser_load() function * or the yaml_document_initialize() function. The emitter takes the * responsibility for the document object and destoys its content after * it is emitted. The document object is destroyedeven if the function fails. * * @param[in,out] emitter An emitter object. * @param[in,out] document A document object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_emitter_dump(yaml_emitter_t *emitter, yaml_document_t *document); /** * Flush the accumulated characters to the output. * * @param[in,out] emitter An emitter object. * * @returns @c 1 if the function succeeded, @c 0 on error. */ YAML_DECLARE(int) yaml_emitter_flush(yaml_emitter_t *emitter); /** @} */ #ifdef __cplusplus } #endif #endif /* #ifndef YAML_H */ flvmeta-1.2.2/src/libyaml/yaml_private.h000066400000000000000000000667131346231570700202640ustar00rootroot00000000000000 #if HAVE_CONFIG_H #include #endif #include #include #include /* * Memory management. */ YAML_DECLARE(void *) yaml_malloc(size_t size); YAML_DECLARE(void *) yaml_realloc(void *ptr, size_t size); YAML_DECLARE(void) yaml_free(void *ptr); YAML_DECLARE(yaml_char_t *) yaml_strdup(const yaml_char_t *); /* * Reader: Ensure that the buffer contains at least `length` bytes. */ YAML_DECLARE(int) yaml_parser_update_buffer(yaml_parser_t *parser, size_t length); /* * Scanner: Ensure that the token stack contains at least one token ready. */ YAML_DECLARE(int) yaml_parser_fetch_more_tokens(yaml_parser_t *parser); /* * The size of the input raw buffer. */ #define INPUT_RAW_BUFFER_SIZE 16384 /* * The size of the input buffer. * * It should be possible to decode the whole raw buffer. */ #define INPUT_BUFFER_SIZE (INPUT_RAW_BUFFER_SIZE*3) /* * The size of the output buffer. */ #define OUTPUT_BUFFER_SIZE 16384 /* * The size of the output raw buffer. * * It should be possible to encode the whole output buffer. */ #define OUTPUT_RAW_BUFFER_SIZE (OUTPUT_BUFFER_SIZE*2+2) /* * The size of other stacks and queues. */ #define INITIAL_STACK_SIZE 16 #define INITIAL_QUEUE_SIZE 16 #define INITIAL_STRING_SIZE 16 /* * Buffer management. */ #define BUFFER_INIT(context,buffer,size) \ (((buffer).start = yaml_malloc(size)) ? \ ((buffer).last = (buffer).pointer = (buffer).start, \ (buffer).end = (buffer).start+(size), \ 1) : \ ((context)->error = YAML_MEMORY_ERROR, \ 0)) #define BUFFER_DEL(context,buffer) \ (yaml_free((buffer).start), \ (buffer).start = (buffer).pointer = (buffer).end = 0) /* * String management. */ typedef struct { yaml_char_t *start; yaml_char_t *end; yaml_char_t *pointer; } yaml_string_t; YAML_DECLARE(int) yaml_string_extend(yaml_char_t **start, yaml_char_t **pointer, yaml_char_t **end); YAML_DECLARE(int) yaml_string_join( yaml_char_t **a_start, yaml_char_t **a_pointer, yaml_char_t **a_end, yaml_char_t **b_start, yaml_char_t **b_pointer, yaml_char_t **b_end); #define NULL_STRING { NULL, NULL, NULL } #define STRING(string,length) { (string), (string)+(length), (string) } #define STRING_ASSIGN(value,string,length) \ ((value).start = (string), \ (value).end = (string)+(length), \ (value).pointer = (string)) #define STRING_INIT(context,string,size) \ (((string).start = yaml_malloc(size)) ? \ ((string).pointer = (string).start, \ (string).end = (string).start+(size), \ memset((string).start, 0, (size)), \ 1) : \ ((context)->error = YAML_MEMORY_ERROR, \ 0)) #define STRING_DEL(context,string) \ (yaml_free((string).start), \ (string).start = (string).pointer = (string).end = 0) #define STRING_EXTEND(context,string) \ (((string).pointer+5 < (string).end) \ || yaml_string_extend(&(string).start, \ &(string).pointer, &(string).end)) #define CLEAR(context,string) \ ((string).pointer = (string).start, \ memset((string).start, 0, (string).end-(string).start)) #define JOIN(context,string_a,string_b) \ ((yaml_string_join(&(string_a).start, &(string_a).pointer, \ &(string_a).end, &(string_b).start, \ &(string_b).pointer, &(string_b).end)) ? \ ((string_b).pointer = (string_b).start, \ 1) : \ ((context)->error = YAML_MEMORY_ERROR, \ 0)) /* * String check operations. */ /* * Check the octet at the specified position. */ #define CHECK_AT(string,octet,offset) \ ((string).pointer[offset] == (yaml_char_t)(octet)) /* * Check the current octet in the buffer. */ #define CHECK(string,octet) CHECK_AT((string),(octet),0) /* * Check if the character at the specified position is an alphabetical * character, a digit, '_', or '-'. */ #define IS_ALPHA_AT(string,offset) \ (((string).pointer[offset] >= (yaml_char_t) '0' && \ (string).pointer[offset] <= (yaml_char_t) '9') || \ ((string).pointer[offset] >= (yaml_char_t) 'A' && \ (string).pointer[offset] <= (yaml_char_t) 'Z') || \ ((string).pointer[offset] >= (yaml_char_t) 'a' && \ (string).pointer[offset] <= (yaml_char_t) 'z') || \ (string).pointer[offset] == '_' || \ (string).pointer[offset] == '-') #define IS_ALPHA(string) IS_ALPHA_AT((string),0) /* * Check if the character at the specified position is a digit. */ #define IS_DIGIT_AT(string,offset) \ (((string).pointer[offset] >= (yaml_char_t) '0' && \ (string).pointer[offset] <= (yaml_char_t) '9')) #define IS_DIGIT(string) IS_DIGIT_AT((string),0) /* * Get the value of a digit. */ #define AS_DIGIT_AT(string,offset) \ ((string).pointer[offset] - (yaml_char_t) '0') #define AS_DIGIT(string) AS_DIGIT_AT((string),0) /* * Check if the character at the specified position is a hex-digit. */ #define IS_HEX_AT(string,offset) \ (((string).pointer[offset] >= (yaml_char_t) '0' && \ (string).pointer[offset] <= (yaml_char_t) '9') || \ ((string).pointer[offset] >= (yaml_char_t) 'A' && \ (string).pointer[offset] <= (yaml_char_t) 'F') || \ ((string).pointer[offset] >= (yaml_char_t) 'a' && \ (string).pointer[offset] <= (yaml_char_t) 'f')) #define IS_HEX(string) IS_HEX_AT((string),0) /* * Get the value of a hex-digit. */ #define AS_HEX_AT(string,offset) \ (((string).pointer[offset] >= (yaml_char_t) 'A' && \ (string).pointer[offset] <= (yaml_char_t) 'F') ? \ ((string).pointer[offset] - (yaml_char_t) 'A' + 10) : \ ((string).pointer[offset] >= (yaml_char_t) 'a' && \ (string).pointer[offset] <= (yaml_char_t) 'f') ? \ ((string).pointer[offset] - (yaml_char_t) 'a' + 10) : \ ((string).pointer[offset] - (yaml_char_t) '0')) #define AS_HEX(string) AS_HEX_AT((string),0) /* * Check if the character is ASCII. */ #define IS_ASCII_AT(string,offset) \ ((string).pointer[offset] <= (yaml_char_t) '\x7F') #define IS_ASCII(string) IS_ASCII_AT((string),0) /* * Check if the character can be printed unescaped. * Only correct if you know you are looking at valid UTF-8! */ #define IS_PRINTABLE_AT(string,offset) \ (((string).pointer[offset] == 0x0A) /* . == #x0A */ \ || ((string).pointer[offset] >= 0x20 /* #x20 <= . <= #x7E */ \ && (string).pointer[offset] <= 0x7E) \ || ((string).pointer[offset] == 0xC2 /* #0xA0 <= . <= #xD7FF */ \ && (string).pointer[offset+1] >= 0xA0) \ || ((string).pointer[offset] > 0xC2 \ && (string).pointer[offset] < 0xED) \ || ((string).pointer[offset] == 0xED \ && (string).pointer[offset+1] < 0xA0) \ || ((string).pointer[offset] == 0xEE) \ || ((string).pointer[offset] == 0xEF /* #xE000 <= . <= #xFFFD */ \ && !((string).pointer[offset+1] == 0xBB /* && . != #xFEFF */ \ && (string).pointer[offset+2] == 0xBF) \ && !((string).pointer[offset+1] == 0xBF \ && ((string).pointer[offset+2] == 0xBE \ || (string).pointer[offset+2] == 0xBF)))) #define IS_PRINTABLE(string) IS_PRINTABLE_AT((string),0) /* * Check if the character at the specified position is NUL. */ #define IS_Z_AT(string,offset) CHECK_AT((string),'\0',(offset)) #define IS_Z(string) IS_Z_AT((string),0) /* * Check if the character at the specified position is BOM. */ #define IS_BOM_AT(string,offset) \ (CHECK_AT((string),'\xEF',(offset)) \ && CHECK_AT((string),'\xBB',(offset)+1) \ && CHECK_AT((string),'\xBF',(offset)+2)) /* BOM (#xFEFF) */ #define IS_BOM(string) IS_BOM_AT(string,0) /* * Check if the character at the specified position is space. */ #define IS_SPACE_AT(string,offset) CHECK_AT((string),' ',(offset)) #define IS_SPACE(string) IS_SPACE_AT((string),0) /* * Check if the character at the specified position is tab. */ #define IS_TAB_AT(string,offset) CHECK_AT((string),'\t',(offset)) #define IS_TAB(string) IS_TAB_AT((string),0) /* * Check if the character at the specified position is blank (space or tab). */ #define IS_BLANK_AT(string,offset) \ (IS_SPACE_AT((string),(offset)) || IS_TAB_AT((string),(offset))) #define IS_BLANK(string) IS_BLANK_AT((string),0) /* * Check if the character at the specified position is a line break. */ #define IS_BREAK_AT(string,offset) \ (CHECK_AT((string),'\r',(offset)) /* CR (#xD)*/ \ || CHECK_AT((string),'\n',(offset)) /* LF (#xA) */ \ || (CHECK_AT((string),'\xC2',(offset)) \ && CHECK_AT((string),'\x85',(offset)+1)) /* NEL (#x85) */ \ || (CHECK_AT((string),'\xE2',(offset)) \ && CHECK_AT((string),'\x80',(offset)+1) \ && CHECK_AT((string),'\xA8',(offset)+2)) /* LS (#x2028) */ \ || (CHECK_AT((string),'\xE2',(offset)) \ && CHECK_AT((string),'\x80',(offset)+1) \ && CHECK_AT((string),'\xA9',(offset)+2))) /* PS (#x2029) */ #define IS_BREAK(string) IS_BREAK_AT((string),0) #define IS_CRLF_AT(string,offset) \ (CHECK_AT((string),'\r',(offset)) && CHECK_AT((string),'\n',(offset)+1)) #define IS_CRLF(string) IS_CRLF_AT((string),0) /* * Check if the character is a line break or NUL. */ #define IS_BREAKZ_AT(string,offset) \ (IS_BREAK_AT((string),(offset)) || IS_Z_AT((string),(offset))) #define IS_BREAKZ(string) IS_BREAKZ_AT((string),0) /* * Check if the character is a line break, space, or NUL. */ #define IS_SPACEZ_AT(string,offset) \ (IS_SPACE_AT((string),(offset)) || IS_BREAKZ_AT((string),(offset))) #define IS_SPACEZ(string) IS_SPACEZ_AT((string),0) /* * Check if the character is a line break, space, tab, or NUL. */ #define IS_BLANKZ_AT(string,offset) \ (IS_BLANK_AT((string),(offset)) || IS_BREAKZ_AT((string),(offset))) #define IS_BLANKZ(string) IS_BLANKZ_AT((string),0) /* * Determine the width of the character. * Only correct if you know you are looking at valid UTF-8! */ #define WIDTH_AT(string,offset) \ (((string).pointer[offset] & 0x80) == 0x00 ? 1 : \ ((string).pointer[offset] & 0xE0) == 0xC0 ? 2 : \ ((string).pointer[offset] & 0xF0) == 0xE0 ? 3 : \ ((string).pointer[offset] & 0xF8) == 0xF0 ? 4 : 0) #define WIDTH(string) WIDTH_AT((string),0) /* * Move the string pointer to the next byte. */ #define MOVE(string) ((string).pointer++) #define MOVEN(string,n) ((string).pointer += n) /* * Copy a byte and move the pointers of both strings. */ #define COPY(string_a,string_b) \ (*((string_a).pointer++) = *((string_b).pointer++)) #define COPYN(string_a,string_b,n) \ (memcpy(string_a.pointer, string_b.pointer, n), \ string_a.pointer += n, string_b.pointer += n) /* * Stack and queue management. */ YAML_DECLARE(int) yaml_stack_extend(void **start, void **top, void **end); YAML_DECLARE(int) yaml_queue_extend(void **start, void **head, void **tail, void **end); #define STACK_INIT(context,stack,size) \ (((stack).start = yaml_malloc((size)*sizeof(*(stack).start))) ? \ ((stack).top = (stack).start, \ (stack).end = (stack).start+(size), \ 1) : \ ((context)->error = YAML_MEMORY_ERROR, \ 0)) #define STACK_DEL(context,stack) \ (yaml_free((stack).start), \ (stack).start = (stack).top = (stack).end = 0) #define STACK_EMPTY(context,stack) \ ((stack).start == (stack).top) #define PUSH(context,stack,value) \ (((stack).top != (stack).end \ || yaml_stack_extend((void **)&(stack).start, \ (void **)&(stack).top, (void **)&(stack).end)) ? \ (*((stack).top++) = value, \ 1) : \ ((context)->error = YAML_MEMORY_ERROR, \ 0)) #define POP(context,stack) \ (*(--(stack).top)) #define QUEUE_INIT(context,queue,size) \ (((queue).start = yaml_malloc((size)*sizeof(*(queue).start))) ? \ ((queue).head = (queue).tail = (queue).start, \ (queue).end = (queue).start+(size), \ 1) : \ ((context)->error = YAML_MEMORY_ERROR, \ 0)) #define QUEUE_DEL(context,queue) \ (yaml_free((queue).start), \ (queue).start = (queue).head = (queue).tail = (queue).end = 0) #define QUEUE_EMPTY(context,queue) \ ((queue).head == (queue).tail) #define ENQUEUE(context,queue,value) \ (((queue).tail != (queue).end \ || yaml_queue_extend((void **)&(queue).start, (void **)&(queue).head, \ (void **)&(queue).tail, (void **)&(queue).end)) ? \ (*((queue).tail++) = value, \ 1) : \ ((context)->error = YAML_MEMORY_ERROR, \ 0)) #define DEQUEUE(context,queue) \ (*((queue).head++)) #define QUEUE_INSERT(context,queue,index,value) \ (((queue).tail != (queue).end \ || yaml_queue_extend((void **)&(queue).start, (void **)&(queue).head, \ (void **)&(queue).tail, (void **)&(queue).end)) ? \ (memmove((queue).head+(index)+1,(queue).head+(index), \ ((queue).tail-(queue).head-(index))*sizeof(*(queue).start)), \ *((queue).head+(index)) = value, \ (queue).tail++, \ 1) : \ ((context)->error = YAML_MEMORY_ERROR, \ 0)) /* * Token initializers. */ #define TOKEN_INIT(token,token_type,token_start_mark,token_end_mark) \ (memset(&(token), 0, sizeof(yaml_token_t)), \ (token).type = (token_type), \ (token).start_mark = (token_start_mark), \ (token).end_mark = (token_end_mark)) #define STREAM_START_TOKEN_INIT(token,token_encoding,start_mark,end_mark) \ (TOKEN_INIT((token),YAML_STREAM_START_TOKEN,(start_mark),(end_mark)), \ (token).data.stream_start.encoding = (token_encoding)) #define STREAM_END_TOKEN_INIT(token,start_mark,end_mark) \ (TOKEN_INIT((token),YAML_STREAM_END_TOKEN,(start_mark),(end_mark))) #define ALIAS_TOKEN_INIT(token,token_value,start_mark,end_mark) \ (TOKEN_INIT((token),YAML_ALIAS_TOKEN,(start_mark),(end_mark)), \ (token).data.alias.value = (token_value)) #define ANCHOR_TOKEN_INIT(token,token_value,start_mark,end_mark) \ (TOKEN_INIT((token),YAML_ANCHOR_TOKEN,(start_mark),(end_mark)), \ (token).data.anchor.value = (token_value)) #define TAG_TOKEN_INIT(token,token_handle,token_suffix,start_mark,end_mark) \ (TOKEN_INIT((token),YAML_TAG_TOKEN,(start_mark),(end_mark)), \ (token).data.tag.handle = (token_handle), \ (token).data.tag.suffix = (token_suffix)) #define SCALAR_TOKEN_INIT(token,token_value,token_length,token_style,start_mark,end_mark) \ (TOKEN_INIT((token),YAML_SCALAR_TOKEN,(start_mark),(end_mark)), \ (token).data.scalar.value = (token_value), \ (token).data.scalar.length = (token_length), \ (token).data.scalar.style = (token_style)) #define VERSION_DIRECTIVE_TOKEN_INIT(token,token_major,token_minor,start_mark,end_mark) \ (TOKEN_INIT((token),YAML_VERSION_DIRECTIVE_TOKEN,(start_mark),(end_mark)), \ (token).data.version_directive.major = (token_major), \ (token).data.version_directive.minor = (token_minor)) #define TAG_DIRECTIVE_TOKEN_INIT(token,token_handle,token_prefix,start_mark,end_mark) \ (TOKEN_INIT((token),YAML_TAG_DIRECTIVE_TOKEN,(start_mark),(end_mark)), \ (token).data.tag_directive.handle = (token_handle), \ (token).data.tag_directive.prefix = (token_prefix)) /* * Event initializers. */ #define EVENT_INIT(event,event_type,event_start_mark,event_end_mark) \ (memset(&(event), 0, sizeof(yaml_event_t)), \ (event).type = (event_type), \ (event).start_mark = (event_start_mark), \ (event).end_mark = (event_end_mark)) #define STREAM_START_EVENT_INIT(event,event_encoding,start_mark,end_mark) \ (EVENT_INIT((event),YAML_STREAM_START_EVENT,(start_mark),(end_mark)), \ (event).data.stream_start.encoding = (event_encoding)) #define STREAM_END_EVENT_INIT(event,start_mark,end_mark) \ (EVENT_INIT((event),YAML_STREAM_END_EVENT,(start_mark),(end_mark))) #define DOCUMENT_START_EVENT_INIT(event,event_version_directive, \ event_tag_directives_start,event_tag_directives_end,event_implicit,start_mark,end_mark) \ (EVENT_INIT((event),YAML_DOCUMENT_START_EVENT,(start_mark),(end_mark)), \ (event).data.document_start.version_directive = (event_version_directive), \ (event).data.document_start.tag_directives.start = (event_tag_directives_start), \ (event).data.document_start.tag_directives.end = (event_tag_directives_end), \ (event).data.document_start.implicit = (event_implicit)) #define DOCUMENT_END_EVENT_INIT(event,event_implicit,start_mark,end_mark) \ (EVENT_INIT((event),YAML_DOCUMENT_END_EVENT,(start_mark),(end_mark)), \ (event).data.document_end.implicit = (event_implicit)) #define ALIAS_EVENT_INIT(event,event_anchor,start_mark,end_mark) \ (EVENT_INIT((event),YAML_ALIAS_EVENT,(start_mark),(end_mark)), \ (event).data.alias.anchor = (event_anchor)) #define SCALAR_EVENT_INIT(event,event_anchor,event_tag,event_value,event_length, \ event_plain_implicit, event_quoted_implicit,event_style,start_mark,end_mark) \ (EVENT_INIT((event),YAML_SCALAR_EVENT,(start_mark),(end_mark)), \ (event).data.scalar.anchor = (event_anchor), \ (event).data.scalar.tag = (event_tag), \ (event).data.scalar.value = (event_value), \ (event).data.scalar.length = (event_length), \ (event).data.scalar.plain_implicit = (event_plain_implicit), \ (event).data.scalar.quoted_implicit = (event_quoted_implicit), \ (event).data.scalar.style = (event_style)) #define SEQUENCE_START_EVENT_INIT(event,event_anchor,event_tag, \ event_implicit,event_style,start_mark,end_mark) \ (EVENT_INIT((event),YAML_SEQUENCE_START_EVENT,(start_mark),(end_mark)), \ (event).data.sequence_start.anchor = (event_anchor), \ (event).data.sequence_start.tag = (event_tag), \ (event).data.sequence_start.implicit = (event_implicit), \ (event).data.sequence_start.style = (event_style)) #define SEQUENCE_END_EVENT_INIT(event,start_mark,end_mark) \ (EVENT_INIT((event),YAML_SEQUENCE_END_EVENT,(start_mark),(end_mark))) #define MAPPING_START_EVENT_INIT(event,event_anchor,event_tag, \ event_implicit,event_style,start_mark,end_mark) \ (EVENT_INIT((event),YAML_MAPPING_START_EVENT,(start_mark),(end_mark)), \ (event).data.mapping_start.anchor = (event_anchor), \ (event).data.mapping_start.tag = (event_tag), \ (event).data.mapping_start.implicit = (event_implicit), \ (event).data.mapping_start.style = (event_style)) #define MAPPING_END_EVENT_INIT(event,start_mark,end_mark) \ (EVENT_INIT((event),YAML_MAPPING_END_EVENT,(start_mark),(end_mark))) /* * Document initializer. */ #define DOCUMENT_INIT(document,document_nodes_start,document_nodes_end, \ document_version_directive,document_tag_directives_start, \ document_tag_directives_end,document_start_implicit, \ document_end_implicit,document_start_mark,document_end_mark) \ (memset(&(document), 0, sizeof(yaml_document_t)), \ (document).nodes.start = (document_nodes_start), \ (document).nodes.end = (document_nodes_end), \ (document).nodes.top = (document_nodes_start), \ (document).version_directive = (document_version_directive), \ (document).tag_directives.start = (document_tag_directives_start), \ (document).tag_directives.end = (document_tag_directives_end), \ (document).start_implicit = (document_start_implicit), \ (document).end_implicit = (document_end_implicit), \ (document).start_mark = (document_start_mark), \ (document).end_mark = (document_end_mark)) /* * Node initializers. */ #define NODE_INIT(node,node_type,node_tag,node_start_mark,node_end_mark) \ (memset(&(node), 0, sizeof(yaml_node_t)), \ (node).type = (node_type), \ (node).tag = (node_tag), \ (node).start_mark = (node_start_mark), \ (node).end_mark = (node_end_mark)) #define SCALAR_NODE_INIT(node,node_tag,node_value,node_length, \ node_style,start_mark,end_mark) \ (NODE_INIT((node),YAML_SCALAR_NODE,(node_tag),(start_mark),(end_mark)), \ (node).data.scalar.value = (node_value), \ (node).data.scalar.length = (node_length), \ (node).data.scalar.style = (node_style)) #define SEQUENCE_NODE_INIT(node,node_tag,node_items_start,node_items_end, \ node_style,start_mark,end_mark) \ (NODE_INIT((node),YAML_SEQUENCE_NODE,(node_tag),(start_mark),(end_mark)), \ (node).data.sequence.items.start = (node_items_start), \ (node).data.sequence.items.end = (node_items_end), \ (node).data.sequence.items.top = (node_items_start), \ (node).data.sequence.style = (node_style)) #define MAPPING_NODE_INIT(node,node_tag,node_pairs_start,node_pairs_end, \ node_style,start_mark,end_mark) \ (NODE_INIT((node),YAML_MAPPING_NODE,(node_tag),(start_mark),(end_mark)), \ (node).data.mapping.pairs.start = (node_pairs_start), \ (node).data.mapping.pairs.end = (node_pairs_end), \ (node).data.mapping.pairs.top = (node_pairs_start), \ (node).data.mapping.style = (node_style)) flvmeta-1.2.2/src/types.c000066400000000000000000000047161346231570700152710ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "types.h" #ifndef WORDS_BIGENDIAN /* swap 64 bits doubles */ typedef union __convert_u { uint64 i; number64 f; } convert_u; number64 swap_number64(number64 n) { convert_u c; c.f = n; c.i = (((c.i & 0x00000000000000FFULL) << 56) | ((c.i & 0x000000000000FF00ULL) << 40) | ((c.i & 0x0000000000FF0000ULL) << 24) | ((c.i & 0x00000000FF000000ULL) << 8) | ((c.i & 0x000000FF00000000ULL) >> 8) | ((c.i & 0x0000FF0000000000ULL) >> 24) | ((c.i & 0x00FF000000000000ULL) >> 40) | ((c.i & 0xFF00000000000000ULL) >> 56)); return c.f; } #endif /* !defined WORDS_BIGENDIAN */ /* convert native integers into 24 bits big endian integers */ uint24_be uint32_to_uint24_be(uint32 l) { uint24_be r; r.b[0] = (uint8)((l & 0x00FF0000U) >> 16); r.b[1] = (uint8)((l & 0x0000FF00U) >> 8); r.b[2] = (uint8) (l & 0x000000FFU); return r; } #ifdef WIN32 /* These functions assume fpos_t is a 64-bit signed integer */ file_offset_t lfs_ftell(FILE * stream) { fpos_t p; if (fgetpos(stream, &p) == 0) { return (file_offset_t)p; } else { return -1LL; } } int lfs_fseek(FILE * stream, file_offset_t offset, int whence) { fpos_t p; if (fgetpos(stream, &p) == 0) { switch (whence) { case SEEK_CUR: p += offset; break; case SEEK_SET: p = offset; break; /*case SEEK_END:; not implemented here */ default: return -1; } fsetpos(stream, &p); return 0; } else { return -1; } } #endif /* WIN32 */ flvmeta-1.2.2/src/types.h000066400000000000000000000100571346231570700152710ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __TYPES_H__ #define __TYPES_H__ /* Configuration of the sources */ #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_STDDEF_H # include /* ptrdiff_t */ #endif #ifdef HAVE_SYS_TYPES_H # include /* off_t */ #endif #ifdef HAVE_INTTYPES_H # include #endif #include typedef uint8_t byte, uint8, uint8_bitmask; typedef int8_t sint8; typedef uint16_t uint16, uint16_be, uint16_le; typedef int16_t sint16, sint16_be, sint16_le; typedef uint32_t uint32, uint32_be, uint32_le; typedef int32_t sint32, sint32_be, sint32_le; typedef struct __uint24 { uint8 b[3]; } uint24, uint24_be, uint24_le; typedef uint64_t uint64, uint64_le, uint64_be; typedef int64_t sint64, sint64_le, sint64_be; typedef #if SIZEOF_FLOAT == 8 float #elif SIZEOF_DOUBLE == 8 double #elif SIZEOF_LONG_DOUBLE == 8 long double #else uint64_t #endif number64, number64_le, number64_be; #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #ifdef WORDS_BIGENDIAN # define swap_uint16(x) (x) # define swap_sint16(x) (x) # define swap_uint32(x) (x) # define swap_number64(x) (x) #else /* !defined WORDS_BIGENDIAN */ /* swap 16 bits integers */ # define swap_uint16(x) ((uint16)((((x) & 0x00FFU) << 8) | \ (((x) & 0xFF00U) >> 8))) # define swap_sint16(x) ((sint16)((((x) & 0x00FF) << 8) | \ (((x) & 0xFF00) >> 8))) /* swap 32 bits integers */ # define swap_uint32(x) ((uint32)((((x) & 0x000000FFU) << 24) | \ (((x) & 0x0000FF00U) << 8) | \ (((x) & 0x00FF0000U) >> 8) | \ (((x) & 0xFF000000U) >> 24))) /* swap 64 bits doubles */ number64 swap_number64(number64); #endif /* WORDS_BIGENDIAN */ /* convert big endian 24 bits integers to native integers */ # define uint24_be_to_uint32(x) ((uint32)(((x).b[0] << 16) | \ ((x).b[1] << 8) | (x).b[2])) /* convert native integers into 24 bits big endian integers */ uint24_be uint32_to_uint24_be(uint32); /* portable printf format prefixes */ #ifdef WIN32 # define PRI_BYTE "h" # define PRI_LL "I64" # define PRI_L "I32" #else # define PRI_BYTE "hh" # define PRI_LL "ll" # define PRI_L "l" #endif /* large file support */ #ifdef HAVE_FSEEKO # define lfs_ftell ftello # define lfs_fseek fseeko typedef off_t file_offset_t; /* file offset printf specifier */ # if SIZEOF_OFF_T == SIZEOF_LONG_LONG # define FILE_OFFSET_PRINTF_FORMAT PRI_LL # define FILE_OFFSET_PRINTF_TYPE(v) ((long long)(v)) # elif SIZEOF_OFF_T == SIZEOF_LONG # define FILE_OFFSET_PRINTF_FORMAT PRI_L # define FILE_OFFSET_PRINTF_TYPE(v) ((long)(v)) # else # error("unknown off_t variant") # endif #else /* !HAVE_SEEKO */ # ifdef WIN32 typedef long long int file_offset_t; /* Win32 large file support */ file_offset_t lfs_ftell(FILE * stream); int lfs_fseek(FILE * stream, file_offset_t offset, int whence); # define FILE_OFFSET_PRINTF_FORMAT "I64" # define FILE_OFFSET_PRINTF_TYPE(v) ((long long)(v)) # else /* !defined WIN32 */ # define lfs_ftell ftell # define lfs_fseek fseek typedef long file_offset_t; # define FILE_OFFSET_PRINTF_FORMAT "l" # define FILE_OFFSET_PRINTF_TYPE(v) ((long)(v)) # endif /* WIN32 */ #endif /* HAVE_FSEEKO */ #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __TYPES_H__ */ flvmeta-1.2.2/src/update.c000066400000000000000000000306071346231570700154050ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "flvmeta.h" #include "flv.h" #include "amf.h" #include "dump.h" #include "info.h" #include "update.h" #include "util.h" #include #include #include #define COPY_BUFFER_SIZE 4096 /* Write the flv output file */ static int write_flv(flv_stream * flv_in, FILE * flv_out, const flv_info * info, const flv_metadata * meta, const flvmeta_opts * opts) { uint32_be size; uint32 on_metadata_name_size; uint32 on_metadata_size; uint32 prev_timestamp_video; uint32 prev_timestamp_audio; uint32 prev_timestamp_meta; uint8 timestamp_extended_video; uint8 timestamp_extended_audio; uint8 timestamp_extended_meta; byte * copy_buffer; flv_tag ft, omft; int have_on_last_second; if (opts->verbose) { fprintf(stdout, "Writing %s...\n", opts->output_file); } /* write the flv header */ if (flv_write_header(flv_out, &info->header) != 1) { return ERROR_WRITE; } /* first "previous tag size" */ size = swap_uint32(0); if (fwrite(&size, sizeof(uint32_be), 1, flv_out) != 1) { return ERROR_WRITE; } /* create the onMetaData tag */ on_metadata_name_size = (uint32)amf_data_size(meta->on_metadata_name); on_metadata_size = (uint32)amf_data_size(meta->on_metadata); omft.type = FLV_TAG_TYPE_META; omft.body_length = uint32_to_uint24_be(on_metadata_name_size + on_metadata_size); flv_tag_set_timestamp(&omft, 0); omft.stream_id = uint32_to_uint24_be(0); /* write the computed onMetaData tag first if it doesn't exist in the input file */ if (info->on_metadata_size == 0) { if (flv_write_tag(flv_out, &omft) != 1 || amf_data_file_write(meta->on_metadata_name, flv_out) < on_metadata_name_size || amf_data_file_write(meta->on_metadata, flv_out) < on_metadata_size) { return ERROR_WRITE; } /* previous tag size */ size = swap_uint32(FLV_TAG_SIZE + on_metadata_name_size + on_metadata_size); if (fwrite(&size, sizeof(uint32_be), 1, flv_out) != 1) { return ERROR_WRITE; } } /* extended timestamp initialization */ prev_timestamp_video = 0; prev_timestamp_audio = 0; prev_timestamp_meta = 0; timestamp_extended_video = 0; timestamp_extended_audio = 0; timestamp_extended_meta = 0; /* copy the tags verbatim */ flv_reset(flv_in); copy_buffer = (byte *)malloc(info->biggest_tag_body_size + FLV_TAG_SIZE); have_on_last_second = 0; while (flv_read_tag(flv_in, &ft) == FLV_OK) { file_offset_t offset; uint32 body_length; uint32 timestamp; offset = flv_get_current_tag_offset(flv_in); body_length = flv_tag_get_body_length(ft); timestamp = flv_tag_get_timestamp(ft); /* extended timestamp fixing */ if (ft.type == FLV_TAG_TYPE_META) { if (timestamp < prev_timestamp_meta && prev_timestamp_meta - timestamp > 0xF00000) { ++timestamp_extended_meta; } prev_timestamp_meta = timestamp; if (timestamp_extended_meta > 0) { timestamp += timestamp_extended_meta << 24; } } else if (ft.type == FLV_TAG_TYPE_AUDIO) { if (timestamp < prev_timestamp_audio && prev_timestamp_audio - timestamp > 0xF00000) { ++timestamp_extended_audio; } prev_timestamp_audio = timestamp; if (timestamp_extended_audio > 0) { timestamp += timestamp_extended_audio << 24; } } else if (ft.type == FLV_TAG_TYPE_VIDEO) { if (timestamp < prev_timestamp_video && prev_timestamp_video - timestamp > 0xF00000) { ++timestamp_extended_video; } prev_timestamp_video = timestamp; if (timestamp_extended_video > 0) { timestamp += timestamp_extended_video << 24; } } /* non-zero starting timestamp handling */ if (opts->reset_timestamps && timestamp > 0) { timestamp -= info->first_timestamp; } flv_tag_set_timestamp(&ft, timestamp); /* if we're at the offset of the first onMetaData tag in the input file, we write the one we computed instead, discarding the old one */ if (info->on_metadata_offset == offset) { if (flv_write_tag(flv_out, &omft) != 1 || amf_data_file_write(meta->on_metadata_name, flv_out) < on_metadata_name_size || amf_data_file_write(meta->on_metadata, flv_out) < on_metadata_size) { free(copy_buffer); return ERROR_WRITE; } /* previous tag size */ size = swap_uint32(FLV_TAG_SIZE + on_metadata_name_size + on_metadata_size); if (fwrite(&size, sizeof(uint32_be), 1, flv_out) != 1) { free(copy_buffer); return ERROR_WRITE; } } else { size_t read_body; /* insert an onLastSecond metadata tag */ if (opts->insert_onlastsecond && !have_on_last_second && !info->have_on_last_second && (info->last_timestamp - timestamp) <= 1000) { flv_tag tag; uint32 on_last_second_name_size = (uint32)amf_data_size(meta->on_last_second_name); uint32 on_last_second_size = (uint32)amf_data_size(meta->on_last_second); tag.type = FLV_TAG_TYPE_META; tag.body_length = uint32_to_uint24_be(on_last_second_name_size + on_last_second_size); tag.timestamp = ft.timestamp; tag.timestamp_extended = ft.timestamp_extended; tag.stream_id = uint32_to_uint24_be(0); if (flv_write_tag(flv_out, &tag) != 1 || amf_data_file_write(meta->on_last_second_name, flv_out) < on_last_second_name_size || amf_data_file_write(meta->on_last_second, flv_out) < on_last_second_size) { free(copy_buffer); return ERROR_WRITE; } /* previous tag size */ size = swap_uint32(FLV_TAG_SIZE + on_last_second_name_size + on_last_second_size); if (fwrite(&size, sizeof(uint32_be), 1, flv_out) != 1) { free(copy_buffer); return ERROR_WRITE; } have_on_last_second = 1; } /* if the tag is bigger than expected, it means that it's an unknown tag type. In this case, we only copy as much data as the copy buffer can contain */ if (body_length > info->biggest_tag_body_size) { body_length = info->biggest_tag_body_size; } /* copy the tag verbatim */ read_body = flv_read_tag_body(flv_in, copy_buffer, body_length); if (read_body < body_length) { /* we have reached end of file on an incomplete tag */ if (opts->error_handling == FLVMETA_EXIT_ON_ERROR) { free(copy_buffer); return ERROR_EOF; } else if (opts->error_handling == FLVMETA_FIX_ERRORS) { /* the tag is bogus, just omit it, even though it will make the whole file length calculation wrong, and the metadata inaccurate */ /* TODO : fix it by handling that problem in the first pass */ free(copy_buffer); return OK; } else if (opts->error_handling == FLVMETA_IGNORE_ERRORS) { /* just copy the whole tag and exit */ flv_write_tag(flv_out, &ft); fwrite(copy_buffer, 1, read_body, flv_out); free(copy_buffer); size = swap_uint32(FLV_TAG_SIZE + read_body); fwrite(&size, sizeof(uint32_be), 1, flv_out); return OK; } } if (flv_write_tag(flv_out, &ft) != 1 || fwrite(copy_buffer, 1, body_length, flv_out) < body_length) { free(copy_buffer); return ERROR_WRITE; } /* previous tag length */ size = swap_uint32(FLV_TAG_SIZE + body_length); if (fwrite(&size, sizeof(uint32_be), 1, flv_out) != 1) { free(copy_buffer); return ERROR_WRITE; } } } if (opts->verbose) { fprintf(stdout, "%s successfully written\n", opts->output_file); } free(copy_buffer); return OK; } /* copy a FLV file while adding onMetaData and optionnally onLastSecond events */ int update_metadata(const flvmeta_opts * opts) { int res, in_place_update; flv_stream * flv_in; FILE * flv_out; flv_info info; flv_metadata meta; flv_in = flv_open(opts->input_file); if (flv_in == NULL) { return ERROR_OPEN_READ; } /* get all necessary information from the flv file */ res = get_flv_info(flv_in, &info, opts); if (res != OK) { flv_close(flv_in); amf_data_free(info.keyframes); return res; } compute_metadata(&info, &meta, opts); /* open output file */ /* detect whether we have to overwrite the input file */ if (flvmeta_same_file(opts->input_file, opts->output_file)) { in_place_update = 1; flv_out = flvmeta_tmpfile(); } else { in_place_update = 0; flv_out = fopen(opts->output_file, "wb"); } if (flv_out == NULL) { flv_close(flv_in); amf_data_free(meta.on_last_second_name); amf_data_free(meta.on_last_second); amf_data_free(meta.on_metadata_name); amf_data_free(meta.on_metadata); amf_data_free(info.original_on_metadata); return ERROR_OPEN_WRITE; } /* write the output file */ res = write_flv(flv_in, flv_out, &info, &meta, opts); flv_close(flv_in); amf_data_free(meta.on_last_second_name); amf_data_free(meta.on_last_second); amf_data_free(meta.on_metadata_name); amf_data_free(info.original_on_metadata); /* copy data into the original file if needed */ if (in_place_update == 1) { FILE * flv_out_real; size_t bytes_read; byte copy_buffer[COPY_BUFFER_SIZE]; flv_out_real = fopen(opts->output_file, "wb"); if (flv_out_real == NULL) { amf_data_free(meta.on_metadata); return ERROR_OPEN_WRITE; } /* copy temporary file contents into the final file */ lfs_fseek(flv_out, 0, SEEK_SET); while (!feof(flv_out)) { bytes_read = fread(copy_buffer, sizeof(byte), COPY_BUFFER_SIZE, flv_out); if (bytes_read > 0) { if (fwrite(copy_buffer, sizeof(byte), bytes_read, flv_out_real) < bytes_read) { fclose(flv_out_real); fclose(flv_out); amf_data_free(meta.on_metadata); return ERROR_WRITE; } } else { fclose(flv_out_real); fclose(flv_out); amf_data_free(meta.on_metadata); return ERROR_WRITE; } } fclose(flv_out_real); } fclose(flv_out); /* dump computed metadata if we have to */ if (opts->dump_metadata == 1) { dump_amf_data(meta.on_metadata, opts); } amf_data_free(meta.on_metadata); return res; } flvmeta-1.2.2/src/update.h000066400000000000000000000022101346231570700153770ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __UPDATE_H__ #define __UPDATE_H__ #include "flvmeta.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* inject metadata from a FLV file into a new one */ extern int update_metadata(const flvmeta_opts * options); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __UPDATE_H__ */ flvmeta-1.2.2/src/util.c000066400000000000000000000076531346231570700151050ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef WIN32 # define WIN32_LEAN_AND_MEAN # include # include #else /* !WIN32 */ # include # include # include #endif /* WIN32 */ #include "util.h" int flvmeta_same_file(const char * file1, const char * file2) { #ifdef WIN32 /* in Windows, we have to open the files and use GetFileInformationByHandle */ HANDLE h1, h2; BY_HANDLE_FILE_INFORMATION info1, info2; h1 = CreateFile(file1, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (h1 == INVALID_HANDLE_VALUE) { return 0; } GetFileInformationByHandle(h1, &info1); CloseHandle(h1); h2 = CreateFile(file2, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (h2 == INVALID_HANDLE_VALUE) { return 0; } GetFileInformationByHandle(h2, &info2); CloseHandle(h2); return (info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber && info1.nFileIndexHigh == info2.nFileIndexHigh && info1.nFileIndexLow == info2.nFileIndexLow); #else /* !WIN32 */ /* if not in Windows, we must stat each file and compare device and inode numbers */ struct stat s1, s2; if (stat(file1, &s1) != 0) { return 0; } if (stat(file2, &s2) != 0) { return 0; } return (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino); #endif /* WIN32 */ } #ifdef WIN32 FILE * flvmeta_tmpfile(void) { DWORD path_len; TCHAR path_name[MAX_PATH + 1]; TCHAR file_name[MAX_PATH + 1]; HANDLE handle; int fd; FILE *fp; path_len = GetTempPath(MAX_PATH, path_name); if (path_len <= 0 || path_len >= MAX_PATH) { return NULL; } if (GetTempFileName(path_name, TEXT("flv"), 0, file_name) == 0) { return NULL; } handle = CreateFile(file_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL ); if (handle == INVALID_HANDLE_VALUE) { return NULL; } fd = _open_osfhandle((intptr_t)handle, 0); if (fd == -1) { CloseHandle(handle); return NULL; } fp = _fdopen(fd, "w+b"); if (fp == NULL) { _close(fd); return NULL; } return fp; } #endif /* WIN32 */ int flvmeta_filesize(const char *filename, file_offset_t *filesize) { #ifdef WIN32 BOOL result; WIN32_FILE_ATTRIBUTE_DATA fad; result = GetFileAttributesEx(filename, GetFileExInfoStandard, &fad); if (result) { *filesize = ((file_offset_t)fad.nFileSizeHigh << 32) + fad.nFileSizeLow; return 1; } else { return 0; } #else /* !WIN32 */ struct stat fs; int result; result = stat(filename, &fs); if (result == 0) { *filesize = fs.st_size; return 1; } else { return 0; } #endif /* WIN32 */ } #ifndef HAVE_ISFINITE int flvmeta_isfinite(double d) { /* * This trick is mandated by the IEEE 754 standard. * Can be broken by specific optimizations such as * --ffast-math, but we don't use any. */ return !(d != d); } #endif /* HAVE_ISFINITE */ flvmeta-1.2.2/src/util.h000066400000000000000000000033571346231570700151070ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2019 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __UTIL_H__ #define __UTIL_H__ #include #include "types.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* determine whether two paths physically point to the same file */ int flvmeta_same_file(const char * file1, const char * file2); #ifdef WIN32 /* Returns a descriptor to a temporary file. This function is meant as a Windows replacement to the broken standard tmpfile() function. */ FILE * flvmeta_tmpfile(void); #else /* WIN32 */ # define flvmeta_tmpfile tmpfile #endif /* WIN32 */ /* File size (LFS compatible). Returns a non-zero value if successful, zero otherwise. */ int flvmeta_filesize(const char * filename, file_offset_t * filesize); #ifndef HAVE_ISFINITE /* Check whether a double is finite (not infinity or NaN) */ int flvmeta_isfinite(double d); #endif /* HAVE_ISFINITE */ #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __UTIL_H__ */ flvmeta-1.2.2/tests/000077500000000000000000000000001346231570700143245ustar00rootroot00000000000000flvmeta-1.2.2/tests/CMakeLists.txt000066400000000000000000000006571346231570700170740ustar00rootroot00000000000000# unit tests include_directories(BEFORE ${CHECK_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}) link_directories(${CHECK_LIBRARY_DIRS}) add_executable(check_flvmeta check_flvmeta.c check_flv.c check_amf.c ${CMAKE_SOURCE_DIR}/src/amf.c ${CMAKE_SOURCE_DIR}/src/flv.c ${CMAKE_SOURCE_DIR}/src/types.c ) target_link_libraries(check_flvmeta ${CHECK_LIBRARIES} pthread) add_test(check_flvmeta ${CMAKE_CURRENT_BINARY_DIR}/check_flvmeta) flvmeta-1.2.2/tests/check_amf.c000066400000000000000000000116051346231570700163730ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2016 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "src/amf.h" amf_data * data; void teardown(void) { amf_data_free(data); } /** AMF number */ void setup_amf_number(void) { data = amf_number_new(0); } START_TEST(test_amf_number_new) { ck_assert_ptr_nonnull(data); ck_assert_int_eq(amf_data_get_type(data), AMF_TYPE_NUMBER); /* AMF number size == 1(header) + 8(data) -> 9 bytes */ ck_assert_int_eq(amf_data_size(data), 9); ck_assert_int_eq(amf_number_get_value(data), 0); } END_TEST START_TEST(test_amf_number_set_value) { amf_number_set_value(data, -512.78); ck_assert_double_eq(amf_number_get_value(data), -512.78); } END_TEST START_TEST(test_amf_number_null) { ck_assert_int_eq(amf_number_get_value(NULL), 0); /* just making sure we don't core dump */ amf_number_set_value(NULL, 12); } END_TEST /** AMF boolean */ void setup_amf_boolean(void) { data = amf_boolean_new(1); } START_TEST(test_amf_boolean_new) { ck_assert_ptr_nonnull(data); ck_assert_int_eq(amf_data_get_type(data), AMF_TYPE_BOOLEAN); /* AMF boolean size == 1(header) + 1(data) -> 2 bytes */ ck_assert_int_eq(amf_data_size(data), 2); ck_assert_int_eq(amf_boolean_get_value(data), 1); } END_TEST START_TEST(test_amf_boolean_set_value) { amf_boolean_set_value(data, 0); ck_assert_int_eq(amf_boolean_get_value(data), 0); } END_TEST START_TEST(test_amf_boolean_null) { ck_assert_int_eq(amf_boolean_get_value(NULL), 0); /* just making sure we don't core dump */ amf_boolean_set_value(NULL, 12); } END_TEST /** AMF string */ START_TEST(test_amf_str) { char * str = "hello world"; int length = strlen(str); data = amf_str(str); ck_assert_ptr_nonnull(data); ck_assert_int_eq(amf_data_get_type(data), AMF_TYPE_STRING); /* AMF string size == 1(header) + 2(string length) + length */ ck_assert_int_eq(amf_data_size(data), 3 + length); ck_assert_int_eq(amf_string_get_size(data), length); ck_assert_str_eq(amf_string_get_bytes(data), str); } END_TEST START_TEST(test_amf_str_null) { data = amf_str(NULL); ck_assert_int_eq(amf_string_get_size(data), 0); ck_assert_str_eq(amf_string_get_bytes(data), ""); amf_data_free(data); } END_TEST START_TEST(test_amf_string_new) { char * str = "hello world"; data = amf_string_new(str, 5); ck_assert_int_eq(amf_string_get_size(data), 5); ck_assert_str_eq(amf_string_get_bytes(data), "hello"); amf_data_free(data); } END_TEST START_TEST(test_amf_string_new_null) { data = amf_string_new(NULL, 12); ck_assert_ptr_nonnull(data); ck_assert_int_eq(amf_string_get_size(data), 0); ck_assert_str_eq(amf_string_get_bytes(data), ""); amf_data_free(data); } END_TEST START_TEST(test_amf_string_null) { ck_assert_int_eq(amf_string_get_size(NULL), 0); ck_assert_ptr_null(amf_string_get_bytes(NULL)); } END_TEST /** AMF Types Suite */ Suite * amf_types_suite(void) { Suite * s = suite_create("AMF types"); /* AMF number test case */ TCase * tc_number = tcase_create("AMF number"); tcase_add_checked_fixture(tc_number, setup_amf_number, teardown); tcase_add_test(tc_number, test_amf_number_new); tcase_add_test(tc_number, test_amf_number_set_value); tcase_add_test(tc_number, test_amf_number_null); suite_add_tcase(s, tc_number); /* AMF boolean test case */ TCase * tc_boolean = tcase_create("AMF boolean"); tcase_add_checked_fixture(tc_boolean, setup_amf_boolean, teardown); tcase_add_test(tc_boolean, test_amf_boolean_new); tcase_add_test(tc_boolean, test_amf_boolean_set_value); tcase_add_test(tc_boolean, test_amf_boolean_null); suite_add_tcase(s, tc_boolean); /* AMF string test case */ TCase * tc_string = tcase_create("AMF string"); tcase_add_test(tc_string, test_amf_str); tcase_add_test(tc_string, test_amf_str_null); tcase_add_test(tc_string, test_amf_string_new); tcase_add_test(tc_string, test_amf_string_new_null); tcase_add_test(tc_string, test_amf_string_null); suite_add_tcase(s, tc_string); return s; } flvmeta-1.2.2/tests/check_flv.c000066400000000000000000000134571346231570700164260ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2016 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "src/flv.h" /** FLV types */ START_TEST(test_swap_uint16) { uint16 ile; uint16_be ibe; ile = 0x1122U; ibe = swap_uint16(ile); #ifndef WORDS_BIGENDIAN ck_assert_int_eq(ibe, 0x2211U); #endif ck_assert_int_eq(swap_uint16(ibe), ile); } END_TEST START_TEST(test_swap_uint16_neg) { uint16 ile; uint16_be ibe; ile = 0xFFFEU; ibe = swap_uint16(ile); #ifndef WORDS_BIGENDIAN ck_assert_int_eq(ibe, 0xFEFFU); #endif ck_assert_int_eq(swap_uint16(ibe), ile); } END_TEST START_TEST(test_swap_sint16) { sint16 ile; sint16_be ibe; ile = 0x1122; ibe = swap_sint16(ile); #ifndef WORDS_BIGENDIAN ck_assert_int_eq(ibe, 0x2211); #endif ck_assert_int_eq(swap_sint16(ibe), ile); } END_TEST START_TEST(test_swap_sint16_neg) { sint16 ile; sint16_be ibe; ile = 0xFF00; ibe = swap_sint16(ile); #ifndef WORDS_BIGENDIAN ck_assert_int_eq(ibe, (0x00FF)); #endif ck_assert_int_eq(swap_sint16(ibe), ile); } END_TEST START_TEST(test_swap_uint32) { uint32 ile; uint32_be ibe; ile = 0x11223344U; ibe = swap_uint32(ile); #ifndef WORDS_BIGENDIAN ck_assert_int_eq(ibe, 0x44332211U); #endif ck_assert_int_eq(swap_uint32(ibe), ile); } END_TEST START_TEST(test_swap_uint32_neg) { uint32 ile; uint32_be ibe; ile = 0xFFFEFDFCU; ibe = swap_uint32(ile); #ifndef WORDS_BIGENDIAN ck_assert_int_eq(ibe, 0xFCFDFEFFU); #endif ck_assert_int_eq(swap_uint32(ibe), ile); } END_TEST START_TEST(test_swap_number64) { number64 ile; number64_be ibe; ile = 3.14159; ibe = swap_number64(ile); ck_assert_double_eq(swap_number64(ibe), ile); } END_TEST START_TEST(test_swap_number64_neg) { number64 ile; number64_be ibe; ile = -3.14159; ibe = swap_number64(ile); ck_assert_double_eq(swap_number64(ibe), ile); } END_TEST START_TEST(test_uint24_be_to_uint32) { uint24_be ile; uint32 ibe; ile.b[2] = 0x33; ile.b[1] = 0x22; ile.b[0] = 0x11; ibe = uint24_be_to_uint32(ile); ck_assert_int_eq(ibe, 0x00112233); } END_TEST START_TEST(test_uint32_to_uint24_be) { uint32 ile; uint24_be ibe; ile = 0x00112233; ibe = uint32_to_uint24_be(ile); ck_assert_int_eq(ibe.b[0], 0x11); ck_assert_int_eq(ibe.b[1], 0x22); ck_assert_int_eq(ibe.b[2], 0x33); } END_TEST START_TEST(test_uint32_to_uint24_be_truncate) { uint32 ile; uint24_be ibe; ile = 0x11223344; ibe = uint32_to_uint24_be(ile); ck_assert_int_eq(ibe.b[0], 0x22); ck_assert_int_eq(ibe.b[1], 0x33); ck_assert_int_eq(ibe.b[2], 0x44); } END_TEST /** FLV tags */ START_TEST(test_flv_tag_get_timestamp_short) { flv_tag tag; uint32 val; tag.timestamp = uint32_to_uint24_be(0x00332211); tag.timestamp_extended = 0x00; val = flv_tag_get_timestamp(tag); ck_assert_int_eq(val, 0x00332211); } END_TEST START_TEST(test_flv_tag_get_timestamp_extended) { flv_tag tag; uint32 val; tag.timestamp = uint32_to_uint24_be(0x00332211); tag.timestamp_extended = 0x44; val = flv_tag_get_timestamp(tag); ck_assert_int_eq(val, 0x44332211); } END_TEST START_TEST(test_flv_tag_set_timestamp_short) { flv_tag tag; uint32 val; flv_tag_set_timestamp(&tag, 0x00112233); val = uint24_be_to_uint32(tag.timestamp); ck_assert_int_eq(val, 0x00112233); ck_assert_int_eq(tag.timestamp_extended, 0x00); } END_TEST START_TEST(test_flv_tag_set_timestamp_extended) { flv_tag tag; uint32 val; flv_tag_set_timestamp(&tag, 0x44332211); val = uint24_be_to_uint32(tag.timestamp); ck_assert_int_eq(val, 0x00332211); ck_assert_int_eq(tag.timestamp_extended, 0x44); } END_TEST /** FLV Suite */ Suite * flv_suite(void) { Suite * s = suite_create("FLV Format"); /* FLV data types tests */ TCase * tc_flv_types = tcase_create("FLV types"); tcase_add_test(tc_flv_types, test_swap_uint16); tcase_add_test(tc_flv_types, test_swap_uint16_neg); tcase_add_test(tc_flv_types, test_swap_sint16); tcase_add_test(tc_flv_types, test_swap_sint16_neg); tcase_add_test(tc_flv_types, test_swap_uint32); tcase_add_test(tc_flv_types, test_swap_uint32_neg); tcase_add_test(tc_flv_types, test_swap_number64); tcase_add_test(tc_flv_types, test_swap_number64_neg); tcase_add_test(tc_flv_types, test_uint24_be_to_uint32); tcase_add_test(tc_flv_types, test_uint32_to_uint24_be); tcase_add_test(tc_flv_types, test_uint32_to_uint24_be_truncate); suite_add_tcase(s, tc_flv_types); /* FLV tag tests */ TCase * tc_flv_tag = tcase_create("FLV tags"); tcase_add_test(tc_flv_tag, test_flv_tag_get_timestamp_short); tcase_add_test(tc_flv_tag, test_flv_tag_get_timestamp_extended); tcase_add_test(tc_flv_tag, test_flv_tag_set_timestamp_short); tcase_add_test(tc_flv_tag, test_flv_tag_set_timestamp_extended); suite_add_tcase(s, tc_flv_tag); return s; } flvmeta-1.2.2/tests/check_flvmeta.c000066400000000000000000000024531346231570700172670ustar00rootroot00000000000000/* FLVMeta - FLV Metadata Editor Copyright (C) 2007-2016 Marc Noirot This file is part of FLVMeta. FLVMeta is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. FLVMeta is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FLVMeta; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include extern Suite * amf_types_suite(void); extern Suite * flv_suite(void); int main(void) { int number_failed; SRunner * sr = srunner_create(amf_types_suite()); srunner_add_suite(sr, flv_suite()); /* srunner_set_log (sr, "check_amf.log"); */ srunner_run_all(sr, CK_NORMAL); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; }