├── TODO ├── scanner ├── scanner-common │ ├── nproc.h │ ├── nproc.c │ ├── parse_args.h │ ├── CMakeLists.txt │ ├── scanner-common.h │ ├── parse_args.c │ └── scanner-common.c ├── scanner-dump.h ├── scanner-scan.h ├── scanner-tag │ ├── CMakeLists.txt │ ├── scanner-tag.h │ ├── rgtag.h │ ├── scanner-tag.c │ └── rgtag.cpp ├── inputaudio │ ├── CMakeLists.txt │ ├── input_helper.c │ ├── input.h │ ├── sndfile │ │ ├── CMakeLists.txt │ │ └── input_sndfile.c │ ├── ffmpeg │ │ ├── CMakeLists.txt │ │ └── input_ffmpeg.c │ └── input.c ├── scanner-drop-qt │ ├── CMakeLists.txt │ ├── scanner-drop-qt.h │ └── scanner-drop-qt.cpp ├── scanner-drop-gtk │ ├── CMakeLists.txt │ └── scanner-drop.c ├── CMakeLists.txt ├── scanner-scan.c ├── scanner-dump.c ├── scanner.c └── logo.h ├── .gitmodules ├── cmake ├── wine.cmake ├── wine32.cmake ├── FindTAGLIB.cmake ├── FindSNDFILE.cmake ├── lsb-4-0.cmake ├── i486-mingw32.cmake ├── i586-mingw32msvc.cmake ├── amd64-mingw32msvc-gcc.cmake ├── i686-w64-mingw32.cmake ├── x86_64-w64-mingw32.cmake ├── FindAVFORMAT.cmake ├── utils.cmake └── LibFindMacros.cmake ├── COPYING ├── doc └── license │ ├── R128Scan.txt │ ├── queue.txt │ ├── speex.txt │ └── ffmpeg.txt ├── .clang-tidy ├── README.md ├── CMakeLists.txt └── .clang-format /TODO: -------------------------------------------------------------------------------- 1 | DONE - search PATH for plugins 2 | DONE - GUI 3 | - build Debian packages 4 | DONE - remove fprintf calls in library 5 | DONE - error messages when tagging 6 | -------------------------------------------------------------------------------- /scanner/scanner-common/nproc.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #ifndef NPROC_H 4 | #define NPROC_H 5 | 6 | int nproc(void); 7 | 8 | #endif /* end of include guard: NPROC_H */ 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "scanner/filetree"] 2 | path = scanner/filetree 3 | url = https://github.com/jiixyj/filewalk.git 4 | [submodule "ebur128"] 5 | path = ebur128 6 | url = https://github.com/jiixyj/libebur128.git 7 | -------------------------------------------------------------------------------- /scanner/scanner-common/nproc.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include "nproc.h" 4 | 5 | #include 6 | 7 | int 8 | nproc(void) 9 | { 10 | return (int)g_get_num_processors(); 11 | } 12 | -------------------------------------------------------------------------------- /cmake/wine.cmake: -------------------------------------------------------------------------------- 1 | # Based on http://www.cmake.org/Wiki/CmakeMingw 2 | 3 | # the name of the target operating system 4 | SET(CMAKE_SYSTEM_NAME Windows) 5 | 6 | # which compilers to use for C and C++ 7 | SET(CMAKE_C_COMPILER wine-gcc) 8 | SET(CMAKE_CXX_COMPILER wine-g++) 9 | -------------------------------------------------------------------------------- /cmake/wine32.cmake: -------------------------------------------------------------------------------- 1 | # Based on http://www.cmake.org/Wiki/CmakeMingw 2 | 3 | # the name of the target operating system 4 | SET(CMAKE_SYSTEM_NAME Windows) 5 | 6 | # which compilers to use for C and C++ 7 | SET(CMAKE_C_COMPILER wine32-gcc) 8 | SET(CMAKE_CXX_COMPILER wine32-g++) 9 | -------------------------------------------------------------------------------- /scanner/scanner-dump.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #ifndef SCANNER_DUMP_H 4 | #define SCANNER_DUMP_H 5 | 6 | #include 7 | 8 | int loudness_dump(GSList *files); 9 | gboolean loudness_dump_parse(int *argc, char **argv[]); 10 | 11 | #endif /* end of include guard: SCANNER_DUMP_H */ 12 | -------------------------------------------------------------------------------- /scanner/scanner-scan.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #ifndef SCANNER_SCAN_H 4 | #define SCANNER_SCAN_H 5 | 6 | #include 7 | 8 | void loudness_scan(GSList *files); 9 | gboolean loudness_scan_parse(int *argc, char **argv[]); 10 | 11 | #endif /* end of include guard: SCANNER_SCAN_H */ 12 | -------------------------------------------------------------------------------- /scanner/scanner-common/parse_args.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #ifndef PARSE_GLOBAL_ARGS_H 4 | #define PARSE_GLOBAL_ARGS_H 5 | 6 | #include 7 | 8 | int parse_global_args(int *argc, char ***argv, GOptionEntry *entries, 9 | gboolean ignore_unknown); 10 | gboolean parse_mode_args(int *argc, char **argv[], GOptionEntry *entries); 11 | 12 | #endif /* end of include guard: PARSE_GLOBAL_ARGS_H */ 13 | -------------------------------------------------------------------------------- /scanner/scanner-tag/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(TAGLIB) 2 | 3 | set(SUMMARY_TAGLIB_FOUND 4 | ${TAGLIB_FOUND} 5 | CACHE INTERNAL "") 6 | 7 | if(TAGLIB_FOUND AND NOT DISABLE_TAGLIB) 8 | include_directories(SYSTEM ${GLIB20_INCLUDE_DIRS}) 9 | add_definitions(${GLIB20_CFLAGS_OTHER}) 10 | 11 | add_library(scanner-tag scanner-tag.c rgtag.cpp) 12 | include_directories(${TAGLIB_INCLUDE_DIRS}) 13 | target_link_libraries(scanner-tag scanner-common ${TAGLIB_LIBRARIES}) 14 | endif() 15 | -------------------------------------------------------------------------------- /scanner/scanner-common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(SYSTEM ${GLIB20_INCLUDE_DIRS}) 2 | add_definitions(${GLIB20_CFLAGS_OTHER}) 3 | 4 | add_library(scanner-common parse_args.c nproc.c scanner-common.c) 5 | target_link_libraries(scanner-common ebur128 # 6 | ${GLIB20_LIBRARIES} ${GTHREAD20_LIBRARIES}) 7 | 8 | if(SNDFILE_FOUND AND NOT DISABLE_SNDFILE) 9 | include_directories(${SNDFILE_INCLUDE_DIRS}) 10 | add_definitions(-DUSE_SNDFILE) 11 | target_link_libraries(scanner-common ${SNDFILE_LIBRARIES}) 12 | endif() 13 | -------------------------------------------------------------------------------- /scanner/scanner-tag/scanner-tag.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #ifndef SCANNER_TAG_H 4 | #define SCANNER_TAG_H 5 | 6 | #include 7 | 8 | #include "filetree.h" 9 | 10 | #define RG_REFERENCE_LEVEL -18.0 11 | double clamp_rg(double x); 12 | 13 | void tag_file(struct filename_list_node *fln, int *ret); 14 | int scan_files(GSList *files); 15 | int tag_files(GSList *files); 16 | int loudness_tag(GSList *files); 17 | gboolean loudness_tag_parse(int *argc, char **argv[]); 18 | 19 | #endif /* end of include guard: SCANNER_TAG_H */ 20 | -------------------------------------------------------------------------------- /scanner/inputaudio/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig) 2 | if(PKG_CONFIG_FOUND) 3 | pkg_check_modules(GMODULE20 gmodule-2.0) 4 | endif() 5 | 6 | if(GMODULE20_FOUND AND NOT DISABLE_GLIB20) 7 | set(INPUT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) 8 | 9 | add_subdirectory(sndfile) 10 | add_subdirectory(ffmpeg) 11 | 12 | include_directories(SYSTEM ${GMODULE20_INCLUDE_DIRS}) 13 | link_directories(${GMODULE20_LIBRARY_DIRS}) 14 | add_definitions(${GMODULE20_CFLAGS_OTHER}) 15 | 16 | add_library(input input.c) 17 | 18 | target_link_libraries(input ${GMODULE20_LIBRARIES}) 19 | endif() 20 | -------------------------------------------------------------------------------- /cmake/FindTAGLIB.cmake: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig) 2 | pkg_check_modules(PC_TAGLIB QUIET taglib) 3 | 4 | find_path(TAGLIB_INCLUDE_DIR taglib.h PATH_SUFFIXES taglib 5 | HINTS ${PC_TAGLIB_INCLUDEDIR} ${PC_TAGLIB_INCLUDE_DIRS}) 6 | find_library(TAGLIB_LIBRARY tag 7 | HINTS ${PC_TAGLIB_LIBDIR} ${PC_TAGLIB_LIBRARY_DIRS}) 8 | 9 | set(TAGLIB_LIBRARIES ${TAGLIB_LIBRARY}) 10 | set(TAGLIB_INCLUDE_DIRS ${TAGLIB_INCLUDE_DIR}) 11 | 12 | include(FindPackageHandleStandardArgs) 13 | find_package_handle_standard_args(TAGLIB DEFAULT_MSG TAGLIB_LIBRARY TAGLIB_INCLUDE_DIR) 14 | mark_as_advanced(TAGLIB_INCLUDE_DIR TAGLIB_LIBRARY) 15 | -------------------------------------------------------------------------------- /cmake/FindSNDFILE.cmake: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig) 2 | pkg_check_modules(PC_SNDFILE QUIET sndfile) 3 | 4 | find_path(SNDFILE_INCLUDE_DIR sndfile.h 5 | HINTS ${PC_SNDFILE_INCLUDEDIR} ${PC_SNDFILE_INCLUDE_DIRS}) 6 | find_library(SNDFILE_LIBRARY NAMES sndfile sndfile-1 libsndfile-1 7 | HINTS ${PC_SNDFILE_LIBDIR} ${PC_SNDFILE_LIBRARY_DIRS}) 8 | 9 | set(SNDFILE_LIBRARIES ${SNDFILE_LIBRARY}) 10 | set(SNDFILE_INCLUDE_DIRS ${SNDFILE_INCLUDE_DIR}) 11 | 12 | include(FindPackageHandleStandardArgs) 13 | find_package_handle_standard_args(SNDFILE DEFAULT_MSG SNDFILE_LIBRARY SNDFILE_INCLUDE_DIR) 14 | mark_as_advanced(SNDFILE_INCLUDE_DIR SNDFILE_LIBRARY) 15 | -------------------------------------------------------------------------------- /scanner/scanner-drop-qt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(TARGET scanner-tag) 2 | set(CMAKE_AUTOMOC ON) 3 | 4 | find_package(Qt5 COMPONENTS Gui Svg Widgets) 5 | 6 | set(SUMMARY_QT5_FOUND 7 | ${Qt5_FOUND} 8 | CACHE INTERNAL "") 9 | 10 | if(Qt5_FOUND AND NOT DISABLE_QT5) 11 | add_executable(loudness-drop-qt5 scanner-drop-qt.cpp) 12 | 13 | include_directories(${CMAKE_CURRENT_BINARY_DIR} ${LOGO_INCLUDE_DIR}) 14 | target_link_libraries( 15 | loudness-drop-qt5 16 | scanner-tag 17 | filetree 18 | input 19 | ${GLIB20_LIBRARIES} 20 | ${GTHREAD20_LIBRARIES} 21 | Qt5::Gui 22 | Qt5::Svg 23 | Qt5::Widgets) 24 | endif() 25 | endif() 26 | -------------------------------------------------------------------------------- /cmake/lsb-4-0.cmake: -------------------------------------------------------------------------------- 1 | # Based on http://www.cmake.org/Wiki/CmakeMingw 2 | 3 | # the name of the target operating system 4 | SET(CMAKE_SYSTEM_NAME Linux) 5 | 6 | # which compilers to use for C and C++ 7 | SET(CMAKE_C_COMPILER /opt/lsb/bin/lsbcc) 8 | SET(CMAKE_CXX_COMPILER /opt/lsb/bin/lsbc++) 9 | 10 | # here is the target environment located 11 | SET(CMAKE_FIND_ROOT_PATH /opt/lsb/) 12 | 13 | # adjust the default behaviour of the FIND_XXX() commands: 14 | # search headers and libraries in the target environment, search 15 | # programs in the host environment 16 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 18 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 19 | -------------------------------------------------------------------------------- /cmake/i486-mingw32.cmake: -------------------------------------------------------------------------------- 1 | # Based on http://www.cmake.org/Wiki/CmakeMingw 2 | 3 | # the name of the target operating system 4 | SET(CMAKE_SYSTEM_NAME Windows) 5 | 6 | # which compilers to use for C and C++ 7 | SET(CMAKE_C_COMPILER i486-mingw32-gcc) 8 | SET(CMAKE_CXX_COMPILER i486-mingw32-g++) 9 | 10 | # here is the target environment located 11 | SET(CMAKE_FIND_ROOT_PATH /usr/i486-mingw32) 12 | 13 | # adjust the default behaviour of the FIND_XXX() commands: 14 | # search headers and libraries in the target environment, search 15 | # programs in the host environment 16 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 18 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 19 | -------------------------------------------------------------------------------- /cmake/i586-mingw32msvc.cmake: -------------------------------------------------------------------------------- 1 | # Based on http://www.cmake.org/Wiki/CmakeMingw 2 | 3 | # the name of the target operating system 4 | SET(CMAKE_SYSTEM_NAME Windows) 5 | 6 | # which compilers to use for C and C++ 7 | SET(CMAKE_C_COMPILER i586-mingw32msvc-gcc) 8 | SET(CMAKE_CXX_COMPILER i586-mingw32msvc-g++) 9 | 10 | # here is the target environment located 11 | SET(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc) 12 | 13 | # adjust the default behaviour of the FIND_XXX() commands: 14 | # search headers and libraries in the target environment, search 15 | # programs in the host environment 16 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 18 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 19 | -------------------------------------------------------------------------------- /cmake/amd64-mingw32msvc-gcc.cmake: -------------------------------------------------------------------------------- 1 | # Based on http://www.cmake.org/Wiki/CmakeMingw 2 | 3 | # the name of the target operating system 4 | SET(CMAKE_SYSTEM_NAME Windows) 5 | 6 | # which compilers to use for C and C++ 7 | SET(CMAKE_C_COMPILER amd64-mingw32msvc-gcc) 8 | SET(CMAKE_CXX_COMPILER amd64-mingw32msvc-g++) 9 | 10 | # here is the target environment located 11 | SET(CMAKE_FIND_ROOT_PATH /usr/amd64-mingw32msvc) 12 | 13 | # adjust the default behaviour of the FIND_XXX() commands: 14 | # search headers and libraries in the target environment, search 15 | # programs in the host environment 16 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 18 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 19 | -------------------------------------------------------------------------------- /cmake/i686-w64-mingw32.cmake: -------------------------------------------------------------------------------- 1 | # Based on http://www.cmake.org/Wiki/CmakeMingw 2 | 3 | # the name of the target operating system 4 | set(CMAKE_SYSTEM_NAME Windows) 5 | 6 | # which compilers to use for C and C++ 7 | set(CMAKE_C_COMPILER i686-w64-mingw32-gcc) 8 | set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) 9 | set(CMAKE_RC_COMPILER i686-w64-mingw32-windres) 10 | 11 | # here is the target environment located 12 | if(NOT CMAKE_FIND_ROOT_PATH) 13 | set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32/sys-root/mingw) 14 | endif() 15 | 16 | # adjust the default behaviour of the FIND_XXX() commands: 17 | # search headers and libraries in the target environment, search 18 | # programs in the host environment 19 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 20 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 21 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 22 | -------------------------------------------------------------------------------- /cmake/x86_64-w64-mingw32.cmake: -------------------------------------------------------------------------------- 1 | # Based on http://www.cmake.org/Wiki/CmakeMingw 2 | 3 | # the name of the target operating system 4 | set(CMAKE_SYSTEM_NAME Windows) 5 | 6 | # which compilers to use for C and C++ 7 | set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) 8 | set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) 9 | set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) 10 | 11 | # here is the target environment located 12 | if(NOT CMAKE_FIND_ROOT_PATH) 13 | set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32/sys-root/mingw) 14 | endif() 15 | 16 | # adjust the default behaviour of the FIND_XXX() commands: 17 | # search headers and libraries in the target environment, search 18 | # programs in the host environment 19 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 20 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 21 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 22 | -------------------------------------------------------------------------------- /scanner/inputaudio/input_helper.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include "input.h" 4 | 5 | 6 | #ifndef G_OS_WIN32 7 | #include 8 | #include 9 | #include 10 | #endif 11 | 12 | #include 13 | #include 14 | 15 | int 16 | input_open_fd(char const *filename) 17 | { 18 | #ifdef G_OS_WIN32 19 | gunichar2 *utf16 = g_utf8_to_utf16(filename, -1, NULL, NULL, NULL); 20 | int ret = _wopen(utf16, _O_RDONLY | _O_BINARY); 21 | g_free(utf16); 22 | #else 23 | int ret = open(filename, O_RDONLY); 24 | #endif 25 | return ret; 26 | } 27 | 28 | void 29 | input_close_fd(int fd) 30 | { 31 | #ifdef G_OS_WIN32 32 | _close(fd); 33 | #else 34 | close(fd); 35 | #endif 36 | } 37 | 38 | int 39 | input_read_fd(int fd, void *buf, unsigned int count) 40 | { 41 | #ifdef G_OS_WIN32 42 | return _read(fd, buf, count); 43 | #else 44 | return (int)read(fd, buf, count); 45 | #endif 46 | } 47 | -------------------------------------------------------------------------------- /scanner/scanner-tag/rgtag.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #ifndef RGTAG_H_ 4 | #define RGTAG_H_ 5 | 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | struct gain_data { 13 | double track_gain; 14 | double track_peak; 15 | int album_mode; 16 | double album_gain; 17 | double album_peak; 18 | }; 19 | 20 | typedef enum { 21 | OPUS_GAIN_REFERENCE_ABSOLUTE, 22 | OPUS_GAIN_REFERENCE_R128, 23 | } OpusGainReference; 24 | 25 | typedef struct { 26 | bool vorbisgain_compat; 27 | OpusGainReference opus_gain_reference; 28 | double offset; 29 | bool is_track; 30 | } OpusTagInfo; 31 | 32 | double clamp_rg(double x); 33 | void clamp_gain_data(struct gain_data *gd); 34 | 35 | int set_rg_info(char const *filename, char const *extension, 36 | struct gain_data *gd, OpusTagInfo const *opus_tag_info); 37 | 38 | bool has_rg_info(char const *filename, char const *extension, 39 | OpusTagInfo const *opus_tag_info); 40 | 41 | #ifdef __cplusplus 42 | } 43 | #endif 44 | 45 | #endif /* RGTAG_H_ */ 46 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Jan Kokemüller 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /scanner/scanner-drop-gtk/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(TARGET scanner-tag) 2 | find_package(GTK2 COMPONENTS gtk) 3 | find_package(PkgConfig) 4 | if(PKG_CONFIG_FOUND) 5 | pkg_check_modules(RSVG2 librsvg-2.0) 6 | pkg_check_modules(HARFBUZZ harfbuzz) 7 | endif() 8 | 9 | set(SUMMARY_GTK2_FOUND 10 | ${GTK2_FOUND} 11 | CACHE INTERNAL "") 12 | set(SUMMARY_RSVG2_FOUND 13 | ${RSVG2_FOUND} 14 | CACHE INTERNAL "") 15 | 16 | if(GTK2_FOUND 17 | AND RSVG2_FOUND 18 | AND NOT DISABLE_GTK2 19 | AND NOT DISABLE_RSVG2) 20 | add_definitions(${RSVG2_CFLAGS_OTHER}) 21 | include_directories(SYSTEM ${GTK2_INCLUDE_DIRS} ${RSVG2_INCLUDE_DIRS}) 22 | include_directories(${LOGO_INCLUDE_DIR}) 23 | if(HARFBUZZ_FOUND) 24 | include_directories(SYSTEM ${HARFBUZZ_INCLUDE_DIRS}) 25 | endif() 26 | 27 | add_executable(loudness-drop-gtk scanner-drop.c) 28 | 29 | target_link_libraries( 30 | loudness-drop-gtk 31 | scanner-tag 32 | filetree 33 | input 34 | ${GTK2_LIBRARIES} 35 | ${GLIB20_LIBRARIES} 36 | ${GTHREAD20_LIBRARIES} 37 | ${RSVG2_LIBRARIES}) 38 | endif() 39 | endif() 40 | -------------------------------------------------------------------------------- /cmake/FindAVFORMAT.cmake: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig) 2 | pkg_check_modules(PC_LIBAVFORMAT QUIET libavformat) 3 | pkg_check_modules(PC_LIBAVCODEC QUIET libavcodec) 4 | pkg_check_modules(PC_LIBAVUTIL QUIET libavutil) 5 | 6 | find_path(AVFORMAT_INCLUDE_DIR libavformat/avformat.h 7 | HINTS ${PC_LIBAVFORMAT_INCLUDEDIR} ${PC_LIBAVFORMAT_INCLUDE_DIRS}) 8 | 9 | find_library(AVFORMAT_LIBRARY NAMES avformat avformat-52 avformat-53 10 | HINTS ${PC_LIBAVFORMAT_LIBDIR} ${PC_LIBAVFORMAT_LIBRARY_DIRS}) 11 | 12 | find_library(AVCODEC_LIBRARY NAMES avcodec avcodec-52 avcodec-53 13 | HINTS ${PC_LIBAVCODEC_LIBDIR} ${PC_LIBAVCODEC_LIBRARY_DIRS}) 14 | 15 | find_library(AVUTIL_LIBRARY NAMES avutil avutil-50 avutil-51 16 | HINTS ${PC_LIBAVUTIL_LIBDIR} ${PC_LIBAVUTIL_LIBRARY_DIRS}) 17 | 18 | set(AVFORMAT_LIBRARIES ${AVFORMAT_LIBRARY} ${AVCODEC_LIBRARY} ${AVUTIL_LIBRARY}) 19 | set(AVFORMAT_INCLUDE_DIRS ${AVFORMAT_INCLUDE_DIR}) 20 | 21 | include(FindPackageHandleStandardArgs) 22 | find_package_handle_standard_args(AVFORMAT DEFAULT_MSG 23 | AVFORMAT_LIBRARY AVFORMAT_INCLUDE_DIR) 24 | mark_as_advanced(AVFORMAT_LIBRARY AVFORMAT_INCLUDE_DIR) 25 | -------------------------------------------------------------------------------- /doc/license/R128Scan.txt: -------------------------------------------------------------------------------- 1 | EBU R128 Gain processor. 2 | 3 | Copyright (C) 2011 Chris Moeller 4 | 5 | Portions copyright (c) 2011 Jan Kokemüller 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE." 24 | -------------------------------------------------------------------------------- /scanner/inputaudio/input.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #ifndef _INPUT_H_ 4 | #define _INPUT_H_ 5 | 6 | #include 7 | 8 | struct input_handle; 9 | 10 | struct input_ops { 11 | unsigned (*get_channels)(struct input_handle *ih); 12 | unsigned long (*get_samplerate)(struct input_handle *ih); 13 | float *(*get_buffer)(struct input_handle *ih); 14 | struct input_handle *(*handle_init)(); 15 | void (*handle_destroy)(struct input_handle **ih); 16 | int (*open_file)(struct input_handle *ih, char const *filename); 17 | int (*set_channel_map)(struct input_handle *ih, int *st); 18 | int (*allocate_buffer)(struct input_handle *ih); 19 | size_t (*get_total_frames)(struct input_handle *ih); 20 | size_t (*read_frames)(struct input_handle *ih); 21 | void (*free_buffer)(struct input_handle *ih); 22 | void (*close_file)(struct input_handle *ih); 23 | int (*init_library)(void); 24 | void (*exit_library)(void); 25 | }; 26 | 27 | int input_init(char *exe_name, char const *forced_plugin); 28 | int input_deinit(void); 29 | struct input_ops *input_get_ops(char const *filename); 30 | 31 | int input_open_fd(char const *filename); 32 | void input_close_fd(int fd); 33 | int input_read_fd(int fd, void *buf, unsigned int count); 34 | 35 | #endif /* _INPUT_H_ */ 36 | -------------------------------------------------------------------------------- /scanner/inputaudio/sndfile/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(utils) 2 | 3 | find_package(SNDFILE) 4 | find_package(PkgConfig) 5 | pkg_check_modules(GMODULE20 gmodule-2.0) 6 | 7 | if(GMODULE20_FOUND 8 | AND SNDFILE_FOUND 9 | AND INPUT_INCLUDE_DIR 10 | AND EBUR128_INCLUDE_DIR 11 | AND NOT DISABLE_GLIB20 12 | AND NOT DISABLE_SNDFILE) 13 | include_directories(SYSTEM ${SNDFILE_INCLUDE_DIRS}) 14 | include_directories(${INPUT_INCLUDE_DIR} ${EBUR128_INCLUDE_DIR}) 15 | include_directories(${GMODULE20_INCLUDE_DIRS}) 16 | link_directories(${SNDFILE_LIBRARY_DIRS} ${GMODULE20_LIBRARY_DIRS}) 17 | 18 | add_library(input_sndfile MODULE input_sndfile.c ../input_helper.c) 19 | 20 | target_link_libraries(input_sndfile ${SNDFILE_LIBRARIES} 21 | ${GMODULE20_LIBRARIES}) 22 | 23 | list(APPEND INPUT_SNDFILE_CFLAGS ${GMODULE20_CFLAGS_OTHER}) 24 | list(APPEND INPUT_SNDFILE_LDFLAGS ${GMODULE20_LDFLAGS_OTHER}) 25 | 26 | if(INPUT_SNDFILES_CFLAGS) 27 | to_space_list(INPUT_SNDFILE_CFLAGS) 28 | set_target_properties(input_sndfile PROPERTIES COMPILE_FLAGS 29 | ${INPUT_SNDFILE_CFLAGS}) 30 | endif() 31 | if(INPUT_SNDFILE_LDFLAGS) 32 | to_space_list(INPUT_SNDFILE_LDFLAGS) 33 | set_target_properties(input_sndfile PROPERTIES LINK_FLAGS 34 | ${INPUT_SNDFILE_LDFLAGS}) 35 | endif() 36 | endif() 37 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: "-*,\ 3 | bugprone-*,\ 4 | -bugprone-macro-parentheses,\ 5 | -bugprone-misplaced-widening-cast,\ 6 | -google-readability-casting,\ 7 | misc-*,\ 8 | -misc-incorrect-roundings,\ 9 | -misc-macro-parentheses,\ 10 | -misc-misplaced-widening-cast,\ 11 | -misc-static-assert,\ 12 | -misc-non-private-member-variables-in-classes, \ 13 | modernize-*,\ 14 | -modernize-deprecated-headers,\ 15 | -modernize-raw-string-literal,\ 16 | -modernize-return-braced-init-list,\ 17 | -modernize-use-nodiscard,\ 18 | -modernize-use-noexcept,\ 19 | -modernize-use-trailing-return-type,\ 20 | -modernize-use-transparent-functors,\ 21 | -modernize-use-using,\ 22 | -modernize-avoid-c-arrays,\ 23 | performance-*,\ 24 | -performance-inefficient-string-concatenation,\ 25 | -performance-inefficient-vector-operation,\ 26 | -performance-move-const-arg,\ 27 | -performance-no-int-to-ptr, \ 28 | readability-*,\ 29 | -readability-function-size,\ 30 | -readability-function-cognitive-complexity,\ 31 | -readability-identifier-naming,\ 32 | -readability-implicit-bool-cast,\ 33 | -readability-implicit-bool-conversion,\ 34 | -readability-inconsistent-declaration-parameter-name,\ 35 | -readability-magic-numbers,\ 36 | -readability-named-parameter,\ 37 | -readability-redundant-declaration,\ 38 | clang-analyzer-*,\ 39 | -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,\ 40 | clang-diagnostic-*,\ 41 | " 42 | CheckOptions: 43 | - key: modernize-use-default-member-init.UseAssignment 44 | value: '1' 45 | - key: modernize-use-equals-default.IgnoreMacros 46 | value: '0' 47 | ... 48 | -------------------------------------------------------------------------------- /doc/license/queue.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 1991, 1993 2 | The Regents of the University of California. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. Neither the name of the University nor the names of its contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /scanner/inputaudio/ffmpeg/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(utils) 2 | 3 | find_package(PkgConfig) 4 | pkg_check_modules(LIBAVFORMAT libavformat) 5 | pkg_check_modules(LIBAVCODEC libavcodec) 6 | pkg_check_modules(LIBAVUTIL libavutil) 7 | pkg_check_modules(GMODULE20 gmodule-2.0) 8 | 9 | set(SUMMARY_FFMPEG_FOUND 10 | ${LIBAVFORMAT_FOUND} 11 | CACHE INTERNAL "") 12 | 13 | if(LIBAVFORMAT_FOUND 14 | AND GMODULE20_FOUND 15 | AND INPUT_INCLUDE_DIR 16 | AND EBUR128_INCLUDE_DIR 17 | AND NOT DISABLE_GLIB20 18 | AND NOT DISABLE_FFMPEG) 19 | 20 | include_directories(${INPUT_INCLUDE_DIR} ${EBUR128_INCLUDE_DIR}) 21 | include_directories(${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} 22 | ${GMODULE20_INCLUDE_DIRS}) 23 | link_directories(${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVCODEC_LIBRARY_DIRS} 24 | ${LIBAVUTIL_LIBRARY_DIRS} ${GMODULE20_LIBRARY_DIRS}) 25 | 26 | add_library(input_ffmpeg MODULE input_ffmpeg.c ../input_helper.c) 27 | 28 | target_link_libraries( 29 | input_ffmpeg ${LIBAVFORMAT_LIBRARIES} ${LIBAVCODEC_LIBRARIES} 30 | ${LIBAVUTIL_LIBRARIES} ${GMODULE20_LIBRARIES}) 31 | 32 | list(APPEND INPUT_FFMPEG_CFLAGS ${LIBAVFORMAT_CFLAGS_OTHER} 33 | ${GMODULE20_CFLAGS_OTHER}) 34 | list(APPEND INPUT_FFMPEG_LDFLAGS ${LIBAVFORMAT_LDFLAGS_OTHER} 35 | ${LIBAVCODEC_LDFLAGS_OTHER} ${LIBAVUTIL_LDFLAGS_OTHER} 36 | ${GMODULE20_LDFLAGS_OTHER}) 37 | 38 | if(INPUT_FFMPEG_CFLAGS) 39 | to_space_list(INPUT_FFMPEG_CFLAGS) 40 | set_target_properties(input_ffmpeg PROPERTIES COMPILE_FLAGS 41 | ${INPUT_FFMPEG_CFLAGS}) 42 | endif() 43 | if(INPUT_FFMPEG_LDFLAGS) 44 | to_space_list(INPUT_FFMPEG_LDFLAGS) 45 | set_target_properties(input_ffmpeg PROPERTIES LINK_FLAGS 46 | ${INPUT_FFMPEG_LDFLAGS}) 47 | endif() 48 | endif() 49 | -------------------------------------------------------------------------------- /scanner/scanner-common/scanner-common.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #ifndef SCANNER_COMMON_H 4 | #define SCANNER_COMMON_H 5 | 6 | #include "ebur128.h" 7 | #include "filetree.h" 8 | #include "input.h" 9 | 10 | #include 11 | 12 | #define LOUDNESS_SCANNER_VERSION_MAJOR 0 13 | #define LOUDNESS_SCANNER_VERSION_MINOR 6 14 | #define LOUDNESS_SCANNER_VERSION_PATCH 0 15 | 16 | struct file_data { 17 | ebur128_state *st; 18 | size_t number_of_frames; 19 | size_t number_of_elapsed_frames; 20 | double loudness; 21 | double lra; 22 | double peak; 23 | double true_peak; 24 | 25 | double gain_album; 26 | double peak_album; 27 | 28 | void *user; 29 | 30 | gboolean scanned; 31 | int tagged; 32 | }; 33 | 34 | struct scan_opts { 35 | gboolean lra; 36 | gchar *peak; 37 | gboolean histogram; 38 | 39 | /* used if in tag mode to force dual mono */ 40 | gboolean force_dual_mono; 41 | /* if non-zero, decode all input audio to this file */ 42 | gchar *decode_file; 43 | }; 44 | 45 | extern GMutex progress_mutex; 46 | extern GCond progress_cond; 47 | extern guint64 elapsed_frames; 48 | extern guint64 total_frames; 49 | 50 | int open_plugin(char const *raw, char const *display, struct input_ops **ops, 51 | struct input_handle **ih); 52 | void scanner_init_common(void); 53 | void scanner_reset_common(void); 54 | void init_and_get_number_of_frames(struct filename_list_node *fln, 55 | int *do_scan); 56 | void init_state_and_scan_work_item(struct filename_list_node *fln, 57 | struct scan_opts *opts); 58 | void init_state_and_scan(gpointer work_item, GThreadPool *pool); 59 | void destroy_state(struct filename_list_node *fln, gpointer unused); 60 | void get_state(struct filename_list_node *fln, GPtrArray *states); 61 | void get_max_peaks(struct filename_list_node *fln, struct file_data *result); 62 | void clear_line(void); 63 | void process_files(GSList *files, struct scan_opts *opts); 64 | void print_version(void); 65 | 66 | #endif /* end of include guard: SCANNER_COMMON_H */ 67 | -------------------------------------------------------------------------------- /scanner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SCANNERCOMMON_INCLUDE_DIRS 2 | ${CMAKE_CURRENT_SOURCE_DIR}/scanner-common 3 | ${CMAKE_CURRENT_SOURCE_DIR}/scanner-tag 4 | ${CMAKE_CURRENT_SOURCE_DIR}/filetree 5 | ${CMAKE_CURRENT_SOURCE_DIR}/inputaudio 6 | ${EBUR128_INCLUDE_DIR}) 7 | 8 | add_subdirectory(filetree) 9 | add_subdirectory(inputaudio) 10 | 11 | include(utils) 12 | 13 | find_package(PkgConfig) 14 | if(PKG_CONFIG_FOUND) 15 | pkg_check_modules(GLIB20 glib-2.0) 16 | pkg_check_modules(GTHREAD20 gthread-2.0) 17 | endif() 18 | find_package(SNDFILE) 19 | 20 | set(SUMMARY_GLIB20_FOUND 21 | ${GLIB20_FOUND} 22 | CACHE INTERNAL "") 23 | set(SUMMARY_GTHREAD20_FOUND 24 | ${GTHREAD20_FOUND} 25 | CACHE INTERNAL "") 26 | set(SUMMARY_SNDFILE_FOUND 27 | ${SNDFILE_FOUND} 28 | CACHE INTERNAL "") 29 | 30 | if(GLIB20_FOUND 31 | AND GTHREAD20_FOUND 32 | AND NOT DISABLE_GLIB20) 33 | include_directories(${SCANNERCOMMON_INCLUDE_DIRS}) 34 | 35 | add_subdirectory(scanner-common) 36 | add_subdirectory(scanner-tag) 37 | 38 | include_directories(SYSTEM ${GLIB20_INCLUDE_DIRS} ${GTHREAD20_INCLUDE_DIRS}) 39 | link_directories(${GLIB20_LIBRARY_DIRS} ${GTHREAD20_LIBRARY_DIRS}) 40 | add_definitions(${GLIB20_CFLAGS_OTHER} ${GTHREAD20_CFLAGS_OTHER}) 41 | 42 | set(LOGO_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) 43 | add_subdirectory(scanner-drop-gtk) 44 | add_subdirectory(scanner-drop-qt) 45 | 46 | add_library(scanner-lib scanner-scan.c scanner-dump.c) 47 | target_link_libraries(scanner-lib scanner-common ebur128) 48 | 49 | add_executable(loudness scanner.c) 50 | target_link_libraries(loudness scanner-lib filetree input # 51 | ${GLIB20_LIBRARIES} ${GTHREAD20_LIBRARIES}) 52 | 53 | if(SNDFILE_FOUND AND NOT DISABLE_SNDFILE) 54 | set_property( 55 | TARGET loudness 56 | APPEND 57 | PROPERTY COMPILE_DEFINITIONS "USE_SNDFILE") 58 | endif() 59 | 60 | if(TARGET scanner-tag) 61 | target_link_libraries(loudness scanner-tag) 62 | set_property( 63 | TARGET loudness 64 | APPEND 65 | PROPERTY COMPILE_DEFINITIONS "USE_TAGLIB") 66 | endif() 67 | endif() 68 | -------------------------------------------------------------------------------- /cmake/utils.cmake: -------------------------------------------------------------------------------- 1 | macro(to_yes_no vars) 2 | foreach(var ${ARGV}) 3 | if(${var}) 4 | set(${var} "yes") 5 | else() 6 | set(${var} "no ") 7 | endif() 8 | endforeach() 9 | endmacro() 10 | 11 | macro(if_empty_print_missing vars) 12 | foreach(var ${ARGV}) 13 | if(NOT ${var}) 14 | set(${var} "") 15 | endif() 16 | endforeach() 17 | endmacro() 18 | 19 | function(to_space_list sc_list) 20 | set(ret) 21 | foreach(val ${${sc_list}}) 22 | set(ret "${ret} ${val}") 23 | endforeach() 24 | if(ret) 25 | string(STRIP ${ret} ret) 26 | set(${sc_list} "${ret}" PARENT_SCOPE) 27 | endif() 28 | endfunction() 29 | 30 | macro(find_pkg_config prefix pkgname) 31 | find_package(PkgConfig ${ARGV2}) 32 | if(PKG_CONFIG_FOUND) 33 | pkg_check_modules(${prefix}_PKGCONF ${ARGV2} ${pkgname}) 34 | if(${${prefix}_PKGCONF_FOUND}) 35 | message(STATUS "${pkgname} library dirs: ${${prefix}_PKGCONF_LIBRARY_DIRS}") 36 | message(STATUS "${pkgname} cflags: ${${prefix}_PKGCONF_CFLAGS_OTHER}") 37 | message(STATUS "${pkgname} include dirs: ${${prefix}_PKGCONF_INCLUDE_DIRS}") 38 | message(STATUS "${pkgname} libraries: ${${prefix}_PKGCONF_LIBRARIES}") 39 | message(STATUS "${pkgname} ldflags: ${${prefix}_PKGCONF_LDFLAGS_OTHER}") 40 | 41 | set(${prefix}_FOUND ${${prefix}_PKGCONF_FOUND}) 42 | set(${prefix}_CFLAGS ${${prefix}_PKGCONF_CFLAGS_OTHER}) 43 | to_space_list(${prefix}_CFLAGS) 44 | set(${prefix}_INCLUDE_DIRS ${${prefix}_PKGCONF_INCLUDE_DIRS}) 45 | foreach(lib ${${prefix}_PKGCONF_LIBRARIES}) 46 | string(TOUPPER ${lib} LIB) 47 | find_library(${prefix}_${LIB}_LIBRARY ${lib} 48 | HINTS ${${prefix}_PKGCONF_LIBRARY_DIRS}) 49 | mark_as_advanced(${prefix}_${LIB}_LIBRARY) 50 | list(APPEND ${prefix}_LIBRARIES ${${prefix}_${LIB}_LIBRARY}) 51 | endforeach() 52 | list(APPEND ${prefix}_LIBRARIES ${${prefix}_PKGCONF_LDFLAGS_OTHER}) 53 | endif() 54 | endif() 55 | endmacro() 56 | -------------------------------------------------------------------------------- /doc/license/speex.txt: -------------------------------------------------------------------------------- 1 | This package was first debianized by A. Maitland Bottoms 2 | on Tue, 16 Jul 2002 10:02:40 -0400. 3 | 4 | Downloaded from http://www.speex.org/ 5 | 6 | Upstream authors: 7 | Jean-Marc Valin 8 | David Rowe 9 | 10 | Copyright 2002-2007 Xiph.org Foundation 11 | Copyright 2002-2007 Jean-Marc Valin 12 | Copyright 2005-2007 Analog Devices Inc. 13 | Copyright 2005-2007 Commonwealth Scientific and Industrial Research 14 | Organisation (CSIRO) 15 | Copyright 1993, 2002, 2006 David Rowe 16 | Copyright 2003 EpicGames 17 | Copyright 1992-1994 Jutta Degener, Carsten Bormann 18 | 19 | License: 20 | 21 | Redistribution and use in source and binary forms, with or without 22 | modification, are permitted provided that the following conditions 23 | are met: 24 | 25 | - Redistributions of source code must retain the above copyright 26 | notice, this list of conditions and the following disclaimer. 27 | 28 | - Redistributions in binary form must reproduce the above copyright 29 | notice, this list of conditions and the following disclaimer in the 30 | documentation and/or other materials provided with the distribution. 31 | 32 | - Neither the name of the Xiph.org Foundation nor the names of its 33 | contributors may be used to endorse or promote products derived from 34 | this software without specific prior written permission. 35 | 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 37 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 38 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 39 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 40 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 41 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 42 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 43 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 44 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 45 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 46 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 47 | -------------------------------------------------------------------------------- /scanner/scanner-common/parse_args.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include "parse_args.h" 4 | 5 | #include 6 | #include 7 | 8 | #ifdef G_OS_WIN32 9 | #include 10 | typedef struct __startupinfo { 11 | DWORD cb; 12 | } _startupinfo; 13 | 14 | extern int __wgetmainargs(int *_Argc, wchar_t ***_Argv, wchar_t ***_Env, 15 | int _DoWildCard, _startupinfo *_StartInfo); 16 | #endif 17 | 18 | int 19 | parse_global_args(int *argc, char ***argv, GOptionEntry *entries, 20 | gboolean ignore_unknown) 21 | { 22 | GError *error = NULL; 23 | GOptionContext *context = g_option_context_new(NULL); 24 | #ifdef G_OS_WIN32 25 | wchar_t **wargv, **wenv; 26 | _startupinfo si = { 0 }; 27 | int i; 28 | 29 | __wgetmainargs(argc, &wargv, &wenv, TRUE, &si); 30 | *argv = g_new(gchar *, *argc + 1); 31 | for (i = 0; i < *argc; ++i) { 32 | (*argv)[i] = g_utf16_to_utf8(wargv[i], -1, NULL, NULL, NULL); 33 | } 34 | (*argv)[i] = NULL; 35 | #endif 36 | 37 | g_option_context_add_main_entries(context, entries, NULL); 38 | g_option_context_set_ignore_unknown_options(context, ignore_unknown); 39 | g_option_context_set_help_enabled(context, FALSE); 40 | if (!g_option_context_parse(context, argc, argv, &error)) { 41 | g_print("%s\n", error->message); 42 | g_option_context_free(context); 43 | return 1; 44 | } 45 | if (*argc == 1) { 46 | g_option_context_free(context); 47 | return 1; 48 | } 49 | g_option_context_free(context); 50 | return 0; 51 | } 52 | 53 | static void 54 | shift_arguments(int *argc, char **argv[]) 55 | { 56 | int i; 57 | for (i = 1; i < *argc - 1; ++i) { 58 | (*argv)[i] = (*argv)[i + 1]; 59 | } 60 | --(*argc); 61 | } 62 | 63 | gboolean 64 | parse_mode_args(int *argc, char **argv[], GOptionEntry *entries) 65 | { 66 | GError *error = NULL; 67 | GOptionContext *context = g_option_context_new(NULL); 68 | 69 | shift_arguments(argc, argv); 70 | 71 | g_option_context_add_main_entries(context, entries, NULL); 72 | g_option_context_set_help_enabled(context, FALSE); 73 | if (!g_option_context_parse(context, argc, argv, &error)) { 74 | g_print("%s\n", error->message); 75 | g_option_context_free(context); 76 | return FALSE; 77 | } 78 | g_option_context_free(context); 79 | if (*argc > 1 && !strcmp((*argv)[1], "--")) { 80 | shift_arguments(argc, argv); 81 | } 82 | if (*argc == 1) { 83 | return FALSE; 84 | } 85 | return TRUE; 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | loudness-scanner 2 | ================ 3 | 4 | loudness-scanner is a tool that scans your music files according to the EBU 5 | R128 standard for loudness normalisation. It optionally adds ReplayGain 6 | compatible tags to the files. 7 | 8 | All source code is licensed under the MIT license. See LICENSE file for 9 | details. 10 | 11 | Features 12 | -------- 13 | 14 | * Supports all libebur128 features: 15 | * Portable ANSI C code 16 | * Implements M, S and I modes 17 | * Implements loudness range measurement (EBU - TECH 3342) 18 | * True peak scanning 19 | * Supports all samplerates by recalculation of the filter coefficients 20 | * ReplayGain compatible tagging support for MP3, OGG, Musepack, FLAC and more 21 | 22 | 23 | Requirements 24 | ------------ 25 | 26 | - Glib 27 | - taglib 28 | 29 | input plugins (all optional): 30 | 31 | - libsndfile 32 | - ffmpeg 33 | 34 | optional GUI frontends: 35 | 36 | - GTK2 37 | 38 | or 39 | 40 | - Qt 41 | 42 | 43 | Installation 44 | ------------ 45 | 46 | In the root folder, type: 47 | 48 | mkdir build 49 | cd build 50 | cmake .. 51 | make 52 | 53 | If you want the git version, run: 54 | 55 | git clone https://github.com/jiixyj/loudness-scanner.git 56 | cd loudness-scanner 57 | git submodule init 58 | git submodule update 59 | 60 | Usage 61 | ----- 62 | 63 | Run "loudness scan" with the files you want to scan as arguments. The scanner 64 | will automatically choose the best input plugin for each file. You can force an 65 | input plugin with the command line option "--force-plugin=PLUGIN", where PLUGIN 66 | is one of `sndfile` or `ffmpeg`. 67 | 68 | The scanner also support ReplayGain tagging. Run it like this: 69 | 70 | loudness tag # scan files as album 71 | 72 | or: 73 | 74 | loudness tag -t # scan files as single tracks 75 | 76 | Use the option "-r" to search recursively for music files and tag them as one 77 | album per subfolder. 78 | 79 | Some more advanced tagging options are supported as well: 80 | 81 | - incremental tagging 82 | - forcing files to be treated as a single album (even though the files might be 83 | scattered over multiple folders) 84 | - `REPLAYGAIN_*` tags for Opus files (may be useful for older player software) 85 | - fine control over what values are written into the Opus header gain field 86 | 87 | The reference volume for tagging is -18 LUFS (5 dB louder than the EBU R 128 88 | reference level of -23 LUFS). See 89 | [here]() 90 | for more details and sources. 91 | 92 | Use the option "-p" to print information about peak values. Use "-p sample" for 93 | sample peaks, "-p true" for true peaks, "-p dbtp" for true peaks in dBTP and 94 | "-p all" to print all values. 95 | 96 | The scanner supports loudness range measurement with the command line 97 | option "-l". 98 | 99 | In "dump" mode, use the options "-s", "-m" or "-i" to print short-term 100 | (last 3s), momentary (last 0.4s) or integrated loudness information to stdout. 101 | For example: 102 | 103 | loudness dump -m 0.1 foo.wav 104 | 105 | to print the momentary loudness of foo.wav to stdout every 0.1s. 106 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(loudness-scanner) 3 | 4 | option(ENABLE_COMPILER_WARNINGS "enable compiler warnings" OFF) 5 | 6 | set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) 7 | set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}) 8 | set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}) 9 | 10 | include(utils) 11 | include(GNUInstallDirs) 12 | 13 | set(ENABLE_INTERNAL_QUEUE_H 14 | OFF 15 | CACHE BOOL "Use own queue.h") 16 | set(DISABLE_GLIB20 17 | OFF 18 | CACHE BOOL "Don't build with glib") 19 | set(DISABLE_SNDFILE 20 | OFF 21 | CACHE BOOL "Don't build with sndfile") 22 | set(DISABLE_TAGLIB 23 | OFF 24 | CACHE BOOL "Don't build with taglib") 25 | set(DISABLE_FFMPEG 26 | OFF 27 | CACHE BOOL "Don't build with ffmpeg") 28 | set(DISABLE_RSVG2 29 | OFF 30 | CACHE BOOL "Don't build with rsvg2") 31 | set(DISABLE_GTK2 32 | OFF 33 | CACHE BOOL "Don't build with gtk2") 34 | set(DISABLE_QT5 35 | OFF 36 | CACHE BOOL "Don't build with qt5") 37 | 38 | add_subdirectory(ebur128/ebur128) 39 | 40 | if(ENABLE_COMPILER_WARNINGS) 41 | add_compile_options( 42 | -Wall # 43 | -Wextra # 44 | -Wconversion # 45 | -Wsign-conversion # 46 | -Wundef # 47 | -Werror=return-type # 48 | -Werror=undef # 49 | -Wno-unknown-pragmas) 50 | 51 | add_compile_options( 52 | $<$:-Wmissing-prototypes> 53 | $<$:-Werror=implicit-function-declaration> 54 | $<$:-Werror=incompatible-pointer-types>) 55 | 56 | add_compile_options(-fdiagnostics-color=always) 57 | 58 | add_compile_options( 59 | $<$:-Wno-missing-field-initializers> # 60 | $<$:-Wno-builtin-declaration-mismatch> # 61 | ) 62 | endif() 63 | 64 | add_subdirectory(scanner) 65 | 66 | to_yes_no( 67 | SUMMARY_HAS_QUEUE 68 | SUMMARY_GLIB20_FOUND 69 | SUMMARY_GTHREAD20_FOUND 70 | SUMMARY_SNDFILE_FOUND 71 | SUMMARY_TAGLIB_FOUND 72 | SUMMARY_FFMPEG_FOUND 73 | SUMMARY_RSVG2_FOUND 74 | SUMMARY_GTK2_FOUND 75 | SUMMARY_QT5_FOUND) 76 | to_yes_no( 77 | DISABLE_GLIB20 78 | DISABLE_SNDFILE 79 | DISABLE_TAGLIB 80 | DISABLE_FFMPEG 81 | DISABLE_RSVG2 82 | DISABLE_GTK2 83 | DISABLE_QT5) 84 | 85 | if(ENABLE_INTERNAL_QUEUE_H) 86 | set(USE_QUEUE "using own copy of queue.h") 87 | else() 88 | set(USE_QUEUE "using system copy of queue.h") 89 | endif() 90 | 91 | ##### Print status 92 | message(STATUS "status found / disabled --") 93 | message(STATUS "queue.h: ${SUMMARY_HAS_QUEUE}" # 94 | " ${USE_QUEUE}") 95 | message(STATUS "glib-2.0: ${SUMMARY_GLIB20_FOUND}" 96 | " ${DISABLE_GLIB20}") 97 | message(STATUS "gthread-2.0: ${SUMMARY_GTHREAD20_FOUND}" 98 | " ${DISABLE_GLIB20}") 99 | message(STATUS "sndfile: ${SUMMARY_SNDFILE_FOUND}" 100 | " ${DISABLE_SNDFILE}") 101 | message(STATUS "taglib: ${SUMMARY_TAGLIB_FOUND}" 102 | " ${DISABLE_TAGLIB}") 103 | message(STATUS "ffmpeg: ${SUMMARY_FFMPEG_FOUND}" 104 | " ${DISABLE_FFMPEG}") 105 | message(STATUS "rsvg2: ${SUMMARY_RSVG2_FOUND}" # 106 | " ${DISABLE_RSVG2}") 107 | message(STATUS "gtk2: ${SUMMARY_GTK2_FOUND}" # 108 | " ${DISABLE_GTK2}") 109 | message(STATUS "qt5: ${SUMMARY_QT5_FOUND}" # 110 | " ${DISABLE_QT5}") 111 | 112 | if(NOT SUMMARY_HAS_QUEUE AND NOT ENABLE_INTERNAL_QUEUE_H) 113 | message( 114 | FATAL_ERROR "queue.h not found, please set ENABLE_INTERNAL_QUEUE_H to ON") 115 | endif() 116 | -------------------------------------------------------------------------------- /scanner/scanner-drop-qt/scanner-drop-qt.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | extern "C" { 12 | #include "input.h" 13 | 14 | // work around error when including mingw-w64 1.0 float.h header 15 | #ifdef __MINGW32__ 16 | #define _FLOAT_H___ 17 | #endif 18 | 19 | #include "filetree.h" 20 | #include "scanner-common.h" 21 | #include "scanner-tag.h" 22 | } 23 | 24 | class RenderArea : public QWidget { 25 | Q_OBJECT 26 | public: 27 | RenderArea(QWidget *parent = NULL); 28 | public slots: 29 | void updateLogo(); 30 | void resetLogo(); 31 | 32 | protected: 33 | void paintEvent(QPaintEvent *event); 34 | 35 | private: 36 | QSvgRenderer *svg_renderer_; 37 | qreal rotation_state; 38 | }; 39 | 40 | class WorkerThread : public QThread { 41 | Q_OBJECT 42 | public: 43 | WorkerThread(QList const &files); 44 | signals: 45 | void showResultList(GSList *files, void *tree); 46 | 47 | protected: 48 | void run(); 49 | 50 | private: 51 | QList urls_; 52 | }; 53 | 54 | class GUIUpdateThread : public QThread { 55 | Q_OBJECT 56 | public: 57 | GUIUpdateThread(QWidget *parent); 58 | public slots: 59 | void stopThread(); 60 | 61 | protected: 62 | void run(); 63 | signals: 64 | void rotateLogo(); 65 | void resetLogo(); 66 | void setProgressBar(int value); 67 | 68 | private: 69 | int old_progress_bar_value_; 70 | bool rotation_active_; 71 | bool stop_thread_; 72 | }; 73 | 74 | class MainWindow : public QWidget { 75 | Q_OBJECT 76 | public: 77 | MainWindow(QWidget *parent = NULL); 78 | ~MainWindow(); 79 | QSize sizeHint() const; 80 | 81 | protected: 82 | void mousePressEvent(QMouseEvent *event); 83 | void mouseMoveEvent(QMouseEvent *event); 84 | void mouseReleaseEvent(QMouseEvent *event); 85 | void dragEnterEvent(QDragEnterEvent *event); 86 | void dropEvent(QDropEvent *event); 87 | private slots: 88 | void cleanUpThread(); 89 | void setProgressBar(int); 90 | void rotateLogo(); 91 | void resetLogo(); 92 | void showResultList(GSList *files, void *tree); 93 | signals: 94 | void stopGUIThread(); 95 | 96 | private: 97 | QPoint dragPosition; 98 | RenderArea *render_area_; 99 | QProgressBar *progress_bar_; 100 | WorkerThread *worker_thread_; 101 | GUIUpdateThread gui_update_thread_; 102 | QTimer *logo_rotation_timer; 103 | }; 104 | 105 | class ResultData : public QAbstractTableModel { 106 | Q_OBJECT 107 | public: 108 | ResultData(GSList *files); 109 | int rowCount(QModelIndex const &parent = QModelIndex()) const; 110 | int columnCount(QModelIndex const &parent = QModelIndex()) const; 111 | QVariant data(QModelIndex const &index, 112 | int role = Qt::DisplayRole) const; 113 | QVariant headerData(int section, Qt::Orientation orientation, 114 | int role = Qt::DisplayRole) const; 115 | 116 | private: 117 | std::vector files_; 118 | }; 119 | 120 | class ResultWindow : public QWidget { 121 | Q_OBJECT 122 | public: 123 | ResultWindow(QWidget *parent, GSList *files, Filetree tree); 124 | ~ResultWindow(); 125 | QSize sizeHint() const; 126 | private slots: 127 | void tag_files(); 128 | 129 | private: 130 | ResultData data; 131 | QTreeView *view; 132 | QSortFilterProxyModel *proxyModel; 133 | QPushButton *tag_button; 134 | GSList *files_; 135 | Filetree tree_; 136 | }; 137 | 138 | class IconDelegate : public QStyledItemDelegate { 139 | Q_OBJECT 140 | public: 141 | IconDelegate(QWidget *parent = NULL); 142 | void paint(QPainter *painter, QStyleOptionViewItem const &option, 143 | QModelIndex const &index) const; 144 | }; 145 | -------------------------------------------------------------------------------- /scanner/scanner-scan.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include "scanner-scan.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "input.h" 13 | #include "nproc.h" 14 | #include "parse_args.h" 15 | #include "scanner-common.h" 16 | 17 | 18 | static struct file_data empty; 19 | 20 | extern gboolean histogram; 21 | static gboolean lra = FALSE; 22 | static gchar *peak = NULL; 23 | extern gchar *decode_to_file; 24 | 25 | static GOptionEntry entries[] = { { "lra", 'l', 0, G_OPTION_ARG_NONE, &lra, 26 | NULL, NULL }, 27 | { "peak", 'p', 0, G_OPTION_ARG_STRING, &peak, NULL, NULL }, 28 | { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, 0 } }; 29 | 30 | static void 31 | print_file_data(struct filename_list_node *fln, gpointer unused) 32 | { 33 | struct file_data *fd = (struct file_data *)fln->d; 34 | 35 | (void)unused; 36 | if (fd->scanned) { 37 | if (fd->loudness <= -HUGE_VAL) { 38 | g_print(" -inf LUFS"); 39 | } else { 40 | g_print("%5.1f LUFS", fd->loudness); 41 | } 42 | if (lra) 43 | g_print(", %4.1f LU", fd->lra); 44 | if (peak) { 45 | if (!strcmp(peak, "sample") || !strcmp(peak, "all")) 46 | g_print(", %11.6f", fd->peak); 47 | if (!strcmp(peak, "true") || !strcmp(peak, "all")) 48 | g_print(", %11.6f", fd->true_peak); 49 | if (!strcmp(peak, "dbtp") || !strcmp(peak, "all")) { 50 | if (fd->true_peak < DBL_MIN) 51 | g_print(", -inf dBTP"); 52 | else 53 | g_print(", %5.1f dBTP", 54 | 20.0 * log(fd->true_peak) / 55 | log(10.0)); 56 | } 57 | } 58 | if (fln->fr->display[0]) { 59 | g_print(", "); 60 | print_utf8_string(fln->fr->display); 61 | } 62 | putchar('\n'); 63 | } 64 | } 65 | 66 | static void 67 | print_summary(GSList *files) 68 | { 69 | int i; 70 | GPtrArray *states = g_ptr_array_new(); 71 | struct filename_list_node n; 72 | struct filename_representations fr; 73 | struct file_data result; 74 | memcpy(&result, &empty, sizeof empty); 75 | 76 | g_slist_foreach(files, (GFunc)get_state, states); 77 | ebur128_loudness_global_multiple((ebur128_state **)states->pdata, 78 | states->len, &result.loudness); 79 | if (lra) { 80 | ebur128_loudness_range_multiple((ebur128_state **)states->pdata, 81 | states->len, &result.lra); 82 | } 83 | if (peak) { 84 | g_slist_foreach(files, (GFunc)get_max_peaks, &result); 85 | } 86 | 87 | result.scanned = TRUE; 88 | n.fr = &fr; 89 | n.fr->display = ""; 90 | n.d = &result; 91 | for (i = 0; i < 79; ++i) { 92 | fputc('-', stderr); 93 | }; 94 | fputc('\n', stderr); 95 | print_file_data(&n, NULL); 96 | 97 | g_ptr_array_free(states, TRUE); 98 | } 99 | 100 | void 101 | loudness_scan(GSList *files) 102 | { 103 | struct scan_opts opts = { lra, peak, histogram, FALSE, decode_to_file }; 104 | int do_scan = FALSE; 105 | 106 | g_slist_foreach(files, (GFunc)init_and_get_number_of_frames, &do_scan); 107 | if (do_scan) { 108 | 109 | process_files(files, &opts); 110 | 111 | clear_line(); 112 | fprintf(stderr, " Loudness"); 113 | if (lra) 114 | fprintf(stderr, ", LRA"); 115 | if (peak) { 116 | if (!strcmp(peak, "sample") || !strcmp(peak, "all")) 117 | fprintf(stderr, ", Sample peak"); 118 | if (!strcmp(peak, "true") || !strcmp(peak, "all")) 119 | fprintf(stderr, ", True peak"); 120 | if (!strcmp(peak, "dbtp") || !strcmp(peak, "all")) 121 | fprintf(stderr, ", True peak"); 122 | } 123 | fprintf(stderr, "\n"); 124 | 125 | g_slist_foreach(files, (GFunc)print_file_data, NULL); 126 | print_summary(files); 127 | } 128 | g_slist_foreach(files, (GFunc)destroy_state, NULL); 129 | scanner_reset_common(); 130 | 131 | g_free(peak); 132 | } 133 | 134 | gboolean 135 | loudness_scan_parse(int *argc, char **argv[]) 136 | { 137 | gboolean success = parse_mode_args(argc, argv, entries); 138 | if (peak && strcmp(peak, "sample") && strcmp(peak, "true") && 139 | strcmp(peak, "dbtp") && strcmp(peak, "all")) { 140 | fprintf(stderr, "Invalid argument to --peak!\n"); 141 | return FALSE; 142 | } 143 | if (!success) { 144 | if (*argc == 1) 145 | fprintf(stderr, "Missing arguments\n"); 146 | return FALSE; 147 | } 148 | return TRUE; 149 | } 150 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # $FreeBSD$ 2 | # Basic .clang-format 3 | --- 4 | BasedOnStyle: WebKit 5 | AlignAfterOpenBracket: DontAlign 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: false 10 | AlignTrailingComments: true 11 | AllowAllArgumentsOnNextLine: false 12 | AllowAllParametersOfDeclarationOnNextLine: false 13 | AllowShortBlocksOnASingleLine: Never 14 | AllowShortCaseLabelsOnASingleLine: false 15 | AllowShortFunctionsOnASingleLine: InlineOnly 16 | AllowShortIfStatementsOnASingleLine: Never 17 | AllowShortLoopsOnASingleLine: false 18 | AlwaysBreakAfterReturnType: TopLevelDefinitions 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: MultiLine 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BreakBeforeBinaryOperators: None 24 | BreakBeforeBraces: WebKit 25 | BreakBeforeTernaryOperators: false 26 | # TODO: BreakStringLiterals can cause very strange formatting so turn it off? 27 | BreakStringLiterals: false 28 | # Prefer: 29 | # some_var = function(arg1, 30 | # arg2) 31 | # over: 32 | # some_var = 33 | # function(arg1, arg2) 34 | PenaltyBreakAssignment: 100 35 | # Prefer: 36 | # some_long_function(arg1, arg2 37 | # arg3) 38 | # over: 39 | # some_long_function( 40 | # arg1, arg2, arg3) 41 | PenaltyBreakBeforeFirstCallParameter: 100 42 | CompactNamespaces: true 43 | DerivePointerAlignment: false 44 | DisableFormat: false 45 | ForEachMacros: 46 | - ARB_ARRFOREACH 47 | - ARB_ARRFOREACH_REVWCOND 48 | - ARB_ARRFOREACH_REVERSE 49 | - ARB_FOREACH 50 | - ARB_FOREACH_FROM 51 | - ARB_FOREACH_SAFE 52 | - ARB_FOREACH_REVERSE 53 | - ARB_FOREACH_REVERSE_FROM 54 | - ARB_FOREACH_REVERSE_SAFE 55 | - BIT_FOREACH_ISCLR 56 | - BIT_FOREACH_ISSET 57 | - CPU_FOREACH 58 | - CPU_FOREACH_ISCLR 59 | - CPU_FOREACH_ISSET 60 | - FOREACH_THREAD_IN_PROC 61 | - FOREACH_PROC_IN_SYSTEM 62 | - FOREACH_PRISON_CHILD 63 | - FOREACH_PRISON_DESCENDANT 64 | - FOREACH_PRISON_DESCENDANT_LOCKED 65 | - FOREACH_PRISON_DESCENDANT_LOCKED_LEVEL 66 | - MNT_VNODE_FOREACH_ALL 67 | - MNT_VNODE_FOREACH_ACTIVE 68 | - RB_FOREACH 69 | - RB_FOREACH_FROM 70 | - RB_FOREACH_SAFE 71 | - RB_FOREACH_REVERSE 72 | - RB_FOREACH_REVERSE_FROM 73 | - RB_FOREACH_REVERSE_SAFE 74 | - SLIST_FOREACH 75 | - SLIST_FOREACH_FROM 76 | - SLIST_FOREACH_FROM_SAFE 77 | - SLIST_FOREACH_SAFE 78 | - SLIST_FOREACH_PREVPTR 79 | - SPLAY_FOREACH 80 | - LIST_FOREACH 81 | - LIST_FOREACH_FROM 82 | - LIST_FOREACH_FROM_SAFE 83 | - LIST_FOREACH_SAFE 84 | - STAILQ_FOREACH 85 | - STAILQ_FOREACH_FROM 86 | - STAILQ_FOREACH_FROM_SAFE 87 | - STAILQ_FOREACH_SAFE 88 | - TAILQ_FOREACH 89 | - TAILQ_FOREACH_FROM 90 | - TAILQ_FOREACH_FROM_SAFE 91 | - TAILQ_FOREACH_REVERSE 92 | - TAILQ_FOREACH_REVERSE_FROM 93 | - TAILQ_FOREACH_REVERSE_FROM_SAFE 94 | - TAILQ_FOREACH_REVERSE_SAFE 95 | - TAILQ_FOREACH_SAFE 96 | - VM_MAP_ENTRY_FOREACH 97 | - VM_PAGE_DUMP_FOREACH 98 | IndentCaseLabels: false 99 | IndentPPDirectives: None 100 | Language: Cpp 101 | NamespaceIndentation: None 102 | PointerAlignment: Right 103 | ContinuationIndentWidth: 4 104 | IndentWidth: 8 105 | TabWidth: 8 106 | ColumnLimit: 80 107 | QualifierAlignment: Right 108 | UseTab: Always 109 | SpaceAfterCStyleCast: false 110 | # LLVM's header include ordering style is almost the exact opposite of ours. 111 | # Unfortunately, they have hard-coded their preferences into clang-format. 112 | # Clobbering this regular expression to avoid matching prevents non-system 113 | # headers from being forcibly moved to the top of the include list. 114 | # http://llvm.org/docs/CodingStandards.html#include-style 115 | IncludeIsMainRegex: 'BLAH_DONT_MATCH_ANYTHING' 116 | SortIncludes: true 117 | KeepEmptyLinesAtTheStartOfBlocks: true 118 | TypenameMacros: 119 | - ARB_ELMTYPE 120 | - ARB_HEAD 121 | - ARB8_HEAD 122 | - ARB16_HEAD 123 | - ARB32_HEAD 124 | - ARB_ENTRY 125 | - ARB8_ENTRY 126 | - ARB16_ENTRY 127 | - ARB32_ENTRY 128 | - LIST_CLASS_ENTRY 129 | - LIST_CLASS_HEAD 130 | - LIST_ENTRY 131 | - LIST_HEAD 132 | - QUEUE_TYPEOF 133 | - RB_ENTRY 134 | - RB_HEAD 135 | - SLIST_CLASS_HEAD 136 | - SLIST_CLASS_ENTRY 137 | - SLIST_HEAD 138 | - SLIST_ENTRY 139 | - SMR_POINTER 140 | - SPLAY_ENTRY 141 | - SPLAY_HEAD 142 | - STAILQ_CLASS_ENTRY 143 | - STAILQ_CLASS_HEAD 144 | - STAILQ_ENTRY 145 | - STAILQ_HEAD 146 | - TAILQ_CLASS_ENTRY 147 | - TAILQ_CLASS_HEAD 148 | - TAILQ_ENTRY 149 | - TAILQ_HEAD 150 | MaxEmptyLinesToKeep: 2 151 | -------------------------------------------------------------------------------- /cmake/LibFindMacros.cmake: -------------------------------------------------------------------------------- 1 | # Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments 2 | # used for the current package. For this to work, the first parameter must be the 3 | # prefix of the current package, then the prefix of the new package etc, which are 4 | # passed to find_package. 5 | macro (libfind_package PREFIX) 6 | set (LIBFIND_PACKAGE_ARGS ${ARGN}) 7 | if (${PREFIX}_FIND_QUIETLY) 8 | set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} QUIET) 9 | endif (${PREFIX}_FIND_QUIETLY) 10 | if (${PREFIX}_FIND_REQUIRED) 11 | set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} REQUIRED) 12 | endif (${PREFIX}_FIND_REQUIRED) 13 | find_package(${LIBFIND_PACKAGE_ARGS}) 14 | endmacro (libfind_package) 15 | 16 | # CMake developers made the UsePkgConfig system deprecated in the same release (2.6) 17 | # where they added pkg_check_modules. Consequently I need to support both in my scripts 18 | # to avoid those deprecated warnings. Here's a helper that does just that. 19 | # Works identically to pkg_check_modules, except that no checks are needed prior to use. 20 | macro (libfind_pkg_check_modules PREFIX PKGNAME) 21 | if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) 22 | include(UsePkgConfig) 23 | pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS) 24 | else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) 25 | find_package(PkgConfig) 26 | if (PKG_CONFIG_FOUND) 27 | pkg_check_modules(${PREFIX} ${PKGNAME}) 28 | endif (PKG_CONFIG_FOUND) 29 | endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) 30 | endmacro (libfind_pkg_check_modules) 31 | 32 | # Do the final processing once the paths have been detected. 33 | # If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain 34 | # all the variables, each of which contain one include directory. 35 | # Ditto for ${PREFIX}_PROCESS_LIBS and library files. 36 | # Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. 37 | # Also handles errors in case library detection was required, etc. 38 | macro (libfind_process PREFIX) 39 | # Skip processing if already processed during this run 40 | if (NOT ${PREFIX}_FOUND) 41 | # Start with the assumption that the library was found 42 | set (${PREFIX}_FOUND TRUE) 43 | 44 | # Process all includes and set _FOUND to false if any are missing 45 | foreach (i ${${PREFIX}_PROCESS_INCLUDES}) 46 | if (${i}) 47 | set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}}) 48 | mark_as_advanced(${i}) 49 | else (${i}) 50 | set (${PREFIX}_FOUND FALSE) 51 | endif (${i}) 52 | endforeach (i) 53 | 54 | # Process all libraries and set _FOUND to false if any are missing 55 | foreach (i ${${PREFIX}_PROCESS_LIBS}) 56 | if (${i}) 57 | set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}}) 58 | mark_as_advanced(${i}) 59 | else (${i}) 60 | set (${PREFIX}_FOUND FALSE) 61 | endif (${i}) 62 | endforeach (i) 63 | 64 | # Print message and/or exit on fatal error 65 | if (${PREFIX}_FOUND) 66 | if (NOT ${PREFIX}_FIND_QUIETLY) 67 | message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") 68 | endif (NOT ${PREFIX}_FIND_QUIETLY) 69 | else (${PREFIX}_FOUND) 70 | if (${PREFIX}_FIND_REQUIRED) 71 | foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS}) 72 | message("${i}=${${i}}") 73 | endforeach (i) 74 | message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.") 75 | endif (${PREFIX}_FIND_REQUIRED) 76 | endif (${PREFIX}_FOUND) 77 | endif (NOT ${PREFIX}_FOUND) 78 | endmacro (libfind_process) 79 | 80 | macro(libfind_library PREFIX basename) 81 | set(TMP "") 82 | if(MSVC80) 83 | set(TMP -vc80) 84 | endif(MSVC80) 85 | if(MSVC90) 86 | set(TMP -vc90) 87 | endif(MSVC90) 88 | set(${PREFIX}_LIBNAMES ${basename}${TMP}) 89 | if(${ARGC} GREATER 2) 90 | set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2}) 91 | string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES}) 92 | set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP}) 93 | endif(${ARGC} GREATER 2) 94 | find_library(${PREFIX}_LIBRARY 95 | NAMES ${${PREFIX}_LIBNAMES} 96 | PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS} 97 | ) 98 | endmacro(libfind_library) 99 | 100 | -------------------------------------------------------------------------------- /scanner/scanner-dump.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include "scanner-dump.h" 4 | 5 | #include 6 | #include 7 | 8 | #include "nproc.h" 9 | #include "parse_args.h" 10 | #include "scanner-common.h" 11 | 12 | extern gboolean verbose; 13 | static double momentary; 14 | static double shortterm; 15 | static double integrated; 16 | extern gchar *decode_to_file; 17 | 18 | static GOptionEntry entries[] = { { "momentary", 'm', 0, G_OPTION_ARG_DOUBLE, 19 | &momentary, NULL, NULL }, 20 | { "shortterm", 's', 0, G_OPTION_ARG_DOUBLE, &shortterm, NULL, NULL }, 21 | { "integrated", 'i', 0, G_OPTION_ARG_DOUBLE, &integrated, NULL, NULL }, 22 | { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, 0 } }; 23 | 24 | static double interval; 25 | static int r128_mode; 26 | static ebur128_state *st; 27 | 28 | static void 29 | dump_loudness_info(struct filename_list_node *fln, int *ret) 30 | { 31 | struct input_ops *ops = NULL; 32 | struct input_handle *ih = NULL; 33 | float *buffer = NULL; 34 | 35 | int result; 36 | static size_t nr_frames_read; 37 | static size_t frames_counter, frames_needed; 38 | 39 | result = open_plugin(fln->fr->raw, fln->fr->display, &ops, &ih); 40 | if (result) { 41 | *ret = EXIT_FAILURE; 42 | goto free; 43 | } 44 | 45 | if (!st) { 46 | st = ebur128_init(ops->get_channels(ih), 47 | ops->get_samplerate(ih), r128_mode); 48 | if (!st) 49 | abort(); 50 | } else { 51 | if (!ebur128_change_parameters(st, ops->get_channels(ih), 52 | ops->get_samplerate(ih))) { 53 | frames_counter = 0; 54 | } 55 | } 56 | 57 | result = ops->allocate_buffer(ih); 58 | if (result) 59 | abort(); 60 | buffer = ops->get_buffer(ih); 61 | 62 | frames_needed = (size_t)(interval * (double)st->samplerate + 0.5); 63 | 64 | while ((nr_frames_read = ops->read_frames(ih))) { 65 | float *tmp_buffer = buffer; 66 | double loudness; 67 | while (nr_frames_read > 0) { 68 | if (frames_counter + nr_frames_read >= frames_needed) { 69 | result = ebur128_add_frames_float(st, 70 | tmp_buffer, frames_needed - frames_counter); 71 | if (result) 72 | abort(); 73 | tmp_buffer += (frames_needed - frames_counter) * 74 | st->channels; 75 | nr_frames_read -= frames_needed - 76 | frames_counter; 77 | frames_counter = 0; 78 | switch (r128_mode) { 79 | case EBUR128_MODE_M: 80 | ebur128_loudness_momentary(st, 81 | &loudness); 82 | printf("%.1f\n", loudness); 83 | break; 84 | case EBUR128_MODE_S: 85 | ebur128_loudness_shortterm(st, 86 | &loudness); 87 | printf("%.1f\n", loudness); 88 | break; 89 | case EBUR128_MODE_I: 90 | ebur128_loudness_global(st, &loudness); 91 | printf("%.1f\n", loudness); 92 | break; 93 | default: 94 | fprintf(stderr, "Invalid mode!\n"); 95 | abort(); 96 | } 97 | } else { 98 | result = ebur128_add_frames_float(st, 99 | tmp_buffer, nr_frames_read); 100 | if (result) 101 | abort(); 102 | tmp_buffer += (nr_frames_read)*st->channels; 103 | frames_counter += nr_frames_read; 104 | nr_frames_read = 0; 105 | } 106 | } 107 | } 108 | 109 | free: 110 | if (ih) 111 | ops->free_buffer(ih); 112 | if (!result) 113 | ops->close_file(ih); 114 | if (ih) 115 | ops->handle_destroy(&ih); 116 | } 117 | 118 | int 119 | loudness_dump(GSList *files) 120 | { 121 | int ret = 0; 122 | 123 | if (momentary > 0.0) 124 | r128_mode = EBUR128_MODE_M; 125 | else if (shortterm > 0.0) 126 | r128_mode = EBUR128_MODE_S; 127 | else if (integrated > 0.0) 128 | r128_mode = EBUR128_MODE_I; 129 | else 130 | return EXIT_FAILURE; 131 | 132 | g_slist_foreach(files, (GFunc)dump_loudness_info, &ret); 133 | if (st) 134 | ebur128_destroy(&st); 135 | 136 | return ret; 137 | } 138 | 139 | gboolean 140 | loudness_dump_parse(int *argc, char **argv[]) 141 | { 142 | if (decode_to_file) { 143 | fprintf(stderr, "Cannot decode to file in dump mode\n"); 144 | return FALSE; 145 | } 146 | 147 | if (!parse_mode_args(argc, argv, entries)) { 148 | if (*argc == 1) 149 | fprintf(stderr, "Missing arguments\n"); 150 | return FALSE; 151 | } 152 | 153 | if ((momentary != 0.0) + (shortterm != 0.0) + (integrated != 0.0) != 154 | 1 || 155 | (interval = momentary + shortterm + integrated) <= 0.0) { 156 | fprintf(stderr, 157 | "Exactly one of -m, -s and -i must be positive!\n"); 158 | return FALSE; 159 | } 160 | 161 | if (momentary > 0.4 || shortterm > 3.0) { 162 | fprintf(stderr, 163 | "Warning: you may lose samples when specifying " 164 | "this interval!\n"); 165 | } 166 | return TRUE; 167 | } 168 | -------------------------------------------------------------------------------- /scanner/inputaudio/sndfile/input_sndfile.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #define _POSIX_C_SOURCE 1 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ebur128.h" 10 | #include "input.h" 11 | 12 | struct input_handle { 13 | SF_INFO file_info; 14 | SNDFILE *file; 15 | float *buffer; 16 | }; 17 | 18 | static unsigned 19 | sndfile_get_channels(struct input_handle *ih) 20 | { 21 | return (unsigned)ih->file_info.channels; 22 | } 23 | 24 | static unsigned long 25 | sndfile_get_samplerate(struct input_handle *ih) 26 | { 27 | return (unsigned long)ih->file_info.samplerate; 28 | } 29 | 30 | static float * 31 | sndfile_get_buffer(struct input_handle *ih) 32 | { 33 | return ih->buffer; 34 | } 35 | 36 | static struct input_handle * 37 | sndfile_handle_init() 38 | { 39 | struct input_handle *ret; 40 | ret = malloc(sizeof(struct input_handle)); 41 | memset(&ret->file_info, '\0', sizeof(ret->file_info)); 42 | return ret; 43 | } 44 | 45 | static void 46 | sndfile_handle_destroy(struct input_handle **ih) 47 | { 48 | free(*ih); 49 | *ih = NULL; 50 | } 51 | 52 | static int 53 | sndfile_open_file(struct input_handle *ih, char const *filename) 54 | { 55 | #ifdef G_OS_WIN32 56 | int fd; 57 | g_usleep(10); 58 | fd = input_open_fd(filename); 59 | if (fd < 0) 60 | return 1; 61 | ih->file = sf_open_fd(fd, SFM_READ, &ih->file_info, 1); 62 | #else 63 | ih->file = sf_open(filename, SFM_READ, &ih->file_info); 64 | #endif 65 | 66 | if (!ih->file) { 67 | #ifdef G_OS_WIN32 68 | _close(fd); 69 | #endif 70 | return 1; 71 | } 72 | 73 | return 0; 74 | } 75 | 76 | static int 77 | sndfile_set_channel_map(struct input_handle *ih, int *st) 78 | { 79 | int result; 80 | int *channel_map = (int *)calloc((size_t)ih->file_info.channels, 81 | sizeof(int)); 82 | if (!channel_map) { 83 | return 1; 84 | } 85 | result = sf_command(ih->file, SFC_GET_CHANNEL_MAP_INFO, 86 | (void *)channel_map, 87 | (int)((size_t)ih->file_info.channels * sizeof(int))); 88 | /* If sndfile found a channel map, set it with 89 | * ebur128_set_channel_map */ 90 | if (result == SF_TRUE) { 91 | int j; 92 | for (j = 0; j < ih->file_info.channels; ++j) { 93 | switch (channel_map[j]) { 94 | case SF_CHANNEL_MAP_INVALID: 95 | st[j] = EBUR128_UNUSED; 96 | break; 97 | case SF_CHANNEL_MAP_MONO: 98 | st[j] = EBUR128_CENTER; 99 | break; 100 | case SF_CHANNEL_MAP_LEFT: 101 | st[j] = EBUR128_LEFT; 102 | break; 103 | case SF_CHANNEL_MAP_RIGHT: 104 | st[j] = EBUR128_RIGHT; 105 | break; 106 | case SF_CHANNEL_MAP_CENTER: 107 | st[j] = EBUR128_CENTER; 108 | break; 109 | case SF_CHANNEL_MAP_REAR_LEFT: 110 | st[j] = EBUR128_LEFT_SURROUND; 111 | break; 112 | case SF_CHANNEL_MAP_REAR_RIGHT: 113 | st[j] = EBUR128_RIGHT_SURROUND; 114 | break; 115 | default: 116 | st[j] = EBUR128_UNUSED; 117 | break; 118 | } 119 | } 120 | free(channel_map); 121 | return 0; 122 | } 123 | 124 | free(channel_map); 125 | return 1; 126 | } 127 | 128 | static int 129 | sndfile_allocate_buffer(struct input_handle *ih) 130 | { 131 | ih->buffer = (float *)malloc((size_t)ih->file_info.samplerate * 132 | (size_t)ih->file_info.channels * sizeof(float)); 133 | if (!ih->buffer) { 134 | return 1; 135 | } 136 | return 0; 137 | } 138 | 139 | static size_t 140 | sndfile_get_total_frames(struct input_handle *ih) 141 | { 142 | return (size_t)ih->file_info.frames; 143 | } 144 | 145 | static size_t 146 | sndfile_read_frames(struct input_handle *ih) 147 | { 148 | return (size_t)sf_readf_float(ih->file, ih->buffer, 149 | (sf_count_t)ih->file_info.samplerate); 150 | } 151 | 152 | static void 153 | sndfile_free_buffer(struct input_handle *ih) 154 | { 155 | free(ih->buffer); 156 | ih->buffer = NULL; 157 | } 158 | 159 | static void 160 | sndfile_close_file(struct input_handle *ih) 161 | { 162 | if (sf_close(ih->file)) { 163 | fprintf(stderr, "Could not close input file!\n"); 164 | } 165 | ih->file = NULL; 166 | } 167 | 168 | static int 169 | sndfile_init_library(void) 170 | { 171 | return 0; 172 | } 173 | 174 | static void 175 | sndfile_exit_library(void) 176 | { 177 | } 178 | 179 | G_MODULE_EXPORT struct input_ops ip_ops = { sndfile_get_channels, 180 | sndfile_get_samplerate, sndfile_get_buffer, sndfile_handle_init, 181 | sndfile_handle_destroy, sndfile_open_file, sndfile_set_channel_map, 182 | sndfile_allocate_buffer, sndfile_get_total_frames, sndfile_read_frames, 183 | sndfile_free_buffer, sndfile_close_file, sndfile_init_library, 184 | sndfile_exit_library }; 185 | 186 | G_MODULE_EXPORT char const *ip_exts[] = { "wav", "flac", "ogg", "oga", "w64", 187 | NULL }; 188 | -------------------------------------------------------------------------------- /scanner/inputaudio/input.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include "input.h" 4 | 5 | #include 6 | #include 7 | 8 | static char const *plugin_names[] = { "input_ffmpeg", "input_sndfile", NULL }; 9 | 10 | static char const *plugin_search_dirs[] = { ".", "r128", "", 11 | NULL, /* = g_path_get_dirname(av0); */ 12 | NULL }; 13 | 14 | static GSList *g_modules; 15 | static GSList *plugin_ops; /*struct input_ops* ops;*/ 16 | static GSList *plugin_exts; 17 | 18 | extern int verbose; 19 | static int plugin_forced; 20 | 21 | static void 22 | search_module_in_paths(char const *plugin, GModule **module, 23 | char const *const *search_dir) 24 | { 25 | int search_dir_index = 0; 26 | while (!*module && search_dir[search_dir_index]) { 27 | char *path = g_module_build_path(search_dir[search_dir_index], 28 | plugin); 29 | *module = g_module_open(path, 30 | G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); 31 | g_free(path); 32 | ++search_dir_index; 33 | } 34 | } 35 | 36 | int 37 | input_init(char *exe_name, char const *forced_plugin) 38 | { 39 | int plugin_found = 0; 40 | char const **cur_plugin_name = plugin_names; 41 | struct input_ops *ops; 42 | char **exts; 43 | GModule *module; 44 | char *exe_dir; 45 | char const *env_path; 46 | char **env_path_split; 47 | char **it; 48 | 49 | exe_dir = g_path_get_dirname(exe_name); 50 | plugin_search_dirs[3] = exe_dir; 51 | 52 | env_path = g_getenv("PATH"); 53 | env_path_split = g_strsplit(env_path, G_SEARCHPATH_SEPARATOR_S, 0); 54 | for (it = env_path_split; *it; ++it) { 55 | char *r128_path = g_build_filename(*it, "r128", NULL); 56 | g_free(*it); 57 | *it = r128_path; 58 | } 59 | 60 | if (forced_plugin) { 61 | plugin_forced = 1; 62 | } 63 | /* Load plugins */ 64 | while (*cur_plugin_name) { 65 | if (forced_plugin && 66 | strcmp(forced_plugin, (*cur_plugin_name) + 6) != 0) { 67 | ++cur_plugin_name; 68 | continue; 69 | } 70 | ops = NULL; 71 | exts = NULL; 72 | module = NULL; 73 | search_module_in_paths(*cur_plugin_name, &module, 74 | plugin_search_dirs); 75 | search_module_in_paths(*cur_plugin_name, &module, 76 | (char const *const *)env_path_split); 77 | if (!module) { 78 | /* fprintf(stderr, "%s\n", g_module_error()); */ 79 | } else { 80 | if (!g_module_symbol(module, "ip_ops", 81 | (gpointer *)&ops)) { 82 | fprintf(stderr, "%s: %s\n", *cur_plugin_name, 83 | g_module_error()); 84 | } 85 | if (!g_module_symbol(module, "ip_exts", 86 | (gpointer *)&exts)) { 87 | fprintf(stderr, "%s: %s\n", *cur_plugin_name, 88 | g_module_error()); 89 | } 90 | } 91 | if (ops) { 92 | if (verbose) { 93 | fprintf(stderr, "found plugin %s\n", 94 | *cur_plugin_name); 95 | } 96 | ops->init_library(); 97 | plugin_found = 1; 98 | } 99 | g_modules = g_slist_append(g_modules, module); 100 | plugin_ops = g_slist_append(plugin_ops, ops); 101 | plugin_exts = g_slist_append(plugin_exts, exts); 102 | ++cur_plugin_name; 103 | } 104 | 105 | g_free(exe_dir); 106 | g_strfreev(env_path_split); 107 | if (!plugin_found) { 108 | fprintf(stderr, "Warning: no plugins found!\n"); 109 | return 1; 110 | } 111 | return 0; 112 | } 113 | 114 | int 115 | input_deinit(void) 116 | { 117 | /* unload plugins */ 118 | GSList *ops = plugin_ops; 119 | GSList *modules = g_modules; 120 | 121 | while (ops && modules) { 122 | if (ops->data && modules->data) { 123 | ((struct input_ops *)ops->data)->exit_library(); 124 | if (!g_module_close((GModule *)modules->data)) { 125 | fprintf(stderr, "%s\n", g_module_error()); 126 | } 127 | } 128 | ops = g_slist_next(ops); 129 | modules = g_slist_next(modules); 130 | } 131 | g_slist_free(g_modules); 132 | g_slist_free(plugin_ops); 133 | g_slist_free(plugin_exts); 134 | return 0; 135 | } 136 | 137 | struct input_ops * 138 | input_get_ops(char const *filename) 139 | { 140 | static char empty[] = { '\0' }; 141 | GSList *ops = plugin_ops; 142 | GSList *exts = plugin_exts; 143 | char *filename_ext = strrchr(filename, '.'); 144 | 145 | if (filename_ext) { 146 | ++filename_ext; 147 | } else { 148 | filename_ext = &empty[0]; 149 | } 150 | while (ops && exts) { 151 | if (ops->data && exts->data) { 152 | char const **cur_exts = exts->data; 153 | if (!(*cur_exts)) { 154 | return (struct input_ops *)ops->data; 155 | } 156 | while (*cur_exts) { 157 | if (!g_ascii_strcasecmp(filename_ext, 158 | *cur_exts) || 159 | plugin_forced) { 160 | return (struct input_ops *)ops->data; 161 | } 162 | ++cur_exts; 163 | } 164 | } 165 | ops = g_slist_next(ops); 166 | exts = g_slist_next(exts); 167 | } 168 | return NULL; 169 | } 170 | -------------------------------------------------------------------------------- /scanner/scanner.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #ifdef G_OS_WIN32 4 | #include 5 | #endif 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "filetree.h" 14 | #include "input.h" 15 | #include "parse_args.h" 16 | 17 | #include "scanner-scan.h" 18 | #ifdef USE_TAGLIB 19 | #include "scanner-tag.h" 20 | #endif 21 | #include "scanner-common.h" 22 | #include "scanner-dump.h" 23 | 24 | /* knobs: USE_TAGLIB, USE_SNDFILE */ 25 | 26 | 27 | static void 28 | print_help(void) 29 | { 30 | printf( 31 | "Usage: loudness scan|tag|dump|--version [OPTION...] [FILE|DIRECTORY]...\n"); 32 | printf("\n"); 33 | printf( 34 | "`loudness' scans audio files according to the EBU R128 standard. It can output\n"); 35 | #ifdef USE_TAGLIB 36 | printf( 37 | "loudness and peak information, write it to ReplayGain conformant tags, or dump\n"); 38 | printf( 39 | "momentary/shortterm/integrated loudness in fixed intervals to the console.\n"); 40 | #else 41 | printf( 42 | "loudness and peak information, or dump momentary/shortterm/integrated loudness\n"); 43 | printf("in fixed intervals to the console.\n"); 44 | #endif 45 | printf("\n"); 46 | printf("Examples:\n"); 47 | printf( 48 | " loudness scan foo.wav # Scans foo.wav and writes information to stdout.\n"); 49 | #ifdef USE_TAGLIB 50 | printf( 51 | " loudness tag -r bar/ # Tag all files in foo as one album per subfolder.\n"); 52 | #endif 53 | printf( 54 | " loudness dump -m 1.0 a.wav # Each second, write momentary loudness to stdout.\n"); 55 | printf( 56 | " loudness --version # Write library and scanner version to stdout.\n"); 57 | printf("\n"); 58 | printf(" Main operation mode:\n"); 59 | printf( 60 | " scan output loudness and peak information\n"); 61 | #ifdef USE_TAGLIB 62 | printf( 63 | " tag tag files with ReplayGain conformant tags\n"); 64 | #endif 65 | printf( 66 | " dump output momentary/shortterm/integrated loudness\n"); 67 | printf(" in fixed intervals\n"); 68 | printf("\n"); 69 | printf(" Global options:\n"); 70 | printf( 71 | " -r, --recursive recursively scan files in subdirectories\n"); 72 | printf( 73 | " -L, --follow-symlinks follow symbolic links (*nix only)\n"); 74 | printf(" -v, --verbose verbose error output\n"); 75 | printf( 76 | " --histogram use histogram loudness algorithm (needs less RAM)\n"); 77 | printf( 78 | " --no-sort do not sort command line arguments alphabetically\n"); 79 | printf( 80 | " --force-plugin=PLUGIN force input plugin; PLUGIN is one of:\n"); 81 | printf(/**/ 82 | " sndfile, ffmpeg\n"); 83 | #ifdef USE_SNDFILE 84 | printf( 85 | " --decode=FILE decode one input to FILE (32 bit float WAV,\n"); 86 | printf( 87 | " only available in scan and tag mode)\n"); 88 | #endif 89 | printf("\n"); 90 | printf(" Scan options:\n"); 91 | printf( 92 | " -l, --lra calculate loudness range in LRA\n"); 93 | printf( 94 | " -p, --peak=sample|true|dbtp|all -p sample: sample peak (float value)\n"); 95 | printf( 96 | " -p true: true peak (float value)\n"); 97 | printf( 98 | " -p dbtp: true peak (dB True Peak)\n"); 99 | printf( 100 | " -p all: show all peak values\n"); 101 | printf("\n"); 102 | #ifdef USE_TAGLIB 103 | printf(" Tag options:\n"); 104 | printf( 105 | " -t, --track write only track gain (album gain is default)\n"); 106 | printf( 107 | " -n, --dry-run perform a trial run with no changes made\n"); 108 | printf( 109 | " --incremental skip files that are already tagged\n"); 110 | printf( 111 | " --force-as-album treat all given files as one album\n"); 112 | printf( 113 | " --opus-vorbisgain-compat for compatibility with older software,\n"); 114 | printf( 115 | " write 'REPLAYGAIN_*' tags to Opus files\n"); 116 | printf(/**/ 117 | " in addition to 'R128_*' tags\n"); 118 | printf( 119 | " --opus-header-gain=ABS_DB|r128[,track][,offset=DB]|rg[,track][,offset=DB]\n"); 120 | printf(/**/ 121 | " write specific values into Opus header gain field,\n"); 122 | printf(/**/ 123 | " adjusting 'R128_*'/'REPLAYGAIN_*' tag values\n"); 124 | printf(/**/ 125 | " as needed\n"); 126 | printf(/**/ 127 | " examples:\n"); 128 | printf(/**/ 129 | " --opus-header-gain=r128\n"); 130 | printf(/**/ 131 | " R128 album/track gain (depending\n"); 132 | printf(/**/ 133 | " on '-t' parameter) in gain field,\n"); 134 | printf(/**/ 135 | " this setting is the default\n"); 136 | printf(/**/ 137 | " --opus-header-gain=r128,track\n"); 138 | printf(/**/ 139 | " R128 track gain in gain field\n"); 140 | printf(/**/ 141 | " --opus-header-gain=rg\n"); 142 | printf(/**/ 143 | " ReplayGain compatible album/track gain\n"); 144 | printf(/**/ 145 | " (depending on '-t' parameter) in\n"); 146 | printf(/**/ 147 | " gain field\n"); 148 | printf(/**/ 149 | " --opus-header-gain=rg,offset=3\n"); 150 | printf(/**/ 151 | " ReplayGain compatible album/track gain\n"); 152 | printf(/**/ 153 | " (depending on '-t' parameter) in\n"); 154 | printf(/**/ 155 | " gain field, plus 3dB\n"); 156 | printf(/**/ 157 | " --opus-header-gain=0: 0dB in gain field\n"); 158 | printf(/**/ 159 | " --opus-header-gain=2: 2dB in gain field\n"); 160 | printf(/**/ 161 | " --opus-header-gain=-3: -3dB in gain field\n"); 162 | printf("\n"); 163 | #endif 164 | printf(" Dump options:\n"); 165 | printf( 166 | " -m, --momentary=INTERVAL print momentary loudness every INTERVAL seconds\n"); 167 | printf( 168 | " -s, --shortterm=INTERVAL print shortterm loudness every INTERVAL seconds\n"); 169 | printf( 170 | " -i, --integrated=INTERVAL print integrated loudness every INTERVAL seconds\n"); 171 | } 172 | 173 | static gboolean recursive = FALSE; 174 | static gboolean follow_symlinks = FALSE; 175 | static gboolean no_sort = FALSE; 176 | gboolean verbose = FALSE; 177 | gboolean histogram = FALSE; 178 | static gchar *forced_plugin = NULL; 179 | gchar *decode_to_file = NULL; 180 | static gboolean help = FALSE; 181 | 182 | static GOptionEntry entries[] = { { "recursive", 'r', 0, G_OPTION_ARG_NONE, 183 | &recursive, NULL, NULL }, 184 | { "follow-symlinks", 'L', 0, G_OPTION_ARG_NONE, &follow_symlinks, NULL, 185 | NULL }, 186 | { "no-sort", 0, 0, G_OPTION_ARG_NONE, &no_sort, NULL, NULL }, 187 | { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, NULL, NULL }, 188 | { "histogram", 0, 0, G_OPTION_ARG_NONE, &histogram, NULL, NULL }, 189 | { "force-plugin", 0, 0, G_OPTION_ARG_STRING, &forced_plugin, NULL, 190 | NULL }, 191 | #ifdef USE_SNDFILE 192 | { "decode", 0, 0, G_OPTION_ARG_STRING, &decode_to_file, NULL, NULL }, 193 | #endif 194 | { "help", 'h', 0, G_OPTION_ARG_NONE, &help, NULL, NULL }, 195 | { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, 0 } }; 196 | 197 | enum modes { LOUDNESS_MODE_SCAN, LOUDNESS_MODE_TAG, LOUDNESS_MODE_DUMP }; 198 | 199 | int 200 | main(int argc, char *argv[]) 201 | { 202 | GSList *errors = NULL; 203 | GSList *files = NULL; 204 | Filetree tree; 205 | int mode = 0; 206 | int mode_parsed = FALSE; 207 | int ret = 0; 208 | 209 | if (parse_global_args(&argc, &argv, entries, TRUE) || argc < 2 || 210 | help) { 211 | print_help(); 212 | exit(EXIT_FAILURE); 213 | } 214 | if (!strcmp(argv[1], "scan")) { 215 | mode = LOUDNESS_MODE_SCAN; 216 | mode_parsed = loudness_scan_parse(&argc, &argv); 217 | #ifdef USE_TAGLIB 218 | } else if (!strcmp(argv[1], "tag")) { 219 | mode = LOUDNESS_MODE_TAG; 220 | mode_parsed = loudness_tag_parse(&argc, &argv); 221 | #endif 222 | } else if (!strcmp(argv[1], "dump")) { 223 | mode = LOUDNESS_MODE_DUMP; 224 | mode_parsed = loudness_dump_parse(&argc, &argv); 225 | } else if (!strcmp(argv[1], "--version")) { 226 | print_version(); 227 | exit(EXIT_SUCCESS); 228 | } else { 229 | fprintf(stderr, "Unknown mode '%s'\n", argv[1]); 230 | } 231 | if (!mode_parsed) { 232 | exit(EXIT_FAILURE); 233 | } 234 | if (decode_to_file && argc - 1 != 1) { 235 | fprintf(stderr, "Cannot decode more than one file\n"); 236 | exit(EXIT_FAILURE); 237 | } 238 | 239 | input_init(argv[0], forced_plugin); 240 | scanner_init_common(); 241 | 242 | setlocale(LC_COLLATE, ""); 243 | setlocale(LC_CTYPE, ""); 244 | tree = filetree_init(&argv[1], (size_t)(argc - 1), recursive, 245 | follow_symlinks, no_sort, &errors); 246 | 247 | g_slist_foreach(errors, filetree_print_error, &verbose); 248 | g_slist_foreach(errors, filetree_free_error, NULL); 249 | g_slist_free(errors); 250 | 251 | filetree_file_list(tree, &files); 252 | filetree_remove_common_prefix(files); 253 | 254 | switch (mode) { 255 | case LOUDNESS_MODE_SCAN: 256 | loudness_scan(files); 257 | break; 258 | #ifdef USE_TAGLIB 259 | case LOUDNESS_MODE_TAG: 260 | ret = loudness_tag(files); 261 | break; 262 | #endif 263 | case LOUDNESS_MODE_DUMP: 264 | ret = loudness_dump(files); 265 | break; 266 | } 267 | 268 | g_slist_foreach(files, filetree_free_list_entry, NULL); 269 | g_slist_free(files); 270 | 271 | filetree_destroy(tree); 272 | input_deinit(); 273 | g_free(forced_plugin); 274 | 275 | return ret; 276 | } 277 | -------------------------------------------------------------------------------- /scanner/scanner-tag/scanner-tag.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include "scanner-tag.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "nproc.h" 11 | #include "parse_args.h" 12 | #include "rgtag.h" 13 | #include "scanner-common.h" 14 | 15 | static struct file_data empty; 16 | 17 | extern gboolean verbose; 18 | extern gboolean histogram; 19 | 20 | static gboolean track = FALSE; 21 | static gboolean dry_run = FALSE; 22 | static gboolean incremental_tagging = FALSE; 23 | static gboolean force_as_album = FALSE; 24 | 25 | static gboolean opus_vorbisgain_compat = FALSE; 26 | static OpusTagInfo opus_tag_info = { 27 | .opus_gain_reference = OPUS_GAIN_REFERENCE_R128, 28 | }; 29 | 30 | extern gchar *decode_to_file; 31 | 32 | static gboolean 33 | parse_opus_header_gain(gchar const *option_name, gchar const *value, 34 | gpointer data, GError **error) 35 | { 36 | gboolean rc = FALSE; 37 | 38 | (void)option_name; 39 | (void)data; 40 | (void)error; 41 | 42 | gchar **elements = g_strsplit(value, ",", -1); 43 | if (elements[0] == NULL) { 44 | goto out; 45 | } 46 | 47 | bool is_rg; 48 | if (strcmp(elements[0], "r128") == 0) { 49 | opus_tag_info.opus_gain_reference = OPUS_GAIN_REFERENCE_R128; 50 | is_rg = false; 51 | } else if (strcmp(elements[0], "rg") == 0) { 52 | opus_tag_info.opus_gain_reference = OPUS_GAIN_REFERENCE_R128; 53 | is_rg = true; 54 | } else { 55 | gchar *endptr; 56 | gdouble offset = g_ascii_strtod(elements[0], &endptr); 57 | if (endptr == elements[0] || *endptr != '\0' || errno != 0 || 58 | !isfinite(offset)) { 59 | goto out; 60 | } 61 | opus_tag_info.opus_gain_reference = 62 | OPUS_GAIN_REFERENCE_ABSOLUTE; 63 | opus_tag_info.offset = offset; 64 | } 65 | 66 | bool got_track = false; 67 | bool got_offset = false; 68 | for (gchar **it = elements + 1; *it != NULL; ++it) { 69 | if (opus_tag_info.opus_gain_reference == 70 | OPUS_GAIN_REFERENCE_ABSOLUTE) { 71 | goto out; 72 | } 73 | 74 | if (strcmp(*it, "track") == 0) { 75 | if (got_track) { 76 | goto out; 77 | } 78 | got_track = true; 79 | 80 | opus_tag_info.is_track = true; 81 | 82 | } else if (strncmp(*it, "offset=", 7) == 0) { 83 | if (got_offset) { 84 | goto out; 85 | } 86 | got_offset = true; 87 | 88 | gchar *startptr = *it + 7; 89 | gchar *endptr; 90 | gdouble offset = g_ascii_strtod(startptr, &endptr); 91 | if (endptr == startptr || *endptr != '\0' || 92 | errno != 0 || !isfinite(offset)) { 93 | goto out; 94 | } 95 | opus_tag_info.offset = offset; 96 | 97 | } else { 98 | goto out; 99 | } 100 | } 101 | 102 | if (opus_tag_info.opus_gain_reference == OPUS_GAIN_REFERENCE_R128 && 103 | is_rg) { 104 | opus_tag_info.offset += 5.0; 105 | } 106 | 107 | rc = TRUE; 108 | 109 | out: 110 | g_strfreev(elements); 111 | return rc; 112 | } 113 | 114 | static GOptionEntry entries[] = { 115 | { "track", 't', 0, G_OPTION_ARG_NONE, /**/ 116 | &track, NULL, NULL }, 117 | { "dry-run", 'n', 0, G_OPTION_ARG_NONE, /**/ 118 | &dry_run, NULL, NULL }, 119 | { "incremental", 0, 0, G_OPTION_ARG_NONE, /**/ 120 | &incremental_tagging, NULL, NULL }, 121 | { "force-as-album", 0, 0, G_OPTION_ARG_NONE, /**/ 122 | &force_as_album, NULL, NULL }, 123 | { "opus-vorbisgain-compat", 0, 0, G_OPTION_ARG_NONE, /**/ 124 | &opus_vorbisgain_compat, NULL, NULL }, 125 | { "opus-header-gain", 0, 0, G_OPTION_ARG_CALLBACK, /**/ 126 | parse_opus_header_gain, NULL, NULL }, 127 | { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, 0 }, 128 | }; 129 | 130 | static void 131 | fill_album_data(struct filename_list_node *fln, double const *album_data) 132 | { 133 | struct file_data *fd = (struct file_data *)fln->d; 134 | 135 | fd->gain_album = album_data[0]; 136 | fd->peak_album = album_data[1]; 137 | } 138 | 139 | static gchar *current_dir; 140 | static GSList *files_in_current_dir; 141 | 142 | static void 143 | calculate_album_gain_and_peak_last_dir(void) 144 | { 145 | double album_data[] = { 0.0, 0.0 }; 146 | GPtrArray *states = g_ptr_array_new(); 147 | struct file_data result; 148 | memcpy(&result, &empty, sizeof empty); 149 | 150 | files_in_current_dir = g_slist_reverse(files_in_current_dir); 151 | g_slist_foreach(files_in_current_dir, (GFunc)get_state, states); 152 | ebur128_loudness_global_multiple((ebur128_state **)states->pdata, 153 | states->len, &album_data[0]); 154 | album_data[0] = RG_REFERENCE_LEVEL - album_data[0]; 155 | g_slist_foreach(files_in_current_dir, (GFunc)get_max_peaks, &result); 156 | album_data[1] = result.peak; 157 | g_slist_foreach(files_in_current_dir, (GFunc)fill_album_data, 158 | album_data); 159 | 160 | g_ptr_array_free(states, TRUE); 161 | 162 | g_free(current_dir); 163 | current_dir = NULL; 164 | g_slist_free(files_in_current_dir); 165 | files_in_current_dir = NULL; 166 | } 167 | 168 | static void 169 | calculate_album_gain_and_peak(struct filename_list_node *fln, gpointer unused) 170 | { 171 | gchar *dirname; 172 | 173 | (void)unused; 174 | dirname = g_path_get_dirname(fln->fr->raw); 175 | if (!current_dir) { 176 | current_dir = g_strdup(dirname); 177 | } 178 | if (!strcmp(current_dir, dirname)) { 179 | files_in_current_dir = g_slist_prepend(files_in_current_dir, 180 | fln); 181 | } else { 182 | calculate_album_gain_and_peak_last_dir(); 183 | current_dir = g_strdup(dirname); 184 | files_in_current_dir = g_slist_prepend(files_in_current_dir, 185 | fln); 186 | } 187 | g_free(dirname); 188 | } 189 | 190 | /* must g_free basename and filename */ 191 | static void 192 | get_filename_and_extension(struct filename_list_node *fln, char **basename, 193 | char **extension, char **filename) 194 | { 195 | *basename = g_path_get_basename(fln->fr->raw); 196 | *extension = strrchr(*basename, '.'); 197 | if (*extension) { 198 | ++*extension; 199 | } else { 200 | *extension = ""; 201 | } 202 | #ifdef G_OS_WIN32 203 | *filename = (char *)g_utf8_to_utf16(fln->fr->raw, -1, NULL, NULL, NULL); 204 | #else 205 | *filename = g_strdup(fln->fr->raw); 206 | #endif 207 | } 208 | 209 | static void 210 | print_file_data(struct filename_list_node *fln, gpointer unused) 211 | { 212 | struct file_data *fd = (struct file_data *)fln->d; 213 | 214 | (void)unused; 215 | if (fd->scanned) { 216 | struct gain_data gd = { 217 | RG_REFERENCE_LEVEL - fd->loudness, 218 | fd->peak, 219 | !track, 220 | fd->gain_album, 221 | fd->peak_album, 222 | }; 223 | 224 | char *basename; 225 | char *extension; 226 | char *filename; 227 | get_filename_and_extension(fln, &basename, &extension, 228 | &filename); 229 | 230 | clamp_gain_data(&gd); 231 | 232 | g_free(basename); 233 | g_free(filename); 234 | 235 | if (!track) { 236 | g_print("%7.2f dB, %7.2f dB, %10.6f, %10.6f", 237 | gd.album_gain, gd.track_gain, gd.album_peak, 238 | gd.track_peak); 239 | } else { 240 | g_print("%7.2f dB, %10.6f", gd.track_gain, 241 | gd.track_peak); 242 | } 243 | if (fln->fr->display[0]) { 244 | g_print(", "); 245 | print_utf8_string(fln->fr->display); 246 | } 247 | putchar('\n'); 248 | } 249 | } 250 | 251 | static int tag_output_state = 0; 252 | void 253 | tag_file(struct filename_list_node *fln, int *ret) 254 | { 255 | struct file_data *fd = (struct file_data *)fln->d; 256 | if (fd->scanned) { 257 | int error; 258 | 259 | struct gain_data gd = { 260 | RG_REFERENCE_LEVEL - fd->loudness, 261 | fd->peak, 262 | !track, 263 | fd->gain_album, 264 | fd->peak_album, 265 | }; 266 | 267 | char *basename; 268 | char *extension; 269 | char *filename; 270 | get_filename_and_extension(fln, &basename, &extension, 271 | &filename); 272 | 273 | error = set_rg_info(filename, extension, &gd, &opus_tag_info); 274 | if (error) { 275 | if (tag_output_state == 0) { 276 | fflush(stderr); 277 | fputc('\n', stderr); 278 | tag_output_state = 1; 279 | } 280 | g_message("Error tagging %s", fln->fr->display); 281 | *ret = EXIT_FAILURE; 282 | } else { 283 | fputc('.', stderr); 284 | tag_output_state = 0; 285 | } 286 | 287 | g_free(basename); 288 | g_free(filename); 289 | } 290 | } 291 | 292 | int 293 | scan_files(GSList *files) 294 | { 295 | struct scan_opts opts = { FALSE, "sample", histogram, TRUE, 296 | decode_to_file }; 297 | int do_scan = 0; 298 | 299 | g_slist_foreach(files, (GFunc)init_and_get_number_of_frames, &do_scan); 300 | if (do_scan) { 301 | 302 | process_files(files, &opts); 303 | 304 | if (!track) { 305 | if (force_as_album) { 306 | files_in_current_dir = g_slist_copy(files); 307 | } else { 308 | g_slist_foreach(files, 309 | (GFunc)calculate_album_gain_and_peak, NULL); 310 | } 311 | calculate_album_gain_and_peak_last_dir(); 312 | } 313 | 314 | clear_line(); 315 | if (!track) { 316 | fprintf(stderr, 317 | "Album gain, Track gain, Album peak, Track peak\n"); 318 | } else { 319 | fprintf(stderr, "Track gain, Track peak\n"); 320 | } 321 | g_slist_foreach(files, (GFunc)print_file_data, NULL); 322 | } 323 | g_slist_foreach(files, (GFunc)destroy_state, NULL); 324 | scanner_reset_common(); 325 | 326 | return do_scan; 327 | } 328 | 329 | int 330 | tag_files(GSList *files) 331 | { 332 | int ret = 0; 333 | 334 | fprintf(stderr, "Tagging"); 335 | g_slist_foreach(files, (GFunc)tag_file, &ret); 336 | if (ret == 0) { 337 | fprintf(stderr, " Success!"); 338 | } 339 | fputc('\n', stderr); 340 | 341 | return ret; 342 | } 343 | 344 | static void 345 | append_to_untagged_list(struct filename_list_node *fln, GSList **ret) 346 | { 347 | char *basename; 348 | char *extension; 349 | char *filename; 350 | get_filename_and_extension(fln, &basename, &extension, &filename); 351 | 352 | if (!has_rg_info(filename, extension, &opus_tag_info)) { 353 | *ret = g_slist_prepend(*ret, fln); 354 | } 355 | 356 | g_free(basename); 357 | g_free(filename); 358 | } 359 | 360 | int 361 | loudness_tag(GSList *files) 362 | { 363 | if (incremental_tagging) { 364 | GSList *untagged_files = NULL; 365 | g_slist_foreach(files, (GFunc)append_to_untagged_list, 366 | &untagged_files); 367 | untagged_files = g_slist_reverse(untagged_files); 368 | 369 | files = untagged_files; 370 | } 371 | 372 | if (scan_files(files) && !dry_run) { 373 | return tag_files(files); 374 | } 375 | return 0; 376 | } 377 | 378 | gboolean 379 | loudness_tag_parse(int *argc, char **argv[]) 380 | { 381 | gboolean success = parse_mode_args(argc, argv, entries); 382 | if (!success) { 383 | if (*argc == 1) { 384 | fprintf(stderr, "Missing arguments\n"); 385 | } 386 | return FALSE; 387 | } 388 | 389 | opus_tag_info.vorbisgain_compat = opus_vorbisgain_compat != FALSE; 390 | 391 | return TRUE; 392 | } 393 | -------------------------------------------------------------------------------- /scanner/scanner-common/scanner-common.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include "nproc.h" 4 | #include "scanner-common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /* knobs: USE_SNDFILE */ 11 | 12 | #ifdef USE_SNDFILE 13 | #include 14 | #endif 15 | 16 | extern gboolean verbose; 17 | 18 | static struct file_data empty; 19 | 20 | GMutex progress_mutex; 21 | GCond progress_cond; 22 | guint64 elapsed_frames = 0; 23 | guint64 total_frames = 0; 24 | 25 | void 26 | scanner_init_common(void) 27 | { 28 | total_frames = 0; 29 | elapsed_frames = 0; 30 | } 31 | 32 | void 33 | scanner_reset_common(void) 34 | { 35 | g_mutex_lock(&progress_mutex); 36 | total_frames = elapsed_frames = 0; 37 | g_cond_broadcast(&progress_cond); 38 | g_mutex_unlock(&progress_mutex); 39 | } 40 | 41 | int 42 | open_plugin(char const *raw, char const *display, struct input_ops **ops, 43 | struct input_handle **ih) 44 | { 45 | int result; 46 | 47 | *ops = input_get_ops(raw); 48 | if (!(*ops)) { 49 | if (verbose) { 50 | fprintf(stderr, "No plugin found for file '%s'\n", 51 | display); 52 | } 53 | return 1; 54 | } 55 | *ih = (*ops)->handle_init(); 56 | 57 | result = (*ops)->open_file(*ih, raw); 58 | if (result) { 59 | if (verbose) { 60 | fprintf(stderr, "Error opening file '%s'\n", display); 61 | } 62 | return 1; 63 | } 64 | return 0; 65 | } 66 | 67 | void 68 | init_and_get_number_of_frames(struct filename_list_node *fln, int *do_scan) 69 | { 70 | struct file_data *fd; 71 | 72 | struct input_ops *ops = NULL; 73 | struct input_handle *ih = NULL; 74 | int result; 75 | 76 | fln->d = g_malloc(sizeof(struct file_data)); 77 | memcpy(fln->d, &empty, sizeof empty); 78 | fd = (struct file_data *)fln->d; 79 | 80 | result = open_plugin(fln->fr->raw, fln->fr->display, &ops, &ih); 81 | if (result) { 82 | goto free; 83 | } 84 | 85 | *do_scan = TRUE; 86 | fd->number_of_frames = ops->get_total_frames(ih); 87 | g_mutex_lock(&progress_mutex); 88 | total_frames += fd->number_of_frames; 89 | g_cond_broadcast(&progress_cond); 90 | g_mutex_unlock(&progress_mutex); 91 | 92 | free: 93 | if (!result) { 94 | ops->close_file(ih); 95 | } 96 | if (ih) { 97 | ops->handle_destroy(&ih); 98 | } 99 | } 100 | 101 | void 102 | init_state_and_scan_work_item(struct filename_list_node *fln, 103 | struct scan_opts *opts) 104 | { 105 | struct file_data *fd = (struct file_data *)fln->d; 106 | 107 | struct input_ops *ops = NULL; 108 | struct input_handle *ih = NULL; 109 | int r128_mode = EBUR128_MODE_I; 110 | unsigned int i; 111 | int *channel_map; 112 | 113 | int result; 114 | float *buffer = NULL; 115 | size_t nr_frames_read; 116 | 117 | #ifdef USE_SNDFILE 118 | SNDFILE *outfile = NULL; 119 | #endif 120 | 121 | result = open_plugin(fln->fr->raw, fln->fr->display, &ops, &ih); 122 | if (result) { 123 | g_mutex_lock(&progress_mutex); 124 | elapsed_frames += fd->number_of_frames; 125 | g_cond_broadcast(&progress_cond); 126 | g_mutex_unlock(&progress_mutex); 127 | goto free; 128 | } 129 | 130 | if (opts->lra) { 131 | r128_mode |= EBUR128_MODE_LRA; 132 | } 133 | if (opts->peak) { 134 | if (!strcmp(opts->peak, "sample") || 135 | !strcmp(opts->peak, "all")) { 136 | r128_mode |= EBUR128_MODE_SAMPLE_PEAK; 137 | } 138 | if (!strcmp(opts->peak, "true") || 139 | !strcmp(opts->peak, "dbtp") || /**/ 140 | !strcmp(opts->peak, "all")) { 141 | r128_mode |= EBUR128_MODE_TRUE_PEAK; 142 | } 143 | } 144 | if (opts->histogram) { 145 | r128_mode |= EBUR128_MODE_HISTOGRAM; 146 | } 147 | 148 | fd->st = ebur128_init(ops->get_channels(ih), ops->get_samplerate(ih), 149 | r128_mode); 150 | 151 | channel_map = g_malloc(fd->st->channels * sizeof(int)); 152 | if (!ops->set_channel_map(ih, channel_map)) { 153 | for (i = 0; i < fd->st->channels; ++i) { 154 | ebur128_set_channel(fd->st, i, channel_map[i]); 155 | } 156 | } 157 | free(channel_map); 158 | 159 | if (fd->st->channels == 1 && opts->force_dual_mono) { 160 | ebur128_set_channel(fd->st, 0, EBUR128_DUAL_MONO); 161 | } 162 | 163 | result = ops->allocate_buffer(ih); 164 | if (result) { 165 | abort(); 166 | } 167 | buffer = ops->get_buffer(ih); 168 | 169 | #ifdef USE_SNDFILE 170 | if (opts->decode_file) { 171 | SF_INFO sf_info; 172 | memset(&sf_info, '\0', sizeof sf_info); 173 | sf_info.samplerate = (int)fd->st->samplerate; 174 | sf_info.channels = (int)fd->st->channels; 175 | sf_info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; 176 | outfile = sf_open(opts->decode_file, SFM_WRITE, &sf_info); 177 | if (!outfile) { 178 | fprintf(stderr, "output file could not be opened\n"); 179 | exit(EXIT_FAILURE); 180 | } 181 | } 182 | #endif 183 | 184 | while ((nr_frames_read = ops->read_frames(ih))) { 185 | g_mutex_lock(&progress_mutex); 186 | elapsed_frames += nr_frames_read; 187 | g_cond_broadcast(&progress_cond); 188 | g_mutex_unlock(&progress_mutex); 189 | fd->number_of_elapsed_frames += nr_frames_read; 190 | result = ebur128_add_frames_float(fd->st, buffer, 191 | nr_frames_read); 192 | #ifdef USE_SNDFILE 193 | if (opts->decode_file) { 194 | if (sf_writef_float(outfile, buffer, 195 | (sf_count_t)nr_frames_read) != 196 | (sf_count_t)nr_frames_read) { 197 | sf_perror(outfile); 198 | } 199 | } 200 | #endif 201 | if (result) { 202 | abort(); 203 | } 204 | } 205 | 206 | #ifdef USE_SNDFILE 207 | if (opts->decode_file) { 208 | sf_close(outfile); 209 | } 210 | #endif 211 | 212 | if (fd->number_of_elapsed_frames != fd->number_of_frames) { 213 | if (verbose) { 214 | fprintf(stderr, 215 | "Warning: Could not read full file" 216 | " or determine right length for file %s: " 217 | "Expected: %lu Got: %lu", 218 | fln->fr->display, fd->number_of_frames, 219 | fd->number_of_elapsed_frames); 220 | } 221 | g_mutex_lock(&progress_mutex); 222 | total_frames = total_frames + fd->number_of_elapsed_frames - 223 | fd->number_of_frames; 224 | g_cond_broadcast(&progress_cond); 225 | g_mutex_unlock(&progress_mutex); 226 | } 227 | ebur128_loudness_global(fd->st, &fd->loudness); 228 | if (opts->lra) { 229 | result = ebur128_loudness_range(fd->st, &fd->lra); 230 | if (result) { 231 | abort(); 232 | } 233 | } 234 | 235 | if ((fd->st->mode & EBUR128_MODE_SAMPLE_PEAK) == 236 | EBUR128_MODE_SAMPLE_PEAK) { 237 | for (i = 0; i < fd->st->channels; ++i) { 238 | double sp; 239 | ebur128_sample_peak(fd->st, i, &sp); 240 | if (sp > fd->peak) { 241 | fd->peak = sp; 242 | } 243 | } 244 | } 245 | if ((fd->st->mode & EBUR128_MODE_TRUE_PEAK) == EBUR128_MODE_TRUE_PEAK) { 246 | for (i = 0; i < fd->st->channels; ++i) { 247 | double tp; 248 | ebur128_true_peak(fd->st, i, &tp); 249 | if (tp > fd->true_peak) { 250 | fd->true_peak = tp; 251 | } 252 | } 253 | } 254 | fd->scanned = TRUE; 255 | 256 | if (ih) { 257 | ops->free_buffer(ih); 258 | } 259 | free: 260 | if (!result) { 261 | ops->close_file(ih); 262 | } 263 | if (ih) { 264 | ops->handle_destroy(&ih); 265 | } 266 | } 267 | 268 | void 269 | init_state_and_scan(gpointer work_item, GThreadPool *pool) 270 | { 271 | g_thread_pool_push(pool, work_item, NULL); 272 | } 273 | 274 | void 275 | destroy_state(struct filename_list_node *fln, gpointer unused) 276 | { 277 | struct file_data *fd = (struct file_data *)fln->d; 278 | 279 | (void)unused; 280 | if (fd->st) { 281 | ebur128_destroy(&fd->st); 282 | } 283 | } 284 | 285 | void 286 | get_state(struct filename_list_node *fln, GPtrArray *states) 287 | { 288 | struct file_data *fd = (struct file_data *)fln->d; 289 | 290 | if (fd->scanned) { 291 | g_ptr_array_add(states, fd->st); 292 | } 293 | } 294 | 295 | void 296 | get_max_peaks(struct filename_list_node *fln, struct file_data *result) 297 | { 298 | struct file_data *fd = (struct file_data *)fln->d; 299 | 300 | if (fd->scanned) { 301 | if (fd->peak > result->peak) { 302 | result->peak = fd->peak; 303 | } 304 | if (fd->true_peak > result->true_peak) { 305 | result->true_peak = fd->true_peak; 306 | } 307 | } 308 | } 309 | 310 | static gpointer 311 | print_progress_bar(gpointer arg) 312 | { 313 | int percent; 314 | int bars; 315 | int i; 316 | static char progress_bar[81]; 317 | gint64 last_time = -1; 318 | 319 | int *started = arg; 320 | 321 | for (;;) { 322 | g_mutex_lock(&progress_mutex); 323 | // signal calling thread that we are ready 324 | if (!*started) { 325 | *started = 1; 326 | g_cond_broadcast(&progress_cond); 327 | } 328 | 329 | if (total_frames != elapsed_frames) { 330 | g_cond_wait(&progress_cond, &progress_mutex); 331 | } 332 | 333 | /* refresh progress bar at max 10 times per second */ 334 | gint64 current_time = g_get_monotonic_time(); 335 | if (last_time == -1 || current_time >= last_time + 100 * 1000 || 336 | total_frames == elapsed_frames) { 337 | last_time = current_time; 338 | } else { 339 | g_mutex_unlock(&progress_mutex); 340 | continue; 341 | } 342 | 343 | if (total_frames) { 344 | bars = (int)(elapsed_frames * G_GUINT64_CONSTANT(72) / 345 | total_frames); 346 | percent = (int)(elapsed_frames * 347 | G_GUINT64_CONSTANT(100) / total_frames); 348 | } else { 349 | bars = percent = 0; 350 | } 351 | bars = CLAMP(bars, 0, 72); 352 | percent = CLAMP(percent, 0, 100); 353 | progress_bar[0] = '['; 354 | for (i = 1; i <= bars; ++i) { 355 | progress_bar[i] = '#'; 356 | } 357 | for (; i < 73; ++i) { 358 | progress_bar[i] = ' '; 359 | } 360 | if (percent >= 0 && percent <= 100) { 361 | sprintf(&progress_bar[73], "] %3d%%", percent); 362 | } 363 | fprintf(stderr, "%s\r", progress_bar); 364 | if (total_frames == elapsed_frames) { 365 | g_mutex_unlock(&progress_mutex); 366 | break; 367 | } 368 | g_mutex_unlock(&progress_mutex); 369 | } 370 | return NULL; 371 | } 372 | 373 | void 374 | clear_line(void) 375 | { 376 | int i; 377 | for (i = 0; i < 80; ++i) { 378 | fputc(' ', stderr); 379 | } 380 | fputc('\r', stderr); 381 | } 382 | 383 | void 384 | process_files(GSList *files, struct scan_opts *opts) 385 | { 386 | GThreadPool *pool; 387 | GThread *progress_bar_thread; 388 | 389 | int started = 0; 390 | 391 | // Start the progress bar thread. It misuses progress_mutex and 392 | // progress_cond to signal when it is ready. 393 | g_mutex_lock(&progress_mutex); 394 | progress_bar_thread = g_thread_new(NULL, print_progress_bar, &started); 395 | while (!started) { 396 | g_cond_wait(&progress_cond, &progress_mutex); 397 | } 398 | g_mutex_unlock(&progress_mutex); 399 | 400 | pool = g_thread_pool_new((GFunc)init_state_and_scan_work_item, opts, 401 | nproc(), FALSE, NULL); 402 | g_slist_foreach(files, (GFunc)init_state_and_scan, pool); 403 | g_thread_pool_free(pool, FALSE, TRUE); 404 | g_thread_join(progress_bar_thread); 405 | } 406 | 407 | void 408 | print_version() 409 | { 410 | int lib_major; 411 | int lib_minor; 412 | int lib_patch; 413 | ebur128_get_version(&lib_major, &lib_minor, &lib_patch); 414 | printf("library version: %d.%d.%d\n", lib_major, lib_minor, lib_patch); 415 | printf("scanner version: %d.%d.%d\n", LOUDNESS_SCANNER_VERSION_MAJOR, 416 | LOUDNESS_SCANNER_VERSION_MINOR, LOUDNESS_SCANNER_VERSION_PATCH); 417 | } 418 | -------------------------------------------------------------------------------- /scanner/inputaudio/ffmpeg/input_ffmpeg.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include 4 | #if LIBAVCODEC_VERSION_MAJOR >= 53 5 | #if LIBAVUTIL_VERSION_MAJOR > 52 6 | #include 7 | #else 8 | #include 9 | #endif 10 | #endif 11 | #include 12 | 13 | #include "ebur128.h" 14 | #include "input.h" 15 | 16 | #ifndef AV_INPUT_BUFFER_PADDING_SIZE 17 | #define AV_INPUT_BUFFER_PADDING_SIZE FF_INPUT_BUFFER_PADDING_SIZE 18 | #endif 19 | 20 | #define BUFFER_SIZE (192000 + AV_INPUT_BUFFER_PADDING_SIZE) 21 | 22 | static GMutex ffmpeg_mutex; 23 | 24 | // TODO: Fix deprecated FFmpeg API calls. 25 | #pragma GCC diagnostic push 26 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 27 | 28 | struct input_handle { 29 | AVFormatContext *format_context; 30 | AVCodecContext *codec_context; 31 | AVCodec *codec; 32 | AVFrame *frame; 33 | AVPacket packet; 34 | AVPacket orig_packet; 35 | int audio_stream; 36 | int flushing; 37 | int got_frame; 38 | int packet_left; 39 | float buffer[BUFFER_SIZE / 2 + 1]; 40 | }; 41 | 42 | static unsigned 43 | ffmpeg_get_channels(struct input_handle *ih) 44 | { 45 | return (unsigned)ih->codec_context->channels; 46 | } 47 | 48 | static unsigned long 49 | ffmpeg_get_samplerate(struct input_handle *ih) 50 | { 51 | return (unsigned long)ih->codec_context->sample_rate; 52 | } 53 | 54 | static float * 55 | ffmpeg_get_buffer(struct input_handle *ih) 56 | { 57 | return ih->buffer; 58 | } 59 | 60 | static struct input_handle * 61 | ffmpeg_handle_init() 62 | { 63 | struct input_handle *ret; 64 | ret = malloc(sizeof(struct input_handle)); 65 | 66 | return ret; 67 | } 68 | 69 | static void 70 | ffmpeg_handle_destroy(struct input_handle **ih) 71 | { 72 | free(*ih); 73 | *ih = NULL; 74 | } 75 | 76 | 77 | static int 78 | ffmpeg_open_file(struct input_handle *ih, char const *filename) 79 | { 80 | size_t j; 81 | 82 | g_mutex_lock(&ffmpeg_mutex); 83 | ih->format_context = NULL; 84 | 85 | if (avformat_open_input(&ih->format_context, filename, NULL, NULL) != 86 | 0) { 87 | fprintf(stderr, "Could not open input file!\n"); 88 | g_mutex_unlock(&ffmpeg_mutex); 89 | return 1; 90 | } 91 | if (avformat_find_stream_info(ih->format_context, 0) < 0) { 92 | fprintf(stderr, "Could not find stream info!\n"); 93 | g_mutex_unlock(&ffmpeg_mutex); 94 | goto close_file; 95 | } 96 | // av_dump_format(ih->format_context, 0, "blub", 0); 97 | 98 | // Find the first audio stream 99 | ih->audio_stream = -1; 100 | for (j = 0; j < ih->format_context->nb_streams; ++j) { 101 | if (ih->format_context->streams[j]->codec->codec_type == 102 | AVMEDIA_TYPE_AUDIO) { 103 | ih->audio_stream = (int)j; 104 | break; 105 | } 106 | } 107 | if (ih->audio_stream == -1) { 108 | fprintf(stderr, "Could not find an audio stream in file!\n"); 109 | g_mutex_unlock(&ffmpeg_mutex); 110 | goto close_file; 111 | } 112 | // Get a pointer to the codec context for the audio stream 113 | ih->codec_context = 114 | ih->format_context->streams[ih->audio_stream]->codec; 115 | 116 | ih->codec_context->request_sample_fmt = AV_SAMPLE_FMT_FLT; 117 | 118 | // Ignore Opus gain when decoding. 119 | if (ih->codec_context->codec_id == AV_CODEC_ID_OPUS && 120 | ih->codec_context->extradata_size >= 18) { 121 | ih->codec_context->extradata[16] = 122 | ih->codec_context->extradata[17] = 0; 123 | } 124 | 125 | ih->codec = avcodec_find_decoder(ih->codec_context->codec_id); 126 | if (ih->codec == NULL) { 127 | fprintf(stderr, 128 | "Could not find a decoder for the audio format!\n"); 129 | g_mutex_unlock(&ffmpeg_mutex); 130 | goto close_file; 131 | } 132 | 133 | char *float_codec = g_malloc( 134 | strlen(ih->codec->name) + sizeof("float") + 1); 135 | sprintf(float_codec, "%sfloat", ih->codec->name); 136 | AVCodec *possible_float_codec = avcodec_find_decoder_by_name( 137 | float_codec); 138 | if (possible_float_codec) { 139 | ih->codec = possible_float_codec; 140 | } 141 | g_free(float_codec); 142 | 143 | // Open codec 144 | if (avcodec_open2(ih->codec_context, ih->codec, NULL) < 0) { 145 | fprintf(stderr, "Could not open the codec!\n"); 146 | g_mutex_unlock(&ffmpeg_mutex); 147 | goto close_file; 148 | } 149 | 150 | #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 28, 1) 151 | ih->frame = av_frame_alloc(); 152 | #else 153 | ih->frame = avcodec_alloc_frame(); 154 | #endif 155 | if (!ih->frame) { 156 | fprintf(stderr, "Could not allocate frame!\n"); 157 | g_mutex_unlock(&ffmpeg_mutex); 158 | goto close_file; 159 | } 160 | 161 | av_init_packet(&ih->packet); 162 | ih->packet.data = NULL; 163 | ih->orig_packet.size = 0; 164 | 165 | g_mutex_unlock(&ffmpeg_mutex); 166 | 167 | ih->flushing = 0; 168 | ih->got_frame = 0; 169 | ih->packet_left = 0; 170 | 171 | return 0; 172 | 173 | close_file: 174 | g_mutex_lock(&ffmpeg_mutex); 175 | avformat_close_input(&ih->format_context); 176 | g_mutex_unlock(&ffmpeg_mutex); 177 | return 1; 178 | } 179 | 180 | static int 181 | ffmpeg_set_channel_map(struct input_handle *ih, int *st) 182 | { 183 | if (!ih->codec_context->channel_layout) { 184 | return 1; 185 | } 186 | 187 | unsigned int channel_map_index = 0; 188 | int bit_counter = 0; 189 | while (channel_map_index < (unsigned)ih->codec_context->channels) { 190 | if (ih->codec_context->channel_layout & (1 << bit_counter)) { 191 | switch (1 << bit_counter) { 192 | case AV_CH_FRONT_LEFT: 193 | st[channel_map_index] = EBUR128_LEFT; 194 | break; 195 | case AV_CH_FRONT_RIGHT: 196 | st[channel_map_index] = EBUR128_RIGHT; 197 | break; 198 | case AV_CH_FRONT_CENTER: 199 | st[channel_map_index] = EBUR128_CENTER; 200 | break; 201 | case AV_CH_BACK_LEFT: 202 | st[channel_map_index] = EBUR128_LEFT_SURROUND; 203 | break; 204 | case AV_CH_BACK_RIGHT: 205 | st[channel_map_index] = EBUR128_RIGHT_SURROUND; 206 | break; 207 | default: 208 | st[channel_map_index] = EBUR128_UNUSED; 209 | break; 210 | } 211 | ++channel_map_index; 212 | } 213 | ++bit_counter; 214 | } 215 | 216 | return 0; 217 | } 218 | 219 | static int 220 | ffmpeg_allocate_buffer(struct input_handle *ih) 221 | { 222 | (void)ih; 223 | return 0; 224 | } 225 | 226 | static size_t 227 | ffmpeg_get_total_frames(struct input_handle *ih) 228 | { 229 | double tmp = 230 | (double)ih->format_context->streams[ih->audio_stream]->duration * 231 | (double)ih->format_context->streams[ih->audio_stream] 232 | ->time_base.num / 233 | (double)ih->format_context->streams[ih->audio_stream] 234 | ->time_base.den * 235 | (double)ih->codec_context->sample_rate; 236 | 237 | return tmp <= 0.0 ? 0 : (size_t)(tmp + 0.5); 238 | } 239 | 240 | static int 241 | decode_packet(struct input_handle *ih) 242 | { 243 | int ret = 0; 244 | ret = avcodec_decode_audio4(ih->codec_context, ih->frame, 245 | &ih->got_frame, &ih->packet); 246 | if (ret < 0) { 247 | fprintf(stderr, "Error in decoder!\n"); 248 | return ret; 249 | } 250 | 251 | return FFMIN(ret, ih->packet.size); 252 | } 253 | 254 | static size_t 255 | ffmpeg_read_one_packet(struct input_handle *ih) 256 | { 257 | start: 258 | if (ih->flushing) { 259 | ih->packet.data = NULL; 260 | ih->packet.size = 0; 261 | decode_packet(ih); 262 | if (!ih->got_frame) { 263 | return 0; 264 | } 265 | goto write_to_buffer; 266 | } 267 | 268 | if (ih->packet_left) { 269 | goto packet_left; 270 | } 271 | 272 | for (;;) { 273 | if (av_read_frame(ih->format_context, &ih->packet) < 0) { 274 | ih->flushing = 1; 275 | goto start; 276 | } 277 | if (ih->packet.stream_index != ih->audio_stream) { 278 | av_free_packet(&ih->packet); 279 | continue; 280 | } 281 | break; 282 | } 283 | 284 | int ret; 285 | ih->orig_packet = ih->packet; 286 | packet_left: 287 | ret = decode_packet(ih); 288 | if (ret < 0) { 289 | goto free_packet; 290 | } 291 | ih->packet.data += ret; 292 | ih->packet.size -= ret; 293 | if (ih->packet.size > 0) { 294 | ih->packet_left = 1; 295 | } else { 296 | free_packet: 297 | av_free_packet(&ih->orig_packet); 298 | ih->packet_left = 0; 299 | } 300 | 301 | if (!ih->got_frame) { 302 | goto start; 303 | } 304 | 305 | write_to_buffer:; 306 | int nr_frames_read = ih->frame->nb_samples; 307 | uint8_t **ed = ih->frame->extended_data; 308 | uint8_t *data = ed[0]; 309 | 310 | int16_t *data_short = (int16_t *)data; 311 | int32_t *data_int = (int32_t *)data; 312 | float *data_float = (float *)data; 313 | double *data_double = (double *)data; 314 | 315 | /* TODO: fix this */ 316 | int channels = ih->codec_context->channels; 317 | // channels = ih->frame->channels; 318 | 319 | if (ih->frame->nb_samples * channels > 320 | (int)(sizeof(ih->buffer) / sizeof(*ih->buffer))) { 321 | fprintf(stderr, "buffer too small!\n"); 322 | return 0; 323 | } 324 | 325 | int i; 326 | switch (ih->frame->format) { 327 | case AV_SAMPLE_FMT_U8: 328 | fprintf(stderr, "8 bit audio not supported by libebur128!\n"); 329 | nr_frames_read = 0; 330 | goto out; 331 | case AV_SAMPLE_FMT_S16: 332 | for (i = 0; i < ih->frame->nb_samples * channels; ++i) { 333 | ih->buffer[i] = ((float)data_short[i]) / 334 | MAX(-(float)SHRT_MIN, (float)SHRT_MAX); 335 | } 336 | break; 337 | case AV_SAMPLE_FMT_S32: 338 | for (i = 0; i < ih->frame->nb_samples * channels; ++i) { 339 | ih->buffer[i] = ((float)data_int[i]) / 340 | MAX(-(float)INT_MIN, (float)INT_MAX); 341 | } 342 | break; 343 | case AV_SAMPLE_FMT_FLT: 344 | for (i = 0; i < ih->frame->nb_samples * channels; ++i) { 345 | ih->buffer[i] = data_float[i]; 346 | } 347 | break; 348 | case AV_SAMPLE_FMT_DBL: 349 | for (i = 0; i < ih->frame->nb_samples * channels; ++i) { 350 | ih->buffer[i] = (float)data_double[i]; 351 | } 352 | break; 353 | case AV_SAMPLE_FMT_S16P: 354 | for (i = 0; i < ih->frame->nb_samples * channels; ++i) { 355 | int current_channel = i / ih->frame->nb_samples; 356 | int current_sample = i % ih->frame->nb_samples; 357 | ih->buffer[current_sample * channels + 358 | current_channel] = 359 | ((float)((int16_t *) 360 | ed[current_channel])[current_sample]) / 361 | MAX(-(float)SHRT_MIN, (float)SHRT_MAX); 362 | } 363 | break; 364 | case AV_SAMPLE_FMT_S32P: 365 | for (i = 0; i < ih->frame->nb_samples * channels; ++i) { 366 | int current_channel = i / ih->frame->nb_samples; 367 | int current_sample = i % ih->frame->nb_samples; 368 | ih->buffer[current_sample * channels + 369 | current_channel] = 370 | ((float)((int32_t *) 371 | ed[current_channel])[current_sample]) / 372 | MAX(-(float)INT_MIN, (float)INT_MAX); 373 | } 374 | break; 375 | case AV_SAMPLE_FMT_FLTP: 376 | for (i = 0; i < ih->frame->nb_samples * channels; ++i) { 377 | int current_channel = i / ih->frame->nb_samples; 378 | int current_sample = i % ih->frame->nb_samples; 379 | ih->buffer[current_sample * channels + 380 | current_channel] = ((float *)/**/ 381 | ed[current_channel])[current_sample]; 382 | } 383 | break; 384 | case AV_SAMPLE_FMT_DBLP: 385 | for (i = 0; i < ih->frame->nb_samples * channels; ++i) { 386 | int current_channel = i / ih->frame->nb_samples; 387 | int current_sample = i % ih->frame->nb_samples; 388 | ih->buffer[current_sample * channels + 389 | current_channel] = (float)((double *) 390 | ed[current_channel])[current_sample]; 391 | } 392 | break; 393 | default: 394 | fprintf(stderr, "Unknown sample format!\n"); 395 | nr_frames_read = 0; 396 | goto out; 397 | } 398 | out: 399 | return (size_t)nr_frames_read; 400 | } 401 | 402 | static size_t 403 | ffmpeg_read_frames(struct input_handle *ih) 404 | { 405 | return ffmpeg_read_one_packet(ih); 406 | } 407 | 408 | static void 409 | ffmpeg_free_buffer(struct input_handle *ih) 410 | { 411 | (void)ih; 412 | } 413 | 414 | static void 415 | ffmpeg_close_file(struct input_handle *ih) 416 | { 417 | g_mutex_lock(&ffmpeg_mutex); 418 | avcodec_close(ih->codec_context); 419 | avformat_close_input(&ih->format_context); 420 | g_mutex_unlock(&ffmpeg_mutex); 421 | } 422 | 423 | static int 424 | ffmpeg_init_library(void) 425 | { 426 | #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100) 427 | // Register all formats and codecs 428 | av_register_all(); 429 | #endif 430 | av_log_set_level(AV_LOG_ERROR); 431 | return 0; 432 | } 433 | 434 | static void 435 | ffmpeg_exit_library(void) 436 | { 437 | } 438 | 439 | #pragma GCC diagnostic pop 440 | 441 | G_MODULE_EXPORT struct input_ops ip_ops = { ffmpeg_get_channels, 442 | ffmpeg_get_samplerate, ffmpeg_get_buffer, ffmpeg_handle_init, 443 | ffmpeg_handle_destroy, ffmpeg_open_file, ffmpeg_set_channel_map, 444 | ffmpeg_allocate_buffer, ffmpeg_get_total_frames, ffmpeg_read_frames, 445 | ffmpeg_free_buffer, ffmpeg_close_file, ffmpeg_init_library, 446 | ffmpeg_exit_library }; 447 | 448 | G_MODULE_EXPORT char const *ip_exts[] = { "wav", "flac", "ogg", "oga", "mp3", 449 | "mp2", "mpc", "ac3", "wv", "mpg", "avi", "mkv", "m4a", "mp4", "aac", 450 | "mov", "mxf", "opus", "w64", NULL }; 451 | -------------------------------------------------------------------------------- /scanner/scanner-drop-qt/scanner-drop-qt.cpp: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include "scanner-drop-qt.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "logo.h" 14 | 15 | gboolean verbose = TRUE; 16 | gboolean histogram = FALSE; 17 | gchar *decode_to_file = nullptr; 18 | 19 | MainWindow::MainWindow(QWidget *parent) 20 | : QWidget(parent, 21 | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | 22 | Qt::WindowStaysOnTopHint) 23 | , dragPosition() 24 | , worker_thread_(nullptr) 25 | , gui_update_thread_(this) 26 | , logo_rotation_timer(nullptr) 27 | { 28 | setWindowTitle("Loudness Drop"); 29 | 30 | setMinimumSize(130, 130); 31 | setMaximumSize(130, 130); 32 | setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 33 | 34 | auto *quitAction = new QAction(tr("E&xit"), this); 35 | quitAction->setShortcut(tr("Ctrl+Q")); 36 | connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); 37 | addAction(quitAction); 38 | 39 | setContextMenuPolicy(Qt::ActionsContextMenu); 40 | 41 | auto *layout = new QVBoxLayout; 42 | layout->setMargin(0); 43 | layout->setSpacing(0); 44 | render_area_ = new RenderArea; 45 | progress_bar_ = new QProgressBar; 46 | progress_bar_->setFixedHeight(15); 47 | progress_bar_->setMaximum(130); 48 | progress_bar_->setTextVisible(false); 49 | 50 | layout->addWidget(render_area_); 51 | layout->addWidget(progress_bar_); 52 | setLayout(layout); 53 | 54 | setAcceptDrops(true); 55 | 56 | connect(&gui_update_thread_, SIGNAL(setProgressBar(int)), this, 57 | SLOT(setProgressBar(int))); 58 | connect(&gui_update_thread_, SIGNAL(rotateLogo()), this, 59 | SLOT(rotateLogo())); 60 | connect(&gui_update_thread_, SIGNAL(resetLogo()), this, 61 | SLOT(resetLogo())); 62 | connect(this, SIGNAL(stopGUIThread()), &gui_update_thread_, 63 | SLOT(stopThread())); 64 | gui_update_thread_.start(); 65 | } 66 | 67 | MainWindow::~MainWindow() 68 | { 69 | emit stopGUIThread(); 70 | gui_update_thread_.wait(); 71 | } 72 | 73 | void 74 | MainWindow::mousePressEvent(QMouseEvent *event) 75 | { 76 | if (event->button() == Qt::LeftButton) { 77 | dragPosition = event->globalPos() - frameGeometry().topLeft(); 78 | setCursor(Qt::ClosedHandCursor); 79 | event->accept(); 80 | } 81 | } 82 | 83 | void 84 | MainWindow::mouseMoveEvent(QMouseEvent *event) 85 | { 86 | if (event->buttons() & Qt::LeftButton) { 87 | move(event->globalPos() - dragPosition); 88 | event->accept(); 89 | } 90 | } 91 | 92 | void 93 | MainWindow::mouseReleaseEvent(QMouseEvent *event) 94 | { 95 | if (event->button() == Qt::LeftButton) { 96 | setCursor(Qt::ArrowCursor); 97 | event->accept(); 98 | } 99 | } 100 | 101 | void 102 | MainWindow::dragEnterEvent(QDragEnterEvent *event) 103 | { 104 | if (event->mimeData()->hasUrls()) { 105 | event->acceptProposedAction(); 106 | } 107 | } 108 | 109 | void 110 | MainWindow::dropEvent(QDropEvent *event) 111 | { 112 | QList urls = event->mimeData()->urls(); 113 | if (!worker_thread_) { 114 | worker_thread_ = new WorkerThread(urls); 115 | worker_thread_->start(); 116 | connect(worker_thread_, SIGNAL(finished()), this, 117 | SLOT(cleanUpThread())); 118 | connect(worker_thread_, 119 | SIGNAL(showResultList(GSList *, void *)), this, 120 | SLOT(showResultList(GSList *, void *))); 121 | } 122 | event->acceptProposedAction(); 123 | } 124 | 125 | QSize 126 | MainWindow::sizeHint() const 127 | { 128 | return QSize(130, 130); 129 | } 130 | 131 | void 132 | MainWindow::cleanUpThread() 133 | { 134 | worker_thread_->wait(); 135 | delete worker_thread_; 136 | worker_thread_ = nullptr; 137 | } 138 | 139 | void 140 | MainWindow::setProgressBar(int value) 141 | { 142 | progress_bar_->setValue(value); 143 | } 144 | 145 | void 146 | MainWindow::rotateLogo() 147 | { 148 | logo_rotation_timer = new QTimer; 149 | connect(logo_rotation_timer, SIGNAL(timeout()), render_area_, 150 | SLOT(updateLogo())); 151 | logo_rotation_timer->start(40); 152 | } 153 | 154 | void 155 | MainWindow::resetLogo() 156 | { 157 | logo_rotation_timer->stop(); 158 | delete logo_rotation_timer; 159 | logo_rotation_timer = nullptr; 160 | render_area_->resetLogo(); 161 | } 162 | 163 | void 164 | MainWindow::showResultList(GSList *files, Filetree tree) 165 | { 166 | (void)this; 167 | auto *res = new ResultWindow(nullptr, files, tree); 168 | res->setAttribute(Qt::WA_DeleteOnClose); 169 | res->show(); 170 | } 171 | 172 | ResultWindow::ResultWindow(QWidget *parent, GSList *files, Filetree tree) 173 | : QWidget(parent) 174 | , data(files) 175 | , files_(files) 176 | , tree_(tree) 177 | { 178 | setWindowTitle("Scanning Result"); 179 | auto *layout = new QVBoxLayout; 180 | setLayout(layout); 181 | 182 | view = new QTreeView; 183 | proxyModel = new QSortFilterProxyModel(this); 184 | proxyModel->setSourceModel(&data); 185 | proxyModel->setSortRole(Qt::UserRole); 186 | view->setRootIsDecorated(false); 187 | view->setAlternatingRowColors(true); 188 | view->setModel(proxyModel); 189 | view->setSortingEnabled(true); 190 | view->sortByColumn(-1, Qt::AscendingOrder); 191 | view->setItemsExpandable(false); 192 | #if QT_VERSION >= 0x050000 193 | view->header()->setSectionResizeMode(QHeaderView::ResizeToContents); 194 | view->header()->setSectionResizeMode(1, QHeaderView::Stretch); 195 | #else 196 | view->header()->setResizeMode(QHeaderView::ResizeToContents); 197 | view->header()->setResizeMode(1, QHeaderView::Stretch); 198 | #endif 199 | view->header()->setStretchLastSection(false); 200 | view->setItemDelegateForColumn(0, new IconDelegate); 201 | 202 | auto *bbox = new QHBoxLayout; 203 | auto *close_button = new QPushButton("&Close", this); 204 | connect(close_button, SIGNAL(clicked()), this, SLOT(close())); 205 | tag_button = new QPushButton("&Tag files", this); 206 | connect(tag_button, SIGNAL(clicked()), this, SLOT(tag_files())); 207 | bbox->addStretch(1); 208 | bbox->addWidget(tag_button); 209 | bbox->addWidget(close_button); 210 | 211 | layout->addWidget(view); 212 | layout->addLayout(bbox); 213 | } 214 | 215 | ResultWindow::~ResultWindow() 216 | { 217 | g_slist_foreach(files_, filetree_free_list_entry, nullptr); 218 | g_slist_free(files_); 219 | filetree_destroy(tree_); 220 | } 221 | 222 | QSize 223 | ResultWindow::sizeHint() const 224 | { 225 | return QSize(800, 400); 226 | } 227 | 228 | void 229 | ResultWindow::tag_files() 230 | { 231 | struct filename_list_node *fln; 232 | struct file_data *fd; 233 | GSList *iter = files_; 234 | int row_index = 0; 235 | tag_button->setEnabled(false); 236 | QCoreApplication::processEvents(); 237 | while (iter) { 238 | fln = (struct filename_list_node *)iter->data; 239 | fd = (struct file_data *)fln->d; 240 | if (fd->scanned) { 241 | if (!fd->tagged) { 242 | int ret = 0; 243 | tag_file(fln, &ret); 244 | fd->tagged = !ret ? 1 : 2; 245 | } 246 | emit view->dataChanged(proxyModel->mapFromSource( 247 | data.index(row_index, 0)), 248 | proxyModel->mapFromSource( 249 | data.index(row_index, 0))); 250 | QCoreApplication::processEvents(); 251 | ++row_index; 252 | } 253 | iter = g_slist_next(iter); 254 | } 255 | } 256 | 257 | ResultData::ResultData(GSList *files) 258 | { 259 | GSList *it = files; 260 | while (it) { 261 | auto *fln = (struct filename_list_node *)it->data; 262 | auto *fd = (struct file_data *)fln->d; 263 | if (fd->scanned) { 264 | files_.push_back(fln); 265 | } 266 | it = g_slist_next(it); 267 | } 268 | } 269 | 270 | int 271 | ResultData::rowCount(QModelIndex const &) const 272 | { 273 | return int(files_.size()); 274 | } 275 | 276 | int 277 | ResultData::columnCount(QModelIndex const &) const 278 | { 279 | return 6; 280 | } 281 | 282 | QVariant 283 | ResultData::data(QModelIndex const &index, int role) const 284 | { 285 | if (role == Qt::DisplayRole) { 286 | struct filename_list_node *fln = files_[size_t(index.row())]; 287 | auto *fd = (struct file_data *)fln->d; 288 | switch (index.column()) { 289 | case 1: 290 | return fln->fr->display; 291 | case 2: { 292 | gchar *p = g_strdup_printf("%+.2f dB", fd->gain_album); 293 | QVariant r(p); 294 | g_free(p); 295 | return r; 296 | } 297 | case 3: { 298 | gchar *p = g_strdup_printf("%+.2f dB", 299 | clamp_rg(RG_REFERENCE_LEVEL - fd->loudness)); 300 | QVariant r(p); 301 | g_free(p); 302 | return r; 303 | } 304 | case 4: { 305 | gchar *p = g_strdup_printf("%.6f", fd->peak_album); 306 | QVariant r(p); 307 | g_free(p); 308 | return r; 309 | } 310 | case 5: { 311 | gchar *p = g_strdup_printf("%.6f", fd->peak); 312 | QVariant r(p); 313 | g_free(p); 314 | return r; 315 | } 316 | default: 317 | return QVariant(); 318 | } 319 | } else if (role == Qt::UserRole) { 320 | struct filename_list_node *fln = files_[size_t(index.row())]; 321 | auto *fd = (struct file_data *)fln->d; 322 | switch (index.column()) { 323 | case 0: 324 | return fd->tagged; 325 | case 1: 326 | return fln->fr->collate_key; 327 | case 2: 328 | return fd->gain_album; 329 | case 3: 330 | return clamp_rg(RG_REFERENCE_LEVEL - fd->loudness); 331 | case 4: 332 | return fd->peak_album; 333 | case 5: 334 | return fd->peak; 335 | default: 336 | return QVariant(); 337 | } 338 | } 339 | 340 | return QVariant(); 341 | } 342 | 343 | QVariant 344 | ResultData::headerData(int section, Qt::Orientation orientation, int role) const 345 | { 346 | if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { 347 | switch (section) { 348 | case 0: 349 | return tr("Tagged"); 350 | case 1: 351 | return tr("File"); 352 | case 2: 353 | return tr("Album Gain"); 354 | case 3: 355 | return tr("Track Gain"); 356 | case 4: 357 | return tr("Album Peak"); 358 | case 5: 359 | return tr("Track Peak"); 360 | default: 361 | return QVariant(); 362 | } 363 | } 364 | return QVariant(); 365 | } 366 | 367 | IconDelegate::IconDelegate(QWidget *parent) 368 | : QStyledItemDelegate(parent) 369 | { 370 | } 371 | 372 | void 373 | IconDelegate::paint(QPainter *painter, QStyleOptionViewItem const &option, 374 | QModelIndex const &index) const 375 | { 376 | int tag_status = index.data(Qt::UserRole).toInt(); 377 | QStyledItemDelegate::paint(painter, option, index); 378 | if (tag_status == 0) { 379 | return; 380 | } 381 | QIcon icon = tag_status == 1 ? 382 | QApplication::style()->standardIcon(QStyle::SP_DialogApplyButton) : 383 | QApplication::style()->standardIcon(QStyle::SP_DialogCancelButton); 384 | QRect paint_rect = option.rect; 385 | if (paint_rect.height() > 16) { 386 | int diff = paint_rect.height() - 16; 387 | paint_rect.setHeight(16); 388 | paint_rect.moveTop(paint_rect.top() + diff / 2); 389 | } 390 | icon.paint(painter, paint_rect, option.decorationAlignment); 391 | } 392 | 393 | 394 | RenderArea::RenderArea(QWidget *parent) 395 | : QWidget(parent) 396 | , svg_renderer_() 397 | , rotation_state(0) 398 | { 399 | QByteArray logo((char const *)test_svg, int(test_svg_len)); 400 | svg_renderer_ = new QSvgRenderer(logo, this); 401 | } 402 | 403 | void 404 | RenderArea::updateLogo() 405 | { 406 | rotation_state += G_PI / 20; 407 | if (rotation_state >= 2.0 * G_PI) { 408 | rotation_state = 0.0; 409 | } 410 | repaint(); 411 | } 412 | 413 | void 414 | RenderArea::resetLogo() 415 | { 416 | rotation_state = 0.0; 417 | repaint(); 418 | } 419 | 420 | void 421 | RenderArea::paintEvent(QPaintEvent *) 422 | { 423 | static const qreal scale_factor = 0.8F; 424 | QPainter painter(this); 425 | QSize s = svg_renderer_->defaultSize(); 426 | QSizeF ws(size()); 427 | qreal svg_aspect = qreal(s.width()) / qreal(s.height()); 428 | qreal canvas_aspect = ws.width() / ws.height(); 429 | 430 | painter.translate(ws.width() / 2.0, ws.height() / 2.0); 431 | painter.rotate(rotation_state / G_PI * 180.0); 432 | painter.translate(-ws.width() / 2.0, -ws.height() / 2.0); 433 | QSizeF ns = ws; 434 | ns.setWidth(ws.width() * svg_aspect / canvas_aspect * scale_factor); 435 | ns.setHeight(ws.height() * scale_factor); 436 | painter.translate((ws.width() - ns.width()) / 2.0, 437 | (ws.height() - ns.height()) / 2.0); 438 | 439 | painter.scale(svg_aspect / canvas_aspect * scale_factor, scale_factor); 440 | 441 | svg_renderer_->render(&painter); 442 | } 443 | 444 | WorkerThread::WorkerThread(QList const &urls) 445 | : QThread(nullptr) 446 | , urls_(urls) 447 | { 448 | } 449 | 450 | void 451 | WorkerThread::run() 452 | { 453 | std::vector roots; 454 | for (QList::ConstIterator it = urls_.begin(); it != urls_.end(); 455 | ++it) { 456 | #ifdef G_OS_WIN32 457 | roots.push_back( 458 | g_strdup(it->toLocalFile().toUtf8().constData())); 459 | #else 460 | roots.push_back( 461 | g_strdup(it->toLocalFile().toLocal8Bit().constData())); 462 | #endif 463 | } 464 | GSList *errors = nullptr; 465 | GSList *files = nullptr; 466 | Filetree tree = filetree_init(&roots[0], roots.size(), TRUE, FALSE, 467 | FALSE, &errors); 468 | 469 | g_slist_foreach(errors, filetree_print_error, &verbose); 470 | g_slist_foreach(errors, filetree_free_error, nullptr); 471 | g_slist_free(errors); 472 | 473 | filetree_file_list(tree, &files); 474 | filetree_remove_common_prefix(files); 475 | 476 | int result = scan_files(files); 477 | if (result) { 478 | emit showResultList(files, tree); 479 | } else { 480 | g_slist_foreach(files, filetree_free_list_entry, nullptr); 481 | g_slist_free(files); 482 | filetree_destroy(tree); 483 | } 484 | } 485 | 486 | GUIUpdateThread::GUIUpdateThread(QWidget *parent) 487 | : QThread(parent) 488 | , old_progress_bar_value_(0) 489 | , rotation_active_(false) 490 | , stop_thread_(false) 491 | { 492 | } 493 | 494 | void 495 | GUIUpdateThread::run() 496 | { 497 | for (;;) { 498 | g_mutex_lock(&progress_mutex); 499 | g_cond_wait(&progress_cond, &progress_mutex); 500 | if (stop_thread_) { 501 | g_mutex_unlock(&progress_mutex); 502 | break; 503 | } 504 | if (total_frames > 0) { 505 | if (!rotation_active_ && elapsed_frames) { 506 | rotation_active_ = true; 507 | emit rotateLogo(); 508 | } 509 | int new_value = (int)std::lround( 510 | CLAMP(double(elapsed_frames) / double(total_frames), 511 | 0.0, 1.0) * 512 | 130.0); 513 | if (new_value != old_progress_bar_value_) { 514 | emit setProgressBar(new_value); 515 | old_progress_bar_value_ = new_value; 516 | } 517 | if (total_frames == elapsed_frames) { 518 | emit setProgressBar(0); 519 | old_progress_bar_value_ = 0; 520 | rotation_active_ = false; 521 | emit resetLogo(); 522 | } 523 | } 524 | g_mutex_unlock(&progress_mutex); 525 | } 526 | } 527 | 528 | void 529 | GUIUpdateThread::stopThread() 530 | { 531 | stop_thread_ = true; 532 | while (isRunning()) { 533 | g_cond_broadcast(&progress_cond); 534 | } 535 | } 536 | 537 | int 538 | main(int argc, char *argv[]) 539 | { 540 | QApplication app(argc, argv); 541 | 542 | /* initialization */ 543 | input_init(argv[0], nullptr); 544 | scanner_init_common(); 545 | setlocale(LC_COLLATE, ""); 546 | setlocale(LC_CTYPE, ""); 547 | #if QT_VERSION < 0x050000 548 | QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); 549 | #endif 550 | 551 | MainWindow window; 552 | window.show(); 553 | return QApplication::exec(); 554 | } 555 | -------------------------------------------------------------------------------- /scanner/logo.h: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | /* logo use according to rules here: 4 | * http://tech.ebu.ch/docs/other/EBU_R_128_logo_rules_of_use.pdf */ 5 | 6 | unsigned char test_svg[] = { 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 7 | 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 8 | 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x55, 0x54, 9 | 0x46, 0x2d, 0x38, 0x22, 0x20, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x6c, 10 | 0x6f, 0x6e, 0x65, 0x3d, 0x22, 0x6e, 0x6f, 0x22, 0x3f, 0x3e, 0x0a, 0x3c, 11 | 0x21, 0x2d, 0x2d, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 12 | 0x77, 0x69, 0x74, 0x68, 0x20, 0x49, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 13 | 0x65, 0x20, 0x28, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 14 | 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 15 | 0x72, 0x67, 0x2f, 0x29, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x0a, 0x3c, 0x73, 16 | 0x76, 0x67, 0x0a, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 17 | 0x73, 0x76, 0x67, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 18 | 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x32, 19 | 0x30, 0x30, 0x30, 0x2f, 0x73, 0x76, 0x67, 0x22, 0x0a, 0x20, 0x20, 0x20, 20 | 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 21 | 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 22 | 0x2f, 0x32, 0x30, 0x30, 0x30, 0x2f, 0x73, 0x76, 0x67, 0x22, 0x0a, 0x20, 23 | 0x20, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 24 | 0x2e, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 25 | 0x3d, 0x22, 0x31, 0x38, 0x36, 0x2e, 0x37, 0x31, 0x36, 0x32, 0x35, 0x22, 26 | 0x0a, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3d, 0x22, 27 | 0x32, 0x31, 0x39, 0x2e, 0x32, 0x35, 0x31, 0x31, 0x39, 0x22, 0x0a, 0x20, 28 | 0x20, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x73, 0x76, 0x67, 0x32, 0x22, 0x0a, 29 | 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x3a, 0x73, 0x70, 0x61, 0x63, 0x65, 30 | 0x3d, 0x22, 0x70, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x22, 0x3e, 31 | 0x3c, 0x64, 0x65, 0x66, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 32 | 0x64, 0x3d, 0x22, 0x64, 0x65, 0x66, 0x73, 0x36, 0x22, 0x3e, 0x3c, 0x63, 33 | 0x6c, 0x69, 0x70, 0x50, 0x61, 0x74, 0x68, 0x0a, 0x20, 0x20, 0x20, 0x20, 34 | 0x20, 0x20, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x63, 0x6c, 0x69, 0x70, 0x50, 35 | 0x61, 0x74, 0x68, 0x32, 0x30, 0x22, 0x3e, 0x3c, 0x70, 0x61, 0x74, 0x68, 36 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x3d, 37 | 0x22, 0x4d, 0x20, 0x30, 0x2c, 0x31, 0x37, 0x35, 0x34, 0x2e, 0x30, 0x31, 38 | 0x20, 0x30, 0x2c, 0x30, 0x20, 0x6c, 0x20, 0x31, 0x34, 0x39, 0x33, 0x2e, 39 | 0x37, 0x33, 0x2c, 0x30, 0x20, 0x30, 0x2c, 0x31, 0x37, 0x35, 0x34, 0x2e, 40 | 0x30, 0x31, 0x20, 0x2d, 0x31, 0x34, 0x39, 0x33, 0x2e, 0x37, 0x33, 0x2c, 41 | 0x30, 0x20, 0x7a, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 42 | 0x20, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x70, 0x61, 0x74, 0x68, 0x32, 0x32, 43 | 0x22, 0x20, 0x2f, 0x3e, 0x3c, 0x2f, 0x63, 0x6c, 0x69, 0x70, 0x50, 0x61, 44 | 0x74, 0x68, 0x3e, 0x3c, 0x2f, 0x64, 0x65, 0x66, 0x73, 0x3e, 0x3c, 0x67, 45 | 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 46 | 0x6f, 0x72, 0x6d, 0x3d, 0x22, 0x6d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x28, 47 | 0x31, 0x2e, 0x32, 0x35, 0x2c, 0x30, 0x2c, 0x30, 0x2c, 0x2d, 0x31, 0x2e, 48 | 0x32, 0x35, 0x2c, 0x30, 0x2c, 0x32, 0x31, 0x39, 0x2e, 0x32, 0x35, 0x31, 49 | 0x32, 0x29, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x3d, 50 | 0x22, 0x67, 0x31, 0x32, 0x22, 0x3e, 0x3c, 0x67, 0x0a, 0x20, 0x20, 0x20, 51 | 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 52 | 0x6d, 0x3d, 0x22, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x28, 0x30, 0x2e, 0x31, 53 | 0x2c, 0x30, 0x2e, 0x31, 0x29, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 54 | 0x20, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x67, 0x31, 0x34, 0x22, 0x3e, 0x3c, 55 | 0x67, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 56 | 0x64, 0x3d, 0x22, 0x67, 0x31, 0x36, 0x22, 0x3e, 0x3c, 0x67, 0x0a, 0x20, 57 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6c, 58 | 0x69, 0x70, 0x2d, 0x70, 0x61, 0x74, 0x68, 0x3d, 0x22, 0x75, 0x72, 0x6c, 59 | 0x28, 0x23, 0x63, 0x6c, 0x69, 0x70, 0x50, 0x61, 0x74, 0x68, 0x32, 0x30, 60 | 0x29, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 61 | 0x20, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x67, 0x31, 0x38, 0x22, 0x3e, 0x3c, 62 | 0x70, 0x61, 0x74, 0x68, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 63 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x3d, 0x22, 0x4d, 0x20, 0x31, 64 | 0x33, 0x39, 0x38, 0x2e, 0x39, 0x33, 0x2c, 0x33, 0x32, 0x38, 0x2e, 0x37, 65 | 0x36, 0x32, 0x20, 0x31, 0x31, 0x32, 0x30, 0x2e, 0x39, 0x32, 0x2c, 0x34, 66 | 0x37, 0x30, 0x2e, 0x34, 0x36, 0x39, 0x20, 0x38, 0x39, 0x35, 0x2e, 0x38, 67 | 0x30, 0x31, 0x2c, 0x34, 0x36, 0x37, 0x2e, 0x34, 0x31, 0x20, 0x31, 0x31, 68 | 0x37, 0x32, 0x2e, 0x31, 0x2c, 0x33, 0x31, 0x31, 0x2e, 0x34, 0x34, 0x31, 69 | 0x20, 0x43, 0x20, 0x39, 0x39, 0x31, 0x2e, 0x33, 0x37, 0x39, 0x2c, 0x32, 70 | 0x31, 0x37, 0x2e, 0x32, 0x31, 0x31, 0x20, 0x38, 0x36, 0x36, 0x2e, 0x36, 71 | 0x38, 0x2c, 0x31, 0x33, 0x37, 0x2e, 0x37, 0x35, 0x20, 0x36, 0x35, 0x32, 72 | 0x2e, 0x35, 0x33, 0x39, 0x2c, 0x31, 0x34, 0x30, 0x2e, 0x38, 0x32, 0x20, 73 | 0x34, 0x30, 0x39, 0x2c, 0x31, 0x34, 0x30, 0x2e, 0x38, 0x32, 0x20, 0x32, 74 | 0x32, 0x38, 0x2e, 0x30, 0x38, 0x32, 0x2c, 0x32, 0x32, 0x32, 0x2e, 0x34, 75 | 0x36, 0x39, 0x20, 0x31, 0x30, 0x39, 0x2e, 0x39, 0x36, 0x31, 0x2c, 0x33, 76 | 0x31, 0x38, 0x2e, 0x38, 0x35, 0x32, 0x20, 0x4c, 0x20, 0x30, 0x2c, 0x32, 77 | 0x32, 0x33, 0x2e, 0x35, 0x35, 0x31, 0x20, 0x43, 0x20, 0x31, 0x33, 0x33, 78 | 0x2e, 0x35, 0x33, 0x39, 0x2c, 0x31, 0x32, 0x35, 0x2e, 0x38, 0x30, 0x39, 79 | 0x20, 0x33, 0x36, 0x32, 0x2e, 0x39, 0x38, 0x34, 0x2c, 0x33, 0x2e, 0x38, 80 | 0x35, 0x31, 0x35, 0x36, 0x20, 0x36, 0x33, 0x39, 0x2e, 0x35, 0x30, 0x38, 81 | 0x2c, 0x30, 0x2e, 0x30, 0x38, 0x39, 0x38, 0x34, 0x33, 0x38, 0x20, 0x31, 82 | 0x30, 0x31, 0x32, 0x2e, 0x36, 0x36, 0x2c, 0x2d, 0x34, 0x2e, 0x39, 0x36, 83 | 0x30, 0x39, 0x34, 0x20, 0x31, 0x31, 0x39, 0x32, 0x2e, 0x36, 0x36, 0x2c, 84 | 0x31, 0x39, 0x31, 0x2e, 0x37, 0x31, 0x39, 0x20, 0x31, 0x34, 0x32, 0x30, 85 | 0x2e, 0x37, 0x34, 0x2c, 0x33, 0x32, 0x31, 0x2e, 0x35, 0x33, 0x39, 0x20, 86 | 0x6c, 0x20, 0x2d, 0x32, 0x31, 0x2e, 0x38, 0x31, 0x2c, 0x37, 0x2e, 0x32, 87 | 0x32, 0x33, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 88 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x70, 0x61, 0x74, 89 | 0x68, 0x32, 0x34, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 90 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 91 | 0x22, 0x66, 0x69, 0x6c, 0x6c, 0x3a, 0x23, 0x36, 0x31, 0x38, 0x65, 0x64, 92 | 0x31, 0x3b, 0x66, 0x69, 0x6c, 0x6c, 0x2d, 0x6f, 0x70, 0x61, 0x63, 0x69, 93 | 0x74, 0x79, 0x3a, 0x31, 0x3b, 0x66, 0x69, 0x6c, 0x6c, 0x2d, 0x72, 0x75, 94 | 0x6c, 0x65, 0x3a, 0x6e, 0x6f, 0x6e, 0x7a, 0x65, 0x72, 0x6f, 0x3b, 0x73, 95 | 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x3a, 0x6e, 0x6f, 0x6e, 0x65, 0x22, 0x20, 96 | 0x2f, 0x3e, 0x3c, 0x70, 0x61, 0x74, 0x68, 0x0a, 0x20, 0x20, 0x20, 0x20, 97 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x3d, 0x22, 98 | 0x6d, 0x20, 0x34, 0x36, 0x39, 0x2e, 0x34, 0x37, 0x33, 0x2c, 0x39, 0x39, 99 | 0x39, 0x2e, 0x32, 0x39, 0x33, 0x20, 0x63, 0x20, 0x38, 0x34, 0x2e, 0x37, 100 | 0x38, 0x39, 0x2c, 0x39, 0x37, 0x2e, 0x37, 0x34, 0x37, 0x20, 0x31, 0x33, 101 | 0x38, 0x2e, 0x37, 0x38, 0x35, 0x2c, 0x31, 0x37, 0x39, 0x2e, 0x31, 0x35, 102 | 0x37, 0x20, 0x31, 0x36, 0x34, 0x2e, 0x34, 0x33, 0x37, 0x2c, 0x32, 0x35, 103 | 0x39, 0x2e, 0x30, 0x30, 0x37, 0x20, 0x2d, 0x34, 0x35, 0x2e, 0x35, 0x39, 104 | 0x2c, 0x2d, 0x39, 0x30, 0x2e, 0x37, 0x36, 0x20, 0x2d, 0x31, 0x32, 0x31, 105 | 0x2e, 0x34, 0x36, 0x39, 0x2c, 0x2d, 0x31, 0x34, 0x39, 0x2e, 0x37, 0x34, 106 | 0x20, 0x2d, 0x32, 0x35, 0x33, 0x2e, 0x38, 0x31, 0x32, 0x2c, 0x2d, 0x31, 107 | 0x34, 0x38, 0x2e, 0x34, 0x36, 0x20, 0x2d, 0x31, 0x32, 0x33, 0x2e, 0x34, 108 | 0x38, 0x31, 0x2c, 0x30, 0x20, 0x2d, 0x32, 0x32, 0x39, 0x2e, 0x33, 0x31, 109 | 0x37, 0x2c, 0x35, 0x35, 0x2e, 0x33, 0x37, 0x20, 0x2d, 0x32, 0x32, 0x39, 110 | 0x2e, 0x33, 0x31, 0x37, 0x2c, 0x31, 0x35, 0x36, 0x2e, 0x34, 0x34, 0x20, 111 | 0x30, 0x2c, 0x31, 0x30, 0x39, 0x2e, 0x33, 0x38, 0x20, 0x31, 0x31, 0x39, 112 | 0x2e, 0x30, 0x37, 0x34, 0x2c, 0x31, 0x35, 0x32, 0x2e, 0x32, 0x39, 0x20, 113 | 0x32, 0x33, 0x33, 0x2e, 0x37, 0x33, 0x39, 0x2c, 0x31, 0x35, 0x32, 0x2e, 114 | 0x32, 0x39, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x32, 0x39, 0x32, 0x2c, 0x30, 115 | 0x20, 0x32, 0x31, 0x34, 0x2e, 0x33, 0x37, 0x31, 0x2c, 0x2d, 0x35, 0x37, 116 | 0x2e, 0x30, 0x35, 0x20, 0x32, 0x35, 0x37, 0x2e, 0x33, 0x33, 0x32, 0x2c, 117 | 0x2d, 0x31, 0x33, 0x32, 0x2e, 0x31, 0x34, 0x20, 0x36, 0x2e, 0x34, 0x39, 118 | 0x32, 0x2c, 0x32, 0x37, 0x2e, 0x32, 0x35, 0x20, 0x39, 0x2e, 0x37, 0x32, 119 | 0x36, 0x2c, 0x35, 0x34, 0x2e, 0x35, 0x31, 0x20, 0x39, 0x2e, 0x37, 0x32, 120 | 0x36, 0x2c, 0x38, 0x32, 0x2e, 0x33, 0x39, 0x20, 0x30, 0x2c, 0x32, 0x36, 121 | 0x2e, 0x36, 0x38, 0x20, 0x2d, 0x32, 0x2e, 0x36, 0x34, 0x2c, 0x35, 0x32, 122 | 0x2e, 0x39, 0x33, 0x20, 0x2d, 0x38, 0x2e, 0x35, 0x37, 0x34, 0x2c, 0x37, 123 | 0x37, 0x2e, 0x36, 0x36, 0x20, 0x2d, 0x37, 0x39, 0x2e, 0x32, 0x33, 0x38, 124 | 0x2c, 0x36, 0x35, 0x2e, 0x32, 0x35, 0x20, 0x2d, 0x31, 0x38, 0x32, 0x2e, 125 | 0x32, 0x30, 0x33, 0x2c, 0x38, 0x39, 0x2e, 0x37, 0x37, 0x20, 0x2d, 0x32, 126 | 0x37, 0x38, 0x2e, 0x33, 0x32, 0x2c, 0x38, 0x39, 0x2e, 0x37, 0x37, 0x20, 127 | 0x2d, 0x31, 0x38, 0x30, 0x2e, 0x38, 0x32, 0x39, 0x2c, 0x30, 0x20, 0x2d, 128 | 0x33, 0x35, 0x32, 0x2e, 0x38, 0x34, 0x34, 0x32, 0x2c, 0x2d, 0x38, 0x38, 129 | 0x2e, 0x36, 0x20, 0x2d, 0x33, 0x35, 0x32, 0x2e, 0x38, 0x34, 0x34, 0x32, 130 | 0x2c, 0x2d, 0x32, 0x36, 0x33, 0x2e, 0x30, 0x35, 0x20, 0x30, 0x2c, 0x2d, 131 | 0x31, 0x35, 0x39, 0x2e, 0x32, 0x32, 0x20, 0x31, 0x35, 0x34, 0x2e, 0x33, 132 | 0x36, 0x37, 0x32, 0x2c, 0x2d, 0x32, 0x37, 0x39, 0x2e, 0x36, 0x35, 0x37, 133 | 0x20, 0x33, 0x38, 0x31, 0x2e, 0x34, 0x38, 0x38, 0x32, 0x2c, 0x2d, 0x32, 134 | 0x37, 0x39, 0x2e, 0x36, 0x35, 0x37, 0x20, 0x32, 0x35, 0x2e, 0x35, 0x33, 135 | 0x35, 0x2c, 0x30, 0x20, 0x35, 0x31, 0x2e, 0x30, 0x34, 0x33, 0x2c, 0x31, 136 | 0x2e, 0x39, 0x34, 0x35, 0x20, 0x37, 0x36, 0x2e, 0x31, 0x34, 0x35, 0x2c, 137 | 0x35, 0x2e, 0x37, 0x35, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 138 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x70, 139 | 0x61, 0x74, 0x68, 0x32, 0x36, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 140 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x79, 0x6c, 141 | 0x65, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x6c, 0x3a, 0x23, 0x31, 0x63, 0x35, 142 | 0x38, 0x61, 0x61, 0x3b, 0x66, 0x69, 0x6c, 0x6c, 0x2d, 0x6f, 0x70, 0x61, 143 | 0x63, 0x69, 0x74, 0x79, 0x3a, 0x31, 0x3b, 0x66, 0x69, 0x6c, 0x6c, 0x2d, 144 | 0x72, 0x75, 0x6c, 0x65, 0x3a, 0x6e, 0x6f, 0x6e, 0x7a, 0x65, 0x72, 0x6f, 145 | 0x3b, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x3a, 0x6e, 0x6f, 0x6e, 0x65, 146 | 0x22, 0x20, 0x2f, 0x3e, 0x3c, 0x70, 0x61, 0x74, 0x68, 0x0a, 0x20, 0x20, 147 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 148 | 0x3d, 0x22, 0x6d, 0x20, 0x31, 0x30, 0x37, 0x36, 0x2e, 0x39, 0x33, 0x2c, 149 | 0x31, 0x35, 0x36, 0x38, 0x2e, 0x30, 0x39, 0x20, 0x63, 0x20, 0x2d, 0x31, 150 | 0x31, 0x33, 0x2e, 0x37, 0x39, 0x33, 0x2c, 0x30, 0x20, 0x2d, 0x32, 0x30, 151 | 0x38, 0x2e, 0x31, 0x32, 0x39, 0x2c, 0x2d, 0x33, 0x30, 0x2e, 0x34, 0x35, 152 | 0x20, 0x2d, 0x32, 0x37, 0x39, 0x2e, 0x34, 0x38, 0x39, 0x2c, 0x2d, 0x38, 153 | 0x33, 0x2e, 0x34, 0x33, 0x20, 0x37, 0x2e, 0x35, 0x34, 0x37, 0x2c, 0x2d, 154 | 0x33, 0x31, 0x2e, 0x32, 0x33, 0x20, 0x31, 0x31, 0x2e, 0x32, 0x31, 0x31, 155 | 0x2c, 0x2d, 0x36, 0x33, 0x2e, 0x34, 0x35, 0x20, 0x31, 0x31, 0x2e, 0x32, 156 | 0x31, 0x31, 0x2c, 0x2d, 0x39, 0x35, 0x2e, 0x34, 0x38, 0x20, 0x30, 0x2c, 157 | 0x2d, 0x34, 0x33, 0x2e, 0x36, 0x31, 0x20, 0x2d, 0x36, 0x2e, 0x30, 0x37, 158 | 0x34, 0x2c, 0x2d, 0x38, 0x35, 0x2e, 0x35, 0x37, 0x20, 0x2d, 0x31, 0x37, 159 | 0x2e, 0x35, 0x33, 0x39, 0x2c, 0x2d, 0x31, 0x32, 0x36, 0x2e, 0x37, 0x31, 160 | 0x20, 0x35, 0x35, 0x2e, 0x34, 0x31, 0x38, 0x2c, 0x31, 0x31, 0x32, 0x2e, 161 | 0x36, 0x34, 0x20, 0x31, 0x34, 0x31, 0x2e, 0x31, 0x36, 0x2c, 0x31, 0x38, 162 | 0x32, 0x2e, 0x34, 0x31, 0x20, 0x33, 0x30, 0x35, 0x2e, 0x36, 0x37, 0x37, 163 | 0x2c, 0x31, 0x38, 0x32, 0x2e, 0x34, 0x31, 0x20, 0x31, 0x34, 0x37, 0x2e, 164 | 0x37, 0x34, 0x2c, 0x30, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x37, 0x39, 0x2c, 165 | 0x2d, 0x37, 0x30, 0x2e, 0x36, 0x31, 0x20, 0x32, 0x35, 0x35, 0x2e, 0x37, 166 | 0x39, 0x2c, 0x2d, 0x31, 0x37, 0x39, 0x2e, 0x39, 0x39, 0x20, 0x30, 0x2c, 167 | 0x2d, 0x31, 0x31, 0x34, 0x2e, 0x39, 0x20, 0x2d, 0x31, 0x32, 0x37, 0x2e, 168 | 0x38, 0x39, 0x2c, 0x2d, 0x31, 0x38, 0x34, 0x2e, 0x31, 0x33, 0x20, 0x2d, 169 | 0x32, 0x37, 0x31, 0x2e, 0x32, 0x33, 0x2c, 0x2d, 0x31, 0x37, 0x39, 0x2e, 170 | 0x39, 0x38, 0x20, 0x2d, 0x31, 0x33, 0x35, 0x2e, 0x31, 0x37, 0x38, 0x2c, 171 | 0x30, 0x20, 0x2d, 0x32, 0x34, 0x36, 0x2e, 0x35, 0x34, 0x35, 0x2c, 0x35, 172 | 0x35, 0x2e, 0x35, 0x38, 0x20, 0x2d, 0x32, 0x39, 0x35, 0x2e, 0x39, 0x34, 173 | 0x2c, 0x31, 0x35, 0x38, 0x2e, 0x35, 0x33, 0x20, 0x2d, 0x31, 0x32, 0x2e, 174 | 0x38, 0x32, 0x2c, 0x2d, 0x33, 0x39, 0x2e, 0x38, 0x39, 0x20, 0x2d, 0x33, 175 | 0x30, 0x2e, 0x37, 0x39, 0x37, 0x2c, 0x2d, 0x37, 0x39, 0x2e, 0x31, 0x31, 176 | 0x20, 0x2d, 0x35, 0x33, 0x2e, 0x34, 0x33, 0x33, 0x2c, 0x2d, 0x31, 0x31, 177 | 0x38, 0x2e, 0x34, 0x31, 0x20, 0x37, 0x36, 0x2e, 0x32, 0x38, 0x35, 0x2c, 178 | 0x2d, 0x31, 0x30, 0x32, 0x2e, 0x34, 0x36, 0x20, 0x32, 0x30, 0x32, 0x2e, 179 | 0x37, 0x35, 0x37, 0x2c, 0x2d, 0x31, 0x36, 0x34, 0x2e, 0x37, 0x32, 0x31, 180 | 0x20, 0x33, 0x37, 0x35, 0x2e, 0x38, 0x32, 0x33, 0x2c, 0x2d, 0x31, 0x36, 181 | 0x34, 0x2e, 0x37, 0x32, 0x31, 0x20, 0x32, 0x30, 0x39, 0x2e, 0x35, 0x2c, 182 | 0x30, 0x20, 0x33, 0x38, 0x35, 0x2e, 0x39, 0x33, 0x2c, 0x31, 0x31, 0x33, 183 | 0x2e, 0x35, 0x33, 0x31, 0x20, 0x33, 0x38, 0x35, 0x2e, 0x39, 0x33, 0x2c, 184 | 0x33, 0x30, 0x31, 0x2e, 0x38, 0x31, 0x31, 0x20, 0x30, 0x2c, 0x31, 0x37, 185 | 0x31, 0x2e, 0x36, 0x38, 0x20, 0x2d, 0x31, 0x36, 0x35, 0x2e, 0x34, 0x31, 186 | 0x2c, 0x33, 0x30, 0x35, 0x2e, 0x39, 0x37, 0x20, 0x2d, 0x34, 0x31, 0x36, 187 | 0x2e, 0x38, 0x2c, 0x33, 0x30, 0x35, 0x2e, 0x39, 0x37, 0x22, 0x0a, 0x20, 188 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 189 | 0x69, 0x64, 0x3d, 0x22, 0x70, 0x61, 0x74, 0x68, 0x32, 0x38, 0x22, 0x0a, 190 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 191 | 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x6c, 192 | 0x3a, 0x23, 0x31, 0x63, 0x35, 0x38, 0x61, 0x61, 0x3b, 0x66, 0x69, 0x6c, 193 | 0x6c, 0x2d, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x3a, 0x31, 0x3b, 194 | 0x66, 0x69, 0x6c, 0x6c, 0x2d, 0x72, 0x75, 0x6c, 0x65, 0x3a, 0x6e, 0x6f, 195 | 0x6e, 0x7a, 0x65, 0x72, 0x6f, 0x3b, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 196 | 0x3a, 0x6e, 0x6f, 0x6e, 0x65, 0x22, 0x20, 0x2f, 0x3e, 0x3c, 0x70, 0x61, 197 | 0x74, 0x68, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 198 | 0x20, 0x20, 0x20, 0x20, 0x64, 0x3d, 0x22, 0x6d, 0x20, 0x39, 0x36, 0x2e, 199 | 0x34, 0x32, 0x35, 0x38, 0x2c, 0x35, 0x31, 0x35, 0x2e, 0x31, 0x39, 0x39, 200 | 0x20, 0x30, 0x2c, 0x31, 0x30, 0x30, 0x20, 0x4c, 0x20, 0x32, 0x31, 0x39, 201 | 0x2e, 0x35, 0x39, 0x2c, 0x37, 0x33, 0x39, 0x2e, 0x32, 0x37, 0x20, 0x63, 202 | 0x20, 0x32, 0x39, 0x36, 0x2e, 0x33, 0x32, 0x34, 0x2c, 0x32, 0x39, 0x32, 203 | 0x2e, 0x35, 0x36, 0x20, 0x34, 0x33, 0x31, 0x2e, 0x39, 0x38, 0x38, 0x2c, 204 | 0x34, 0x34, 0x38, 0x2e, 0x30, 0x39, 0x20, 0x34, 0x33, 0x31, 0x2e, 0x39, 205 | 0x38, 0x38, 0x2c, 0x36, 0x32, 0x39, 0x2e, 0x35, 0x35, 0x20, 0x30, 0x2c, 206 | 0x31, 0x32, 0x32, 0x2e, 0x31, 0x39, 0x20, 0x2d, 0x35, 0x35, 0x2e, 0x34, 207 | 0x30, 0x36, 0x2c, 0x32, 0x33, 0x30, 0x2e, 0x33, 0x38, 0x20, 0x2d, 0x32, 208 | 0x32, 0x38, 0x2e, 0x34, 0x38, 0x38, 0x2c, 0x32, 0x33, 0x35, 0x2e, 0x31, 209 | 0x34, 0x20, 0x2d, 0x31, 0x31, 0x30, 0x2e, 0x35, 0x36, 0x33, 0x2c, 0x33, 210 | 0x2e, 0x30, 0x33, 0x20, 0x2d, 0x31, 0x38, 0x36, 0x2e, 0x39, 0x36, 0x31, 211 | 0x2c, 0x2d, 0x37, 0x2e, 0x31, 0x32, 0x20, 0x2d, 0x32, 0x38, 0x31, 0x2e, 212 | 0x33, 0x37, 0x35, 0x2c, 0x2d, 0x35, 0x35, 0x2e, 0x37, 0x35, 0x20, 0x6c, 213 | 0x20, 0x2d, 0x34, 0x39, 0x2e, 0x39, 0x37, 0x36, 0x37, 0x2c, 0x31, 0x30, 214 | 0x39, 0x2e, 0x31, 0x33, 0x20, 0x63, 0x20, 0x39, 0x39, 0x2e, 0x34, 0x31, 215 | 0x37, 0x37, 0x2c, 0x36, 0x33, 0x2e, 0x33, 0x36, 0x20, 0x32, 0x32, 0x32, 216 | 0x2e, 0x39, 0x36, 0x34, 0x37, 0x2c, 0x38, 0x38, 0x2e, 0x32, 0x31, 0x20, 217 | 0x33, 0x36, 0x31, 0x2e, 0x36, 0x39, 0x35, 0x37, 0x2c, 0x38, 0x31, 0x2e, 218 | 0x37, 0x38, 0x20, 0x32, 0x34, 0x39, 0x2e, 0x36, 0x34, 0x38, 0x2c, 0x2d, 219 | 0x31, 0x31, 0x2e, 0x35, 0x37, 0x20, 0x33, 0x35, 0x35, 0x2e, 0x32, 0x31, 220 | 0x38, 0x2c, 0x2d, 0x31, 0x37, 0x37, 0x2e, 0x37, 0x36, 0x20, 0x33, 0x35, 221 | 0x35, 0x2e, 0x32, 0x31, 0x38, 0x2c, 0x2d, 0x33, 0x34, 0x39, 0x2e, 0x39, 222 | 0x34, 0x20, 0x30, 0x2c, 0x2d, 0x32, 0x32, 0x32, 0x2e, 0x32, 0x20, 0x2d, 223 | 0x31, 0x35, 0x35, 0x2e, 0x32, 0x39, 0x37, 0x2c, 0x2d, 0x34, 0x30, 0x31, 224 | 0x2e, 0x37, 0x39, 0x37, 0x20, 0x2d, 0x33, 0x39, 0x39, 0x2e, 0x38, 0x34, 225 | 0x33, 0x2c, 0x2d, 0x36, 0x34, 0x36, 0x2e, 0x32, 0x20, 0x6c, 0x20, 0x2d, 226 | 0x39, 0x31, 0x2e, 0x30, 0x33, 0x39, 0x2c, 0x2d, 0x38, 0x38, 0x2e, 0x38, 227 | 0x37, 0x38, 0x20, 0x30, 0x2c, 0x2d, 0x33, 0x2e, 0x37, 0x31, 0x31, 0x20, 228 | 0x35, 0x31, 0x39, 0x2e, 0x34, 0x33, 0x37, 0x2c, 0x30, 0x20, 0x30, 0x2c, 229 | 0x2d, 0x31, 0x33, 0x35, 0x2e, 0x31, 0x39, 0x32, 0x20, 0x2d, 0x37, 0x34, 230 | 0x30, 0x2e, 0x37, 0x38, 0x31, 0x32, 0x2c, 0x30, 0x22, 0x0a, 0x20, 0x20, 231 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 232 | 0x64, 0x3d, 0x22, 0x70, 0x61, 0x74, 0x68, 0x33, 0x30, 0x22, 0x0a, 0x20, 233 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 234 | 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x6c, 0x3a, 235 | 0x23, 0x66, 0x62, 0x62, 0x66, 0x31, 0x36, 0x3b, 0x66, 0x69, 0x6c, 0x6c, 236 | 0x2d, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x3a, 0x31, 0x3b, 0x66, 237 | 0x69, 0x6c, 0x6c, 0x2d, 0x72, 0x75, 0x6c, 0x65, 0x3a, 0x6e, 0x6f, 0x6e, 238 | 0x7a, 0x65, 0x72, 0x6f, 0x3b, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x3a, 239 | 0x6e, 0x6f, 0x6e, 0x65, 0x22, 0x20, 0x2f, 0x3e, 0x3c, 0x70, 0x61, 0x74, 240 | 0x68, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 241 | 0x20, 0x20, 0x20, 0x64, 0x3d, 0x22, 0x6d, 0x20, 0x38, 0x34, 0x33, 0x2e, 242 | 0x39, 0x32, 0x36, 0x2c, 0x31, 0x35, 0x38, 0x31, 0x2e, 0x35, 0x34, 0x20, 243 | 0x63, 0x20, 0x37, 0x38, 0x2e, 0x32, 0x30, 0x33, 0x2c, 0x33, 0x32, 0x2e, 244 | 0x39, 0x32, 0x20, 0x31, 0x36, 0x34, 0x2e, 0x30, 0x39, 0x34, 0x2c, 0x34, 245 | 0x38, 0x2e, 0x37, 0x39, 0x20, 0x32, 0x38, 0x32, 0x2e, 0x33, 0x32, 0x34, 246 | 0x2c, 0x34, 0x36, 0x2e, 0x36, 0x36, 0x20, 0x31, 0x33, 0x36, 0x2e, 0x31, 247 | 0x38, 0x2c, 0x2d, 0x32, 0x2e, 0x34, 0x37, 0x20, 0x32, 0x31, 0x33, 0x2e, 248 | 0x37, 0x37, 0x2c, 0x2d, 0x33, 0x34, 0x2e, 0x37, 0x37, 0x20, 0x32, 0x39, 249 | 0x32, 0x2e, 0x39, 0x39, 0x2c, 0x2d, 0x37, 0x37, 0x2e, 0x36, 0x20, 0x6c, 250 | 0x20, 0x35, 0x31, 0x2e, 0x39, 0x35, 0x2c, 0x31, 0x30, 0x32, 0x2e, 0x36, 251 | 0x35, 0x20, 0x63, 0x20, 0x2d, 0x38, 0x33, 0x2e, 0x34, 0x38, 0x2c, 0x34, 252 | 0x38, 0x2e, 0x34, 0x33, 0x20, 0x2d, 0x31, 0x38, 0x39, 0x2e, 0x36, 0x36, 253 | 0x2c, 0x39, 0x37, 0x2e, 0x34, 0x39, 0x20, 0x2d, 0x33, 0x34, 0x34, 0x2e, 254 | 0x39, 0x34, 0x2c, 0x31, 0x30, 0x30, 0x2e, 0x36, 0x32, 0x20, 0x2d, 0x31, 255 | 0x33, 0x38, 0x2e, 0x36, 0x36, 0x2c, 0x32, 0x2e, 0x37, 0x39, 0x20, 0x2d, 256 | 0x32, 0x35, 0x30, 0x2e, 0x35, 0x32, 0x37, 0x2c, 0x2d, 0x33, 0x36, 0x2e, 257 | 0x37, 0x33, 0x20, 0x2d, 0x33, 0x33, 0x38, 0x2e, 0x34, 0x38, 0x34, 0x2c, 258 | 0x2d, 0x37, 0x35, 0x2e, 0x32, 0x36, 0x20, 0x30, 0x2c, 0x30, 0x20, 0x32, 259 | 0x30, 0x2e, 0x35, 0x35, 0x34, 0x2c, 0x2d, 0x32, 0x36, 0x2e, 0x39, 0x31, 260 | 0x20, 0x33, 0x30, 0x2e, 0x39, 0x34, 0x35, 0x2c, 0x2d, 0x34, 0x35, 0x2e, 261 | 0x35, 0x35, 0x20, 0x31, 0x38, 0x2e, 0x34, 0x39, 0x36, 0x2c, 0x2d, 0x33, 262 | 0x33, 0x2e, 0x31, 0x37, 0x20, 0x32, 0x35, 0x2e, 0x32, 0x31, 0x35, 0x2c, 263 | 0x2d, 0x35, 0x31, 0x2e, 0x35, 0x32, 0x20, 0x32, 0x35, 0x2e, 0x32, 0x31, 264 | 0x35, 0x2c, 0x2d, 0x35, 0x31, 0x2e, 0x35, 0x32, 0x22, 0x0a, 0x20, 0x20, 265 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 266 | 0x64, 0x3d, 0x22, 0x70, 0x61, 0x74, 0x68, 0x33, 0x32, 0x22, 0x0a, 0x20, 267 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 268 | 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x6c, 0x3a, 269 | 0x23, 0x36, 0x31, 0x38, 0x65, 0x64, 0x31, 0x3b, 0x66, 0x69, 0x6c, 0x6c, 270 | 0x2d, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x3a, 0x31, 0x3b, 0x66, 271 | 0x69, 0x6c, 0x6c, 0x2d, 0x72, 0x75, 0x6c, 0x65, 0x3a, 0x6e, 0x6f, 0x6e, 272 | 0x7a, 0x65, 0x72, 0x6f, 0x3b, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x3a, 273 | 0x6e, 0x6f, 0x6e, 0x65, 0x22, 0x20, 0x2f, 0x3e, 0x3c, 0x2f, 0x67, 0x3e, 274 | 0x3c, 0x2f, 0x67, 0x3e, 0x3c, 0x2f, 0x67, 0x3e, 0x3c, 0x2f, 0x67, 0x3e, 275 | 0x3c, 0x2f, 0x73, 0x76, 0x67, 0x3e }; 276 | unsigned int test_svg_len = 3230; 277 | -------------------------------------------------------------------------------- /scanner/scanner-tag/rgtag.cpp: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include "rgtag.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #ifdef _WIN32 30 | #define CAST_FILENAME (wchar_t const *) 31 | #else 32 | #define CAST_FILENAME 33 | #endif 34 | 35 | static float 36 | parse_string_to_float(std::string const &s, bool string_dummy = false) 37 | { 38 | std::stringstream ss; 39 | ss << s; 40 | if (string_dummy) { 41 | std::string dummy; 42 | ss >> dummy; 43 | } 44 | float f; 45 | ss >> f; 46 | return f; 47 | } 48 | 49 | static bool 50 | clear_txxx_tag(TagLib::ID3v2::Tag *tag, TagLib::String const &tag_name, 51 | float *old_content = nullptr) 52 | { 53 | TagLib::ID3v2::FrameList l = tag->frameList("TXXX"); 54 | for (auto *it : l) { 55 | auto *fr = dynamic_cast(it); 57 | if (fr && fr->description().upper() == tag_name) { 58 | if (old_content) { 59 | *old_content = parse_string_to_float( 60 | fr->fieldList().toString().to8Bit(), true); 61 | } 62 | 63 | tag->removeFrame(fr); 64 | return true; 65 | } 66 | } 67 | return false; 68 | } 69 | 70 | static bool 71 | clear_rva2_tag(TagLib::ID3v2::Tag *tag, TagLib::String const &tag_name) 72 | { 73 | TagLib::ID3v2::FrameList l = tag->frameList("RVA2"); 74 | for (auto *it : l) { 75 | auto *fr = /**/ 76 | dynamic_cast(it); 77 | if (fr && fr->identification().upper() == tag_name) { 78 | tag->removeFrame(fr); 79 | return true; 80 | } 81 | } 82 | return false; 83 | } 84 | 85 | static void 86 | set_txxx_tag(TagLib::ID3v2::Tag *tag, std::string const &tag_name, 87 | std::string const &value) 88 | { 89 | TagLib::ID3v2::UserTextIdentificationFrame *txxx = 90 | TagLib::ID3v2::UserTextIdentificationFrame::find(tag, tag_name); 91 | if (!txxx) { 92 | txxx = new TagLib::ID3v2::UserTextIdentificationFrame; 93 | txxx->setDescription(tag_name); 94 | tag->addFrame(txxx); 95 | } 96 | txxx->setText(value); 97 | } 98 | 99 | static void 100 | set_rva2_tag(TagLib::ID3v2::Tag *tag, std::string const &tag_name, double gain, 101 | double peak) 102 | { 103 | TagLib::ID3v2::RelativeVolumeFrame *rva2 = nullptr; 104 | TagLib::ID3v2::FrameList rva2_frame_list = tag->frameList("RVA2"); 105 | for (auto *it : rva2_frame_list) { 106 | auto *fr = /**/ 107 | dynamic_cast(it); 108 | if (fr->identification() == tag_name) { 109 | rva2 = fr; 110 | break; 111 | } 112 | } 113 | if (!rva2) { 114 | rva2 = new TagLib::ID3v2::RelativeVolumeFrame; 115 | rva2->setIdentification(tag_name); 116 | tag->addFrame(rva2); 117 | } 118 | rva2->setVolumeAdjustment(float(gain), 119 | TagLib::ID3v2::RelativeVolumeFrame::MasterVolume); 120 | 121 | TagLib::ID3v2::RelativeVolumeFrame::PeakVolume peak_volume; 122 | peak_volume.bitsRepresentingPeak = 16; 123 | double amp_peak = peak * 32768.0 > 65535.0 ? 65535.0 : peak * 32768.0; 124 | auto amp_peak_int = static_cast(std::ceil(amp_peak)); 125 | TagLib::ByteVector bv_uint = TagLib::ByteVector::fromUInt(amp_peak_int); 126 | peak_volume.peakVolume = TagLib::ByteVector(&(bv_uint.data()[2]), 2); 127 | rva2->setPeakVolume(peak_volume); 128 | } 129 | 130 | struct gain_data_strings { 131 | gain_data_strings(struct gain_data *gd) 132 | { 133 | std::stringstream ss; 134 | ss.precision(2); 135 | ss << std::fixed; 136 | ss << gd->album_gain << " dB"; 137 | album_gain = ss.str(); 138 | ss.str(std::string()); 139 | ss.clear(); 140 | ss << gd->track_gain << " dB"; 141 | track_gain = ss.str(); 142 | ss.str(std::string()); 143 | ss.clear(); 144 | ss.precision(6); 145 | ss << gd->album_peak; 146 | ss >> album_peak; 147 | ss.str(std::string()); 148 | ss.clear(); 149 | ss << gd->track_peak; 150 | ss >> track_peak; 151 | ss.str(std::string()); 152 | ss.clear(); 153 | } 154 | 155 | std::string track_gain; 156 | std::string track_peak; 157 | std::string album_gain; 158 | std::string album_peak; 159 | }; 160 | 161 | static int 162 | tag_id3v2(char const *filename, struct gain_data *gd, 163 | struct gain_data_strings *gds) 164 | { 165 | TagLib::MPEG::File f(CAST_FILENAME filename); 166 | TagLib::ID3v2::Tag *id3v2tag = f.ID3v2Tag(true); 167 | TagLib::uint version = id3v2tag->header()->majorVersion(); 168 | if (version > 4) { 169 | return 1; 170 | } 171 | 172 | while (clear_txxx_tag(id3v2tag, 173 | TagLib::String("replaygain_album_gain").upper())) { 174 | } 175 | while (clear_txxx_tag(id3v2tag, 176 | TagLib::String("replaygain_album_peak").upper())) { 177 | } 178 | while (clear_rva2_tag(id3v2tag, TagLib::String("album").upper())) { 179 | } 180 | while (clear_txxx_tag(id3v2tag, 181 | TagLib::String("replaygain_track_gain").upper())) { 182 | } 183 | while (clear_txxx_tag(id3v2tag, 184 | TagLib::String("replaygain_track_peak").upper())) { 185 | } 186 | while (clear_rva2_tag(id3v2tag, TagLib::String("track").upper())) { 187 | } 188 | set_txxx_tag(id3v2tag, "replaygain_track_gain", gds->track_gain); 189 | set_txxx_tag(id3v2tag, "replaygain_track_peak", gds->track_peak); 190 | if (version == 4) { 191 | set_rva2_tag(id3v2tag, "track", gd->track_gain, gd->track_peak); 192 | } 193 | if (gd->album_mode) { 194 | set_txxx_tag(id3v2tag, "replaygain_album_gain", 195 | gds->album_gain); 196 | set_txxx_tag(id3v2tag, "replaygain_album_peak", 197 | gds->album_peak); 198 | if (version == 4) { 199 | set_rva2_tag(id3v2tag, "album", gd->album_gain, 200 | gd->album_peak); 201 | } 202 | } 203 | return (int)!f.save(TagLib::MPEG::File::ID3v2, 204 | TagLib::File::StripTags::StripNone, 205 | version <= 3 ? TagLib::ID3v2::Version::v3 : 206 | TagLib::ID3v2::Version::v4); 207 | } 208 | 209 | static bool 210 | has_tag_id3v2(char const *filename) 211 | { 212 | TagLib::MPEG::File f(CAST_FILENAME filename); 213 | TagLib::ID3v2::Tag *id3v2tag = f.ID3v2Tag(true); 214 | 215 | bool has_tag = false; 216 | float old_tag_value = 0; 217 | 218 | while (clear_txxx_tag(id3v2tag, 219 | TagLib::String("replaygain_album_gain").upper())) { 220 | has_tag = true; 221 | } 222 | while (clear_txxx_tag(id3v2tag, 223 | TagLib::String("replaygain_album_peak").upper(), &old_tag_value)) { 224 | if (old_tag_value == 0) { 225 | return false; 226 | } 227 | has_tag = true; 228 | } 229 | while (clear_rva2_tag(id3v2tag, TagLib::String("album").upper())) { 230 | has_tag = true; 231 | } 232 | while (clear_txxx_tag(id3v2tag, 233 | TagLib::String("replaygain_track_gain").upper())) { 234 | has_tag = true; 235 | } 236 | while (clear_txxx_tag(id3v2tag, 237 | TagLib::String("replaygain_track_peak").upper(), &old_tag_value)) { 238 | if (old_tag_value == 0) { 239 | return false; 240 | } 241 | has_tag = true; 242 | } 243 | while (clear_rva2_tag(id3v2tag, TagLib::String("track").upper())) { 244 | has_tag = true; 245 | } 246 | 247 | return has_tag; 248 | } 249 | 250 | static std::pair 251 | get_ogg_file(char const *filename, char const *extension) 252 | { 253 | TagLib::File *file = nullptr; 254 | TagLib::Ogg::XiphComment *xiph = nullptr; 255 | if (!std::strcmp(extension, "flac")) { 256 | auto *f = new TagLib::FLAC::File(CAST_FILENAME filename); 257 | xiph = f->xiphComment(true); 258 | file = f; 259 | } else if (!std::strcmp(extension, "ogg") || 260 | !std::strcmp(extension, "oga")) { 261 | auto *f = new TagLib::Ogg::Vorbis::File(CAST_FILENAME filename); 262 | xiph = f->tag(); 263 | file = f; 264 | } else if (!std::strcmp(extension, "opus")) { 265 | auto *f = new TagLib::Ogg::Opus::File(CAST_FILENAME filename); 266 | xiph = f->tag(); 267 | file = f; 268 | } 269 | return std::make_pair(file, xiph); 270 | } 271 | 272 | static int16_t 273 | to_opus_gain(double gain) 274 | { 275 | gain = 256 * gain + 0.5; 276 | 277 | if (gain < INT16_MIN) { 278 | return INT16_MIN; 279 | } 280 | 281 | if (gain >= INT16_MAX) { 282 | return INT16_MAX; 283 | } 284 | 285 | return static_cast(std::floor(gain)); 286 | } 287 | 288 | static void 289 | adjust_gain_data(struct gain_data *gd, double opus_correction_db) 290 | { 291 | gd->album_gain += opus_correction_db; 292 | gd->track_gain += opus_correction_db; 293 | if (gd->album_peak > 0.0) { 294 | gd->album_peak = std::pow(10.0, 295 | ((20 * log10(gd->album_peak)) - opus_correction_db) / 20.0); 296 | } 297 | if (gd->track_peak > 0.0) { 298 | gd->track_peak = std::pow(10.0, 299 | ((20 * log10(gd->track_peak)) - opus_correction_db) / 20.0); 300 | } 301 | } 302 | 303 | double 304 | clamp_rg(double x) 305 | { 306 | if (x < -51.0) { 307 | return -51.0; 308 | } 309 | 310 | if (x > 51.0) { 311 | return 51.0; 312 | } 313 | 314 | return x; 315 | } 316 | 317 | void 318 | clamp_gain_data(struct gain_data *gd) 319 | { 320 | gd->album_gain = clamp_rg(gd->album_gain); 321 | gd->track_gain = clamp_rg(gd->track_gain); 322 | } 323 | 324 | static int 325 | tag_vorbis_comment(char const *filename, char const *extension, 326 | struct gain_data *gd, struct gain_data_strings *gds, 327 | OpusTagInfo const *opus_tag_info) 328 | { 329 | std::pair p = 330 | get_ogg_file(filename, extension); 331 | 332 | bool is_opus = !std::strcmp(extension, "opus"); 333 | struct gain_data gd_opus; 334 | struct gain_data_strings gds_opus(&gd_opus); 335 | 336 | if (is_opus) { 337 | double opus_header_gain; 338 | 339 | if (opus_tag_info->opus_gain_reference == 340 | OPUS_GAIN_REFERENCE_ABSOLUTE) { 341 | opus_header_gain = opus_tag_info->offset; 342 | } else { 343 | opus_header_gain = /**/ 344 | (!opus_tag_info->is_track && gd->album_mode) ? 345 | gd->album_gain : 346 | gd->track_gain; 347 | opus_header_gain -= 5.0; 348 | opus_header_gain += opus_tag_info->offset; 349 | } 350 | 351 | int16_t opus_header_gain_int = to_opus_gain(opus_header_gain); 352 | 353 | { 354 | auto *opus_file = /**/ 355 | static_cast(p.first); 356 | TagLib::ByteVector header = opus_file->packet(0); 357 | header[16] = static_cast( 358 | static_cast(opus_header_gain_int) & 0xff); 359 | header[17] = static_cast( 360 | static_cast(opus_header_gain_int) >> 8); 361 | opus_file->setPacket(0, header); 362 | } 363 | 364 | gd_opus = *gd; 365 | adjust_gain_data(&gd_opus, -opus_header_gain_int / 256.0); 366 | 367 | int16_t opus_r128_album_gain_int = /**/ 368 | to_opus_gain(gd_opus.album_gain - 5.0); 369 | int16_t opus_r128_track_gain_int = /**/ 370 | to_opus_gain(gd_opus.track_gain - 5.0); 371 | 372 | p.second->addField("R128_TRACK_GAIN", 373 | std::to_string(opus_r128_track_gain_int)); 374 | if (gd->album_mode) { 375 | p.second->addField("R128_ALBUM_GAIN", 376 | std::to_string(opus_r128_album_gain_int)); 377 | } else { 378 | p.second->removeFields("R128_ALBUM_GAIN"); 379 | } 380 | 381 | clamp_gain_data(&gd_opus); 382 | gds_opus = gain_data_strings(&gd_opus); 383 | 384 | gd = &gd_opus; 385 | gds = &gds_opus; 386 | } 387 | 388 | if (is_opus && !opus_tag_info->vorbisgain_compat) { 389 | p.second->removeFields("REPLAYGAIN_TRACK_GAIN"); 390 | p.second->removeFields("REPLAYGAIN_TRACK_PEAK"); 391 | p.second->removeFields("REPLAYGAIN_ALBUM_GAIN"); 392 | p.second->removeFields("REPLAYGAIN_ALBUM_PEAK"); 393 | } else { 394 | p.second->addField("REPLAYGAIN_TRACK_GAIN", gds->track_gain); 395 | p.second->addField("REPLAYGAIN_TRACK_PEAK", gds->track_peak); 396 | if (gd->album_mode) { 397 | p.second->addField("REPLAYGAIN_ALBUM_GAIN", 398 | gds->album_gain); 399 | p.second->addField("REPLAYGAIN_ALBUM_PEAK", 400 | gds->album_peak); 401 | } else { 402 | p.second->removeFields("REPLAYGAIN_ALBUM_GAIN"); 403 | p.second->removeFields("REPLAYGAIN_ALBUM_PEAK"); 404 | } 405 | } 406 | 407 | bool success = p.first->save(); 408 | delete p.first; 409 | return (int)!success; 410 | } 411 | 412 | static bool 413 | has_vorbis_comment(char const *filename, char const *extension, 414 | bool opus_compat) 415 | { 416 | std::pair p = 417 | get_ogg_file(filename, extension); 418 | 419 | bool has_tag; 420 | TagLib::uint fieldCount = p.second->fieldCount(); 421 | 422 | bool is_opus = !std::strcmp(extension, "opus"); 423 | 424 | TagLib::Ogg::FieldListMap const &flm = p.second->fieldListMap(); 425 | if (flm.contains("REPLAYGAIN_ALBUM_PEAK")) { 426 | TagLib::StringList const &sl = flm["REPLAYGAIN_ALBUM_PEAK"]; 427 | for (auto const &i : sl) { 428 | if (parse_string_to_float(i.to8Bit()) == 0) { 429 | has_tag = false; 430 | goto end; 431 | } 432 | } 433 | } 434 | if (flm.contains("REPLAYGAIN_TRACK_PEAK")) { 435 | TagLib::StringList const &sl = flm["REPLAYGAIN_TRACK_PEAK"]; 436 | for (auto const &i : sl) { 437 | if (parse_string_to_float(i.to8Bit()) == 0) { 438 | has_tag = false; 439 | goto end; 440 | } 441 | } 442 | } 443 | 444 | p.second->removeFields("REPLAYGAIN_ALBUM_GAIN"); 445 | p.second->removeFields("REPLAYGAIN_ALBUM_PEAK"); 446 | p.second->removeFields("REPLAYGAIN_TRACK_GAIN"); 447 | p.second->removeFields("REPLAYGAIN_TRACK_PEAK"); 448 | 449 | if (is_opus) { 450 | if (p.second->fieldCount() < fieldCount) { 451 | if (!opus_compat) { 452 | /* If we see any of those tags above in 453 | * 'non-legacy' opus mode, we need to remove 454 | * them. Just rescan the file for now. */ 455 | has_tag = false; 456 | goto end; 457 | } else { 458 | /* If we are in legacy opus mode, we need _both_ 459 | * R128_* and REPLAYGAIN_* tags. So reset 460 | * fieldCount so that the logic below works. */ 461 | fieldCount = p.second->fieldCount(); 462 | } 463 | } else if (opus_compat) { 464 | /* We need those tags in 'legacy' opus mode. Force a 465 | * rescan. */ 466 | has_tag = false; 467 | goto end; 468 | } 469 | 470 | p.second->removeFields("R128_ALBUM_GAIN"); 471 | p.second->removeFields("R128_TRACK_GAIN"); 472 | } 473 | 474 | has_tag = p.second->fieldCount() < fieldCount; 475 | end: 476 | delete p.first; 477 | return has_tag; 478 | } 479 | 480 | static std::pair 481 | get_ape_file(char const *filename, char const *extension) 482 | { 483 | TagLib::File *file = nullptr; 484 | TagLib::APE::Tag *ape = nullptr; 485 | if (!std::strcmp(extension, "mpc")) { 486 | auto *f = new TagLib::MPC::File(CAST_FILENAME filename); 487 | ape = f->APETag(true); 488 | file = f; 489 | } else if (!std::strcmp(extension, "wv")) { 490 | auto *f = new TagLib::WavPack::File(CAST_FILENAME filename); 491 | ape = f->APETag(true); 492 | file = f; 493 | } 494 | return std::make_pair(file, ape); 495 | } 496 | 497 | static int 498 | tag_ape(char const *filename, char const *extension, struct gain_data *gd, 499 | struct gain_data_strings *gds) 500 | { 501 | std::pair p = get_ape_file(filename, 502 | extension); 503 | p.second->addValue("replaygain_track_gain", gds->track_gain); 504 | p.second->addValue("replaygain_track_peak", gds->track_peak); 505 | if (gd->album_mode) { 506 | p.second->addValue("replaygain_album_gain", gds->album_gain); 507 | p.second->addValue("replaygain_album_peak", gds->album_peak); 508 | } else { 509 | p.second->removeItem("replaygain_album_gain"); 510 | p.second->removeItem("replaygain_album_peak"); 511 | } 512 | bool success = p.first->save(); 513 | delete p.first; 514 | return (int)!success; 515 | } 516 | 517 | static bool 518 | has_tag_ape(char const *filename, char const *extension) 519 | { 520 | std::pair p = get_ape_file(filename, 521 | extension); 522 | 523 | TagLib::uint fieldCount = p.second->itemListMap().size(); 524 | 525 | p.second->removeItem("replaygain_album_gain"); 526 | p.second->removeItem("replaygain_album_peak"); 527 | p.second->removeItem("replaygain_track_gain"); 528 | p.second->removeItem("replaygain_track_peak"); 529 | 530 | bool has_tag = p.second->itemListMap().size() < fieldCount; 531 | delete p.first; 532 | return has_tag; 533 | } 534 | 535 | static int 536 | tag_mp4(char const *filename, struct gain_data *gd, 537 | struct gain_data_strings *gds) 538 | { 539 | TagLib::MP4::File f(CAST_FILENAME filename); 540 | TagLib::MP4::Tag *t = f.tag(); 541 | if (!t) { 542 | return 1; 543 | } 544 | t->setItem("----:com.apple.iTunes:replaygain_track_gain", 545 | TagLib::StringList(gds->track_gain)); 546 | t->setItem("----:com.apple.iTunes:replaygain_track_peak", 547 | TagLib::StringList(gds->track_peak)); 548 | if (gd->album_mode) { 549 | t->setItem("----:com.apple.iTunes:replaygain_album_gain", 550 | TagLib::StringList(gds->album_gain)); 551 | t->setItem("----:com.apple.iTunes:replaygain_album_peak", 552 | TagLib::StringList(gds->album_peak)); 553 | } else { 554 | t->removeItem("----:com.apple.iTunes:replaygain_album_gain"); 555 | t->removeItem("----:com.apple.iTunes:replaygain_album_peak"); 556 | } 557 | return (int)!f.save(); 558 | } 559 | 560 | static bool 561 | has_tag_mp4(char const *filename) 562 | { 563 | TagLib::MP4::File f(CAST_FILENAME filename); 564 | TagLib::MP4::Tag *t = f.tag(); 565 | if (!t) { 566 | std::cerr << "Error reading mp4 tag" << std::endl; 567 | return false; 568 | } 569 | TagLib::MP4::ItemMap const &ilm = t->itemMap(); 570 | 571 | TagLib::uint fieldCount = ilm.size(); 572 | 573 | if (ilm.contains("----:com.apple.iTunes:replaygain_album_peak")) { 574 | TagLib::StringList const &sl = 575 | ilm["----:com.apple.iTunes:replaygain_album_peak"] 576 | .toStringList(); 577 | for (auto const &i : sl) { 578 | if (parse_string_to_float(i.to8Bit()) == 0) { 579 | return false; 580 | } 581 | } 582 | } 583 | if (ilm.contains("----:com.apple.iTunes:replaygain_track_peak")) { 584 | TagLib::StringList const &sl = 585 | ilm["----:com.apple.iTunes:replaygain_track_peak"] 586 | .toStringList(); 587 | for (auto const &i : sl) { 588 | if (parse_string_to_float(i.to8Bit()) == 0) { 589 | return false; 590 | } 591 | } 592 | } 593 | 594 | t->removeItem("----:com.apple.iTunes:replaygain_album_gain"); 595 | t->removeItem("----:com.apple.iTunes:replaygain_album_peak"); 596 | t->removeItem("----:com.apple.iTunes:replaygain_track_gain"); 597 | t->removeItem("----:com.apple.iTunes:replaygain_track_peak"); 598 | 599 | bool has_tag = t->itemMap().size() < fieldCount; 600 | return has_tag; 601 | } 602 | 603 | int 604 | set_rg_info(char const *filename, char const *extension, struct gain_data *gd, 605 | OpusTagInfo const *opus_tag_info) 606 | { 607 | if (std::strcmp(extension, "opus") != 0) { 608 | /* For opus, we clamp in tag_vorbis_comment(). */ 609 | clamp_gain_data(gd); 610 | } 611 | 612 | struct gain_data_strings gds(gd); 613 | 614 | if (!std::strcmp(extension, "mp3") || !std::strcmp(extension, "mp2")) { 615 | return tag_id3v2(filename, gd, &gds); 616 | } 617 | 618 | if (!std::strcmp(extension, "flac") || 619 | !std::strcmp(extension, "opus") || /**/ 620 | !std::strcmp(extension, "ogg") || /**/ 621 | !std::strcmp(extension, "oga")) { 622 | return tag_vorbis_comment(filename, extension, gd, &gds, 623 | opus_tag_info); 624 | } 625 | 626 | if (!std::strcmp(extension, "mpc") || !std::strcmp(extension, "wv")) { 627 | return tag_ape(filename, extension, gd, &gds); 628 | } 629 | 630 | if (!std::strcmp(extension, "mp4") || !std::strcmp(extension, "m4a")) { 631 | return tag_mp4(filename, gd, &gds); 632 | } 633 | 634 | return 1; 635 | } 636 | 637 | bool 638 | has_rg_info(char const *filename, char const *extension, 639 | OpusTagInfo const *opus_tag_info) 640 | { 641 | if (!std::strcmp(extension, "mp3") || !std::strcmp(extension, "mp2")) { 642 | return has_tag_id3v2(filename); 643 | } 644 | 645 | if (!std::strcmp(extension, "flac") || 646 | !std::strcmp(extension, "opus") || /**/ 647 | !std::strcmp(extension, "ogg") || /**/ 648 | !std::strcmp(extension, "oga")) { 649 | return has_vorbis_comment(filename, extension, 650 | opus_tag_info->vorbisgain_compat); 651 | } 652 | 653 | // TODO: implement "0.0 workaround" for ape 654 | if (!std::strcmp(extension, "mpc") || !std::strcmp(extension, "wv")) { 655 | return has_tag_ape(filename, extension); 656 | } 657 | 658 | if (!std::strcmp(extension, "mp4") || !std::strcmp(extension, "m4a")) { 659 | return has_tag_mp4(filename); 660 | } 661 | 662 | return false; 663 | } 664 | -------------------------------------------------------------------------------- /scanner/scanner-drop-gtk/scanner-drop.c: -------------------------------------------------------------------------------- 1 | /* See COPYING file for copyright and license details. */ 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "filetree.h" 14 | #include "input.h" 15 | 16 | #include "scanner-common.h" 17 | #include "scanner-tag.h" 18 | 19 | #include "logo.h" 20 | 21 | gboolean verbose = TRUE; 22 | gboolean histogram = FALSE; 23 | gchar *decode_to_file = NULL; 24 | 25 | #if defined(__GNUC__) 26 | static void exit_program(void) __attribute__((noreturn)); 27 | #endif 28 | 29 | static void 30 | exit_program(void) 31 | { 32 | fprintf(stderr, "Exiting...\n"); 33 | input_deinit(); 34 | exit(0); 35 | } 36 | 37 | struct work_data { 38 | Filetree tree; 39 | gchar **roots; 40 | guint length; 41 | int success; 42 | GSList *files; 43 | GtkWidget *result_window; 44 | GtkTreeModel *tag_model; 45 | }; 46 | 47 | static GMutex thread_mutex; 48 | static GThread *worker_thread; 49 | 50 | 51 | /* result list */ 52 | 53 | enum { 54 | COLUMN_IS_TAGGED, 55 | COLUMN_FILENAME, 56 | COLUMN_ALBUM_GAIN, 57 | COLUMN_TRACK_GAIN, 58 | COLUMN_ALBUM_PEAK, 59 | COLUMN_TRACK_PEAK, 60 | HIDDEN_FILENAME_COLLATE, 61 | NUM_COLUMNS 62 | }; 63 | 64 | static GtkTreeModel * 65 | create_result_list_model(GSList *files) 66 | { 67 | GtkListStore *store; 68 | GtkTreeIter iter; 69 | struct filename_list_node *fln; 70 | struct file_data *fd; 71 | 72 | store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_POINTER, G_TYPE_STRING, 73 | G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, 74 | G_TYPE_STRING); 75 | 76 | /* add data to the list store */ 77 | while (files) { 78 | fln = (struct filename_list_node *)files->data; 79 | fd = (struct file_data *)fln->d; 80 | 81 | if (fd->scanned) { 82 | gtk_list_store_append(store, &iter); 83 | gtk_list_store_set(store, &iter, COLUMN_IS_TAGGED, 84 | &fd->tagged, COLUMN_FILENAME, fln->fr->display, 85 | COLUMN_ALBUM_GAIN, fd->gain_album, 86 | COLUMN_TRACK_GAIN, 87 | clamp_rg(RG_REFERENCE_LEVEL - fd->loudness), 88 | COLUMN_ALBUM_PEAK, fd->peak_album, 89 | COLUMN_TRACK_PEAK, fd->peak, 90 | HIDDEN_FILENAME_COLLATE, fln->fr->collate_key, -1); 91 | } 92 | files = g_slist_next(files); 93 | } 94 | 95 | return GTK_TREE_MODEL(store); 96 | } 97 | 98 | static void 99 | float_to_rg_display(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, 100 | GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) 101 | { 102 | float d; 103 | gchar *text; 104 | 105 | (void)tree_column; 106 | /* Get the double value from the model. */ 107 | gtk_tree_model_get(tree_model, iter, GPOINTER_TO_INT(data), &d, -1); 108 | /* Now we can format the value ourselves. */ 109 | text = g_strdup_printf("%+.2f dB", d); 110 | g_object_set(cell, "text", text, NULL); 111 | g_free(text); 112 | } 113 | 114 | static void 115 | is_tagged_to_icon_name(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, 116 | GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) 117 | { 118 | gpointer d; 119 | int tagged; 120 | 121 | (void)tree_column; 122 | gtk_tree_model_get(tree_model, iter, GPOINTER_TO_INT(data), &d, -1); 123 | tagged = *((int *)d); 124 | g_object_set(cell, "icon-name", 125 | tagged ? (tagged == 1 ? GTK_STOCK_APPLY : GTK_STOCK_CANCEL) : "", 126 | NULL); 127 | } 128 | 129 | static void 130 | result_view_add_colums(GtkTreeView *treeview) 131 | { 132 | GtkCellRenderer *renderer; 133 | GtkTreeViewColumn *column; 134 | 135 | renderer = gtk_cell_renderer_pixbuf_new(); 136 | // g_object_set(renderer, "icon-name", GTK_STOCK_APPLY, NULL); 137 | column = gtk_tree_view_column_new_with_attributes("Tagged", renderer, 138 | NULL); 139 | gtk_tree_view_column_set_sort_column_id(column, COLUMN_IS_TAGGED); 140 | gtk_tree_view_append_column(treeview, column); 141 | gtk_tree_view_column_set_cell_data_func(column, renderer, 142 | is_tagged_to_icon_name, (gpointer)COLUMN_IS_TAGGED, NULL); 143 | 144 | renderer = gtk_cell_renderer_text_new(); 145 | g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL); 146 | column = gtk_tree_view_column_new_with_attributes("File", renderer, 147 | "text", COLUMN_FILENAME, NULL); 148 | gtk_tree_view_column_set_sort_column_id(column, COLUMN_FILENAME); 149 | gtk_tree_view_append_column(treeview, column); 150 | gtk_tree_view_column_set_expand(column, TRUE); 151 | gtk_tree_view_column_set_min_width(column, 0); 152 | gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); 153 | 154 | renderer = gtk_cell_renderer_text_new(); 155 | column = gtk_tree_view_column_new_with_attributes("Album Gain", 156 | renderer, "text", COLUMN_ALBUM_GAIN, NULL); 157 | gtk_tree_view_column_set_sort_column_id(column, COLUMN_ALBUM_GAIN); 158 | gtk_tree_view_append_column(treeview, column); 159 | gtk_tree_view_column_set_cell_data_func(column, renderer, 160 | float_to_rg_display, (gpointer)COLUMN_ALBUM_GAIN, NULL); 161 | 162 | renderer = gtk_cell_renderer_text_new(); 163 | column = gtk_tree_view_column_new_with_attributes("Track Gain", 164 | renderer, "text", COLUMN_TRACK_GAIN, NULL); 165 | gtk_tree_view_column_set_sort_column_id(column, COLUMN_TRACK_GAIN); 166 | gtk_tree_view_append_column(treeview, column); 167 | gtk_tree_view_column_set_cell_data_func(column, renderer, 168 | float_to_rg_display, (gpointer)COLUMN_TRACK_GAIN, NULL); 169 | 170 | renderer = gtk_cell_renderer_text_new(); 171 | column = gtk_tree_view_column_new_with_attributes("Album Peak", 172 | renderer, "text", COLUMN_ALBUM_PEAK, NULL); 173 | gtk_tree_view_column_set_sort_column_id(column, COLUMN_ALBUM_PEAK); 174 | gtk_tree_view_append_column(treeview, column); 175 | 176 | renderer = gtk_cell_renderer_text_new(); 177 | column = gtk_tree_view_column_new_with_attributes("Track Peak", 178 | renderer, "text", COLUMN_TRACK_PEAK, NULL); 179 | gtk_tree_view_column_set_sort_column_id(column, COLUMN_TRACK_PEAK); 180 | gtk_tree_view_append_column(treeview, column); 181 | } 182 | 183 | static void 184 | tag_files_and_update_model(GtkWidget *tag_button, struct work_data *wd) 185 | { 186 | struct filename_list_node *fln; 187 | struct file_data *fd; 188 | int ret; 189 | GSList *files; 190 | 191 | files = wd->files; 192 | gtk_widget_set_sensitive(tag_button, FALSE); 193 | while (files) { 194 | fln = (struct filename_list_node *)files->data; 195 | fd = (struct file_data *)fln->d; 196 | if (fd->scanned) { 197 | if (!fd->tagged) { 198 | ret = 0; 199 | tag_file(fln, &ret); 200 | fd->tagged = !ret ? 1 : 2; 201 | gtk_widget_queue_draw(wd->result_window); 202 | while (gtk_events_pending()) { 203 | gtk_main_iteration(); 204 | } 205 | } 206 | } 207 | files = g_slist_next(files); 208 | } 209 | } 210 | 211 | static void 212 | destroy_work_data(struct work_data *wd) 213 | { 214 | g_slist_foreach(wd->files, filetree_free_list_entry, NULL); 215 | g_slist_free(wd->files); 216 | filetree_destroy(wd->tree); 217 | g_free(wd->roots); 218 | if (wd->result_window) { 219 | gtk_widget_destroy(wd->result_window); 220 | } 221 | g_free(wd); 222 | } 223 | 224 | static gint 225 | icon_sort(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, 226 | gpointer data) 227 | { 228 | gpointer aa; 229 | gtk_tree_model_get(tree_model, a, GPOINTER_TO_INT(data), &aa, -1); 230 | gpointer bb; 231 | gtk_tree_model_get(tree_model, b, GPOINTER_TO_INT(data), &bb, -1); 232 | return *((int *)aa) - *((int *)bb); 233 | } 234 | 235 | static gint 236 | filename_sort(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, 237 | gpointer data) 238 | { 239 | gchar *aa; 240 | gtk_tree_model_get(tree_model, a, GPOINTER_TO_INT(data), &aa, -1); 241 | gchar *bb; 242 | gtk_tree_model_get(tree_model, b, GPOINTER_TO_INT(data), &bb, -1); 243 | return strcmp(aa, bb); 244 | } 245 | 246 | static gboolean 247 | show_result_list(struct work_data *wd) 248 | { 249 | GtkTreeModel *model; 250 | GtkWidget *vbox; 251 | GtkWidget *sw; 252 | GtkWidget *treeview; 253 | 254 | GtkWidget *lower_box; 255 | GtkWidget *lower_box_fill; 256 | 257 | GtkWidget *button_box; 258 | GtkWidget *tag_button; 259 | GtkWidget *ok_button; 260 | 261 | gdk_threads_enter(); 262 | wd->result_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 263 | gtk_window_set_title(GTK_WINDOW(wd->result_window), "Scanning Result"); 264 | 265 | g_signal_connect_swapped(wd->result_window, "delete-event", 266 | G_CALLBACK(destroy_work_data), wd); 267 | gtk_container_set_border_width(GTK_CONTAINER(wd->result_window), 8); 268 | 269 | vbox = gtk_vbox_new(FALSE, 8); 270 | gtk_container_add(GTK_CONTAINER(wd->result_window), vbox); 271 | 272 | sw = gtk_scrolled_window_new(NULL, NULL); 273 | gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), 274 | GTK_SHADOW_ETCHED_IN); 275 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), 276 | GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); 277 | gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); 278 | 279 | /* create tree model */ 280 | model = create_result_list_model(wd->files); 281 | gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), 282 | COLUMN_IS_TAGGED, icon_sort, (gpointer)COLUMN_IS_TAGGED, NULL); 283 | gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), 284 | COLUMN_FILENAME, filename_sort, (gpointer)HIDDEN_FILENAME_COLLATE, 285 | NULL); 286 | 287 | /* create tree view */ 288 | treeview = gtk_tree_view_new_with_model(model); 289 | gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE); 290 | gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview), 291 | COLUMN_FILENAME); 292 | 293 | g_object_unref(model); 294 | 295 | /* add columns to the tree view */ 296 | result_view_add_colums(GTK_TREE_VIEW(treeview)); 297 | gtk_container_add(GTK_CONTAINER(sw), treeview); 298 | 299 | /* create tag button */ 300 | lower_box = gtk_hbox_new(FALSE, 8); 301 | ok_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); 302 | g_signal_connect_swapped(ok_button, "clicked", 303 | G_CALLBACK(destroy_work_data), wd); 304 | tag_button = gtk_button_new_with_mnemonic("_Tag files"); 305 | wd->tag_model = model; 306 | g_signal_connect(tag_button, "clicked", 307 | G_CALLBACK(tag_files_and_update_model), wd); 308 | lower_box_fill = gtk_alignment_new(0, 0.5F, 1, 1); 309 | button_box = gtk_hbutton_box_new(); 310 | gtk_container_add(GTK_CONTAINER(button_box), tag_button); 311 | gtk_container_add(GTK_CONTAINER(button_box), ok_button); 312 | gtk_box_set_spacing(GTK_BOX(button_box), 6); 313 | 314 | gtk_box_pack_start(GTK_BOX(lower_box), lower_box_fill, TRUE, TRUE, 0); 315 | gtk_box_pack_start(GTK_BOX(lower_box), button_box, FALSE, FALSE, 0); 316 | gtk_box_pack_start(GTK_BOX(vbox), lower_box, FALSE, FALSE, 0); 317 | 318 | /* finish & show */ 319 | gtk_window_set_default_size(GTK_WINDOW(wd->result_window), 800, 400); 320 | gtk_widget_show_all(wd->result_window); 321 | gdk_threads_leave(); 322 | 323 | return FALSE; 324 | } 325 | 326 | 327 | /* drop handling and work */ 328 | 329 | static gpointer 330 | do_work(gpointer arg) 331 | { 332 | struct work_data *wd = arg; 333 | int result; 334 | 335 | GSList *errors = NULL; 336 | wd->tree = filetree_init(wd->roots, wd->length, TRUE, FALSE, FALSE, 337 | &errors); 338 | 339 | g_slist_foreach(errors, filetree_print_error, &verbose); 340 | g_slist_foreach(errors, filetree_free_error, NULL); 341 | g_slist_free(errors); 342 | 343 | wd->files = NULL; 344 | filetree_file_list(wd->tree, &wd->files); 345 | filetree_remove_common_prefix(wd->files); 346 | 347 | result = scan_files(wd->files); 348 | if (result) { 349 | g_idle_add((GSourceFunc)show_result_list, wd); 350 | } else { 351 | wd->result_window = NULL; 352 | destroy_work_data(wd); 353 | } 354 | 355 | g_mutex_lock(&thread_mutex); 356 | worker_thread = NULL; 357 | g_mutex_unlock(&thread_mutex); 358 | 359 | return NULL; 360 | } 361 | 362 | static gboolean rotate_logo(GtkWidget *widget); 363 | 364 | static void 365 | handle_data_received(GtkWidget *widget, GdkDragContext *drag_context, gint x, 366 | gint y, GtkSelectionData *data, guint info, guint time, gpointer unused) 367 | { 368 | guint i; 369 | guint no_uris; 370 | gchar **uris; 371 | gchar **files; 372 | struct work_data *sl; 373 | 374 | (void)widget; 375 | (void)x; 376 | (void)y; 377 | (void)info; 378 | (void)unused; 379 | 380 | g_mutex_lock(&thread_mutex); 381 | if (worker_thread) { 382 | g_mutex_unlock(&thread_mutex); 383 | gtk_drag_finish(drag_context, FALSE, FALSE, time); 384 | return; 385 | } 386 | g_mutex_unlock(&thread_mutex); 387 | uris = g_uri_list_extract_uris( 388 | (gchar const *)gtk_selection_data_get_data(data)); 389 | no_uris = g_strv_length(uris); 390 | files = g_new(gchar *, no_uris); 391 | 392 | for (i = 0; i < no_uris; ++i) { 393 | files[i] = g_filename_from_uri(uris[i], NULL, NULL); 394 | } 395 | g_strfreev(uris); 396 | 397 | sl = g_new(struct work_data, 1); 398 | sl->roots = files; 399 | sl->length = no_uris; 400 | worker_thread = g_thread_new(NULL, do_work, sl); 401 | g_thread_unref(worker_thread); 402 | 403 | gtk_drag_finish(drag_context, TRUE, FALSE, time); 404 | } 405 | 406 | 407 | /* input handling */ 408 | 409 | 410 | static void 411 | handle_popup(GtkWidget *widget, GdkEventButton *event) 412 | { 413 | GtkWidget *popup_menu; 414 | g_object_get(G_OBJECT(widget), "user-data", &popup_menu, NULL); 415 | gtk_widget_show_all(popup_menu); 416 | gtk_menu_popup(GTK_MENU(popup_menu), NULL, NULL, NULL, NULL, 417 | (event != NULL) ? event->button : 0, 418 | gdk_event_get_time((GdkEvent *)event)); 419 | } 420 | 421 | static gboolean 422 | handle_button_press(GtkWidget *widget, GdkEventButton *event, gpointer *unused) 423 | { 424 | (void)unused; 425 | 426 | if (event->type == GDK_BUTTON_PRESS) { 427 | if (event->button == 1) { 428 | gtk_window_begin_move_drag(GTK_WINDOW(widget), 429 | (int)event->button, (int)event->x_root, 430 | (int)event->y_root, event->time); 431 | } else if (event->button == 3) { 432 | handle_popup(widget, event); 433 | } 434 | } 435 | 436 | return FALSE; 437 | } 438 | 439 | 440 | /* logo drawing */ 441 | 442 | static double rotation_state; 443 | static gboolean rotation_active; 444 | 445 | static gboolean 446 | rotate_logo(GtkWidget *widget) 447 | { 448 | rotation_state += G_PI / 20; 449 | if (rotation_state >= 2.0 * G_PI) { 450 | rotation_state = 0.0; 451 | } 452 | gtk_widget_queue_draw(widget); 453 | if (!rotation_active) { 454 | rotation_state = 0.0; 455 | gtk_widget_queue_draw(widget); 456 | } 457 | return rotation_active; 458 | } 459 | 460 | #define DRAW_WIDTH 130.0 461 | #define DRAW_HEIGHT 115.0 462 | 463 | static gboolean 464 | handle_expose(GtkWidget *widget, GdkEventExpose *event, RsvgHandle *rh) 465 | { 466 | static double scale_factor; 467 | static RsvgDimensionData rdd; 468 | 469 | double new_width; 470 | double new_height; 471 | cairo_t *cr = gdk_cairo_create(widget->window); 472 | (void)event; 473 | 474 | if (scale_factor <= 0.0) { 475 | #pragma GCC diagnostic push 476 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 477 | rsvg_handle_get_dimensions(rh, &rdd); 478 | #pragma GCC diagnostic pop 479 | scale_factor = 0.8 * 480 | MIN(DRAW_HEIGHT / rdd.width, DRAW_HEIGHT / rdd.height); 481 | } 482 | 483 | new_width = scale_factor * rdd.width; 484 | new_height = scale_factor * rdd.height; 485 | 486 | cairo_translate(cr, DRAW_WIDTH / 2.0, DRAW_HEIGHT / 2.0); 487 | cairo_rotate(cr, rotation_state); 488 | cairo_translate(cr, -DRAW_WIDTH / 2.0, -DRAW_HEIGHT / 2.0); 489 | 490 | cairo_translate(cr, (DRAW_WIDTH - new_width) / 2.0, 491 | (DRAW_HEIGHT - new_height) / 2.0); 492 | cairo_scale(cr, scale_factor, scale_factor); 493 | 494 | #pragma GCC diagnostic push 495 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 496 | rsvg_handle_render_cairo(rh, cr); 497 | #pragma GCC diagnostic pop 498 | 499 | cairo_destroy(cr); 500 | return TRUE; 501 | } 502 | 503 | static gpointer 504 | update_bar(gpointer arg) 505 | { 506 | GtkWidget *widget = arg; 507 | 508 | double frac; 509 | double new_frac; 510 | GtkWidget *vbox = gtk_bin_get_child(GTK_BIN(widget)); 511 | GList *children = gtk_container_get_children(GTK_CONTAINER(vbox)); 512 | GtkWidget *progress_bar = GTK_IS_PROGRESS_BAR(children->data) ? 513 | children->data : 514 | g_list_next(children)->data; 515 | g_list_free(children); 516 | 517 | for (;;) { 518 | g_mutex_lock(&progress_mutex); 519 | g_cond_wait(&progress_cond, &progress_mutex); 520 | if (total_frames > 0) { 521 | gdk_threads_enter(); 522 | if (!rotation_active && elapsed_frames) { 523 | g_timeout_add(40, (GSourceFunc)rotate_logo, 524 | widget); 525 | rotation_active = TRUE; 526 | } 527 | frac = gtk_progress_bar_get_fraction( 528 | GTK_PROGRESS_BAR(progress_bar)); 529 | new_frac = CLAMP((double)elapsed_frames / 530 | (double)total_frames, 531 | 0.0, 1.0); 532 | if (ABS(frac - new_frac) > 1.0 / DRAW_WIDTH) { 533 | gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR( 534 | progress_bar), 535 | new_frac); 536 | } 537 | if (total_frames == elapsed_frames) { 538 | gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR( 539 | progress_bar), 540 | 0.0); 541 | rotation_active = FALSE; 542 | } 543 | gdk_threads_leave(); 544 | } 545 | g_mutex_unlock(&progress_mutex); 546 | } 547 | 548 | return NULL; 549 | } 550 | 551 | 552 | static GtkActionEntry action_entries[] = { { "FileMenuAction", NULL, "_File", 553 | NULL, NULL, NULL }, 554 | 555 | { "QuitAction", GTK_STOCK_QUIT, "_Quit", "Q", "Quit", 556 | G_CALLBACK(exit_program) } }; 557 | 558 | static char const *ui = "" 559 | " " 560 | " " 561 | " " 562 | " " 563 | " " 564 | " " 565 | ""; 566 | 567 | 568 | int 569 | main(int argc, char *argv[]) 570 | { 571 | GtkWidget *window; 572 | GtkWidget *vbox; 573 | GtkWidget *drawing_area; 574 | GtkWidget *progress_bar; 575 | GtkActionGroup *action_group; 576 | GtkUIManager *menu_manager; 577 | GError *error; 578 | RsvgHandle *rh; 579 | 580 | /* initialization */ 581 | gdk_threads_init(); 582 | gdk_threads_enter(); 583 | gtk_init(&argc, &argv); 584 | input_init(argv[0], NULL); 585 | scanner_init_common(); 586 | setlocale(LC_COLLATE, ""); 587 | setlocale(LC_CTYPE, ""); 588 | 589 | /* set up widgets */ 590 | window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 591 | vbox = gtk_vbox_new(FALSE, 0); 592 | drawing_area = gtk_drawing_area_new(); 593 | progress_bar = gtk_progress_bar_new(); 594 | action_group = gtk_action_group_new("Actions"); 595 | menu_manager = gtk_ui_manager_new(); 596 | 597 | /* set up window */ 598 | gtk_window_set_title(GTK_WINDOW(window), "Loudness Drop"); 599 | gtk_window_set_decorated(GTK_WINDOW(window), FALSE); 600 | gtk_window_set_default_size(GTK_WINDOW(window), 130, 130); 601 | gtk_window_set_resizable(GTK_WINDOW(window), FALSE); 602 | gtk_window_set_keep_above(GTK_WINDOW(window), TRUE); 603 | gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK); 604 | gtk_drag_dest_set(window, GTK_DEST_DEFAULT_ALL, NULL, 0, 605 | GDK_ACTION_COPY); 606 | gtk_drag_dest_add_uri_targets(window); 607 | g_signal_connect(window, "delete-event", exit_program, NULL); 608 | g_signal_connect(window, "destroy", exit_program, NULL); 609 | g_signal_connect(window, "button-press-event", 610 | G_CALLBACK(handle_button_press), NULL); 611 | g_signal_connect(window, "popup-menu", G_CALLBACK(handle_popup), NULL); 612 | g_signal_connect(window, "drag-data-received", 613 | G_CALLBACK(handle_data_received), NULL); 614 | 615 | /* set up vbox */ 616 | gtk_container_add(GTK_CONTAINER(window), vbox); 617 | gtk_box_pack_start(GTK_BOX(vbox), drawing_area, TRUE, TRUE, 0); 618 | gtk_box_pack_start(GTK_BOX(vbox), progress_bar, TRUE, TRUE, 0); 619 | 620 | /* set up drawing area */ 621 | gtk_widget_set_size_request(drawing_area, (int)DRAW_WIDTH, 622 | (int)DRAW_HEIGHT); 623 | rh = rsvg_handle_new_from_data(test_svg, test_svg_len, NULL); 624 | g_signal_connect(drawing_area, "expose-event", 625 | G_CALLBACK(handle_expose), rh); 626 | 627 | /* set up progress bar */ 628 | gtk_widget_set_size_request(progress_bar, 130, 15); 629 | 630 | /* set up bar thread */ 631 | g_thread_unref(g_thread_new(NULL, update_bar, window)); 632 | 633 | /* set up action group */ 634 | gtk_action_group_add_actions(action_group, action_entries, 635 | G_N_ELEMENTS(action_entries), NULL); 636 | 637 | /* set up menu manager */ 638 | gtk_ui_manager_insert_action_group(menu_manager, action_group, 0); 639 | error = NULL; 640 | gtk_ui_manager_add_ui_from_string(menu_manager, ui, -1, &error); 641 | if (error) { 642 | g_message("building menus failed: %s", error->message); 643 | g_error_free(error); 644 | } 645 | gtk_window_add_accel_group(GTK_WINDOW(window), 646 | gtk_ui_manager_get_accel_group(menu_manager)); 647 | g_object_set(window, "user-data", 648 | gtk_menu_item_get_submenu(GTK_MENU_ITEM( 649 | gtk_ui_manager_get_widget(menu_manager, "/MainMenu/FileMenu"))), 650 | NULL); 651 | 652 | gtk_widget_show_all(window); 653 | 654 | gtk_main(); 655 | gdk_threads_leave(); 656 | return 0; 657 | } 658 | -------------------------------------------------------------------------------- /doc/license/ffmpeg.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | --------------------------------------------------------------------------------