diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-09-05 13:14:37 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-09-05 13:14:37 +0000 |
commit | fd3b2704efc2b206784615c1a23eb25501842259 (patch) | |
tree | 61ba3a8af2a0ae2ac9ec362bbf18b038f5dc0448 /src/share | |
parent | Initial commit. (diff) | |
download | flac-upstream.tar.xz flac-upstream.zip |
Adding upstream version 1.4.3+ds.upstream/1.4.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/share')
28 files changed, 7507 insertions, 0 deletions
diff --git a/src/share/Makefile.am b/src/share/Makefile.am new file mode 100644 index 0000000..caf6122 --- /dev/null +++ b/src/share/Makefile.am @@ -0,0 +1,73 @@ +# FLAC - Free Lossless Audio Codec +# Copyright (C) 2002-2009 Josh Coalson +# Copyright (C) 2011-2023 Xiph.Org Foundation +# +# This file is part the FLAC project. FLAC is comprised of several +# components distributed under different licenses. The codec libraries +# are distributed under Xiph.Org's BSD-like license (see the file +# COPYING.Xiph in this distribution). All other programs, libraries, and +# plugins are distributed under the GPL (see COPYING.GPL). The documentation +# is distributed under the Gnu FDL (see COPYING.FDL). Each file in the +# FLAC distribution contains at the top the terms under which it may be +# distributed. +# +# Since this particular file is relevant to all components of FLAC, +# it may be distributed under the Xiph.Org license, which is the least +# restrictive of those mentioned above. See the file COPYING.Xiph in this +# distribution. + +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = -I$(top_builddir) -I$(srcdir)/include -I$(top_srcdir)/include + +EXTRA_DIST = \ + README \ + getopt/CMakeLists.txt \ + grabbag/CMakeLists.txt \ + replaygain_analysis/CMakeLists.txt \ + replaygain_synthesis/CMakeLists.txt \ + utf8/CMakeLists.txt \ + utf8/charmaps.h \ + utf8/makemap.c \ + utf8/charset_test.c + + +noinst_LTLIBRARIES = \ + getopt/libgetopt.la \ + grabbag/libgrabbag.la \ + utf8/libutf8.la \ + $(libwin_utf8_io) \ + replaygain_analysis/libreplaygain_analysis.la \ + replaygain_synthesis/libreplaygain_synthesis.la + + +if OS_IS_WINDOWS +win_utf8_io_libwin_utf8_io_la_SOURCES = win_utf8_io/win_utf8_io.c +libwin_utf8_io = win_utf8_io/libwin_utf8_io.la +else +win_utf8_io_libwin_utf8_io_la_SOURCES = +libwin_utf8_io = +endif + +getopt_libgetopt_la_SOURCES = getopt/getopt.c getopt/getopt1.c + +grabbag_libgrabbag_la_SOURCES = \ + grabbag/alloc.c \ + grabbag/cuesheet.c \ + grabbag/file.c \ + grabbag/picture.c \ + grabbag/replaygain.c \ + grabbag/seektable.c \ + grabbag/snprintf.c + +utf8_libutf8_la_SOURCES = \ + utf8/charset.c \ + utf8/charset.h \ + utf8/iconvert.c \ + utf8/iconvert.h \ + utf8/utf8.c + +replaygain_analysis_libreplaygain_analysis_la_SOURCES = replaygain_analysis/replaygain_analysis.c + +replaygain_synthesis_libreplaygain_synthesis_la_CFLAGS = -I $(top_srcdir)/src/share/replaygain_synthesis/include +replaygain_synthesis_libreplaygain_synthesis_la_SOURCES = replaygain_synthesis/replaygain_synthesis.c diff --git a/src/share/Makefile.in b/src/share/Makefile.in new file mode 100644 index 0000000..a944998 --- /dev/null +++ b/src/share/Makefile.in @@ -0,0 +1,905 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# FLAC - Free Lossless Audio Codec +# Copyright (C) 2002-2009 Josh Coalson +# Copyright (C) 2011-2023 Xiph.Org Foundation +# +# This file is part the FLAC project. FLAC is comprised of several +# components distributed under different licenses. The codec libraries +# are distributed under Xiph.Org's BSD-like license (see the file +# COPYING.Xiph in this distribution). All other programs, libraries, and +# plugins are distributed under the GPL (see COPYING.GPL). The documentation +# is distributed under the Gnu FDL (see COPYING.FDL). Each file in the +# FLAC distribution contains at the top the terms under which it may be +# distributed. +# +# Since this particular file is relevant to all components of FLAC, +# it may be distributed under the Xiph.Org license, which is the least +# restrictive of those mentioned above. See the file COPYING.Xiph in this +# distribution. + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/share +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/add_cflags.m4 \ + $(top_srcdir)/m4/add_cxxflags.m4 \ + $(top_srcdir)/m4/ax_add_fortify_source.m4 \ + $(top_srcdir)/m4/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_check_enable_debug.m4 \ + $(top_srcdir)/m4/bswap.m4 $(top_srcdir)/m4/clang.m4 \ + $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/gcc_version.m4 \ + $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/ogg.m4 \ + $(top_srcdir)/m4/really_gcc.m4 \ + $(top_srcdir)/m4/stack_protect.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +getopt_libgetopt_la_LIBADD = +am__dirstamp = $(am__leading_dot)dirstamp +am_getopt_libgetopt_la_OBJECTS = getopt/getopt.lo getopt/getopt1.lo +getopt_libgetopt_la_OBJECTS = $(am_getopt_libgetopt_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +grabbag_libgrabbag_la_LIBADD = +am_grabbag_libgrabbag_la_OBJECTS = grabbag/alloc.lo \ + grabbag/cuesheet.lo grabbag/file.lo grabbag/picture.lo \ + grabbag/replaygain.lo grabbag/seektable.lo grabbag/snprintf.lo +grabbag_libgrabbag_la_OBJECTS = $(am_grabbag_libgrabbag_la_OBJECTS) +replaygain_analysis_libreplaygain_analysis_la_LIBADD = +am_replaygain_analysis_libreplaygain_analysis_la_OBJECTS = \ + replaygain_analysis/replaygain_analysis.lo +replaygain_analysis_libreplaygain_analysis_la_OBJECTS = \ + $(am_replaygain_analysis_libreplaygain_analysis_la_OBJECTS) +replaygain_synthesis_libreplaygain_synthesis_la_LIBADD = +am_replaygain_synthesis_libreplaygain_synthesis_la_OBJECTS = replaygain_synthesis/libreplaygain_synthesis_la-replaygain_synthesis.lo +replaygain_synthesis_libreplaygain_synthesis_la_OBJECTS = \ + $(am_replaygain_synthesis_libreplaygain_synthesis_la_OBJECTS) +replaygain_synthesis_libreplaygain_synthesis_la_LINK = $(LIBTOOL) \ + $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) \ + $(replaygain_synthesis_libreplaygain_synthesis_la_CFLAGS) \ + $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +utf8_libutf8_la_LIBADD = +am_utf8_libutf8_la_OBJECTS = utf8/charset.lo utf8/iconvert.lo \ + utf8/utf8.lo +utf8_libutf8_la_OBJECTS = $(am_utf8_libutf8_la_OBJECTS) +win_utf8_io_libwin_utf8_io_la_LIBADD = +am__win_utf8_io_libwin_utf8_io_la_SOURCES_DIST = \ + win_utf8_io/win_utf8_io.c +@OS_IS_WINDOWS_TRUE@am_win_utf8_io_libwin_utf8_io_la_OBJECTS = \ +@OS_IS_WINDOWS_TRUE@ win_utf8_io/win_utf8_io.lo +win_utf8_io_libwin_utf8_io_la_OBJECTS = \ + $(am_win_utf8_io_libwin_utf8_io_la_OBJECTS) +@OS_IS_WINDOWS_TRUE@am_win_utf8_io_libwin_utf8_io_la_rpath = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = getopt/$(DEPDIR)/getopt.Plo \ + getopt/$(DEPDIR)/getopt1.Plo grabbag/$(DEPDIR)/alloc.Plo \ + grabbag/$(DEPDIR)/cuesheet.Plo grabbag/$(DEPDIR)/file.Plo \ + grabbag/$(DEPDIR)/picture.Plo grabbag/$(DEPDIR)/replaygain.Plo \ + grabbag/$(DEPDIR)/seektable.Plo grabbag/$(DEPDIR)/snprintf.Plo \ + replaygain_analysis/$(DEPDIR)/replaygain_analysis.Plo \ + replaygain_synthesis/$(DEPDIR)/libreplaygain_synthesis_la-replaygain_synthesis.Plo \ + utf8/$(DEPDIR)/charset.Plo utf8/$(DEPDIR)/iconvert.Plo \ + utf8/$(DEPDIR)/utf8.Plo win_utf8_io/$(DEPDIR)/win_utf8_io.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(getopt_libgetopt_la_SOURCES) \ + $(grabbag_libgrabbag_la_SOURCES) \ + $(replaygain_analysis_libreplaygain_analysis_la_SOURCES) \ + $(replaygain_synthesis_libreplaygain_synthesis_la_SOURCES) \ + $(utf8_libutf8_la_SOURCES) \ + $(win_utf8_io_libwin_utf8_io_la_SOURCES) +DIST_SOURCES = $(getopt_libgetopt_la_SOURCES) \ + $(grabbag_libgrabbag_la_SOURCES) \ + $(replaygain_analysis_libreplaygain_analysis_la_SOURCES) \ + $(replaygain_synthesis_libreplaygain_synthesis_la_SOURCES) \ + $(utf8_libutf8_la_SOURCES) \ + $(am__win_utf8_io_libwin_utf8_io_la_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp README +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCAS = @CCAS@ +CCASDEPMODE = @CCASDEPMODE@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DOXYGEN = @DOXYGEN@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENABLE_64_BIT_WORDS = @ENABLE_64_BIT_WORDS@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLAC__HAS_OGG = @FLAC__HAS_OGG@ +FLAC__TEST_LEVEL = @FLAC__TEST_LEVEL@ +FLAC__TEST_WITH_VALGRIND = @FLAC__TEST_WITH_VALGRIND@ +GCC_MAJOR_VERSION = @GCC_MAJOR_VERSION@ +GCC_MINOR_VERSION = @GCC_MINOR_VERSION@ +GCC_VERSION = @GCC_VERSION@ +GIT_COMMIT_VERSION_HASH = @GIT_COMMIT_VERSION_HASH@ +GIT_FOUND = @GIT_FOUND@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIB_CLOCK_GETTIME = @LIB_CLOCK_GETTIME@ +LIB_FUZZING_ENGINE = @LIB_FUZZING_ENGINE@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OBJ_FORMAT = @OBJ_FORMAT@ +OGG_CFLAGS = @OGG_CFLAGS@ +OGG_LIBS = @OGG_LIBS@ +OGG_PACKAGE = @OGG_PACKAGE@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +RC = @RC@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = subdir-objects +AM_CPPFLAGS = -I$(top_builddir) -I$(srcdir)/include -I$(top_srcdir)/include +EXTRA_DIST = \ + README \ + getopt/CMakeLists.txt \ + grabbag/CMakeLists.txt \ + replaygain_analysis/CMakeLists.txt \ + replaygain_synthesis/CMakeLists.txt \ + utf8/CMakeLists.txt \ + utf8/charmaps.h \ + utf8/makemap.c \ + utf8/charset_test.c + +noinst_LTLIBRARIES = \ + getopt/libgetopt.la \ + grabbag/libgrabbag.la \ + utf8/libutf8.la \ + $(libwin_utf8_io) \ + replaygain_analysis/libreplaygain_analysis.la \ + replaygain_synthesis/libreplaygain_synthesis.la + +@OS_IS_WINDOWS_FALSE@win_utf8_io_libwin_utf8_io_la_SOURCES = +@OS_IS_WINDOWS_TRUE@win_utf8_io_libwin_utf8_io_la_SOURCES = win_utf8_io/win_utf8_io.c +@OS_IS_WINDOWS_FALSE@libwin_utf8_io = +@OS_IS_WINDOWS_TRUE@libwin_utf8_io = win_utf8_io/libwin_utf8_io.la +getopt_libgetopt_la_SOURCES = getopt/getopt.c getopt/getopt1.c +grabbag_libgrabbag_la_SOURCES = \ + grabbag/alloc.c \ + grabbag/cuesheet.c \ + grabbag/file.c \ + grabbag/picture.c \ + grabbag/replaygain.c \ + grabbag/seektable.c \ + grabbag/snprintf.c + +utf8_libutf8_la_SOURCES = \ + utf8/charset.c \ + utf8/charset.h \ + utf8/iconvert.c \ + utf8/iconvert.h \ + utf8/utf8.c + +replaygain_analysis_libreplaygain_analysis_la_SOURCES = replaygain_analysis/replaygain_analysis.c +replaygain_synthesis_libreplaygain_synthesis_la_CFLAGS = -I $(top_srcdir)/src/share/replaygain_synthesis/include +replaygain_synthesis_libreplaygain_synthesis_la_SOURCES = replaygain_synthesis/replaygain_synthesis.c +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/share/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/share/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } +getopt/$(am__dirstamp): + @$(MKDIR_P) getopt + @: > getopt/$(am__dirstamp) +getopt/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) getopt/$(DEPDIR) + @: > getopt/$(DEPDIR)/$(am__dirstamp) +getopt/getopt.lo: getopt/$(am__dirstamp) \ + getopt/$(DEPDIR)/$(am__dirstamp) +getopt/getopt1.lo: getopt/$(am__dirstamp) \ + getopt/$(DEPDIR)/$(am__dirstamp) + +getopt/libgetopt.la: $(getopt_libgetopt_la_OBJECTS) $(getopt_libgetopt_la_DEPENDENCIES) $(EXTRA_getopt_libgetopt_la_DEPENDENCIES) getopt/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(getopt_libgetopt_la_OBJECTS) $(getopt_libgetopt_la_LIBADD) $(LIBS) +grabbag/$(am__dirstamp): + @$(MKDIR_P) grabbag + @: > grabbag/$(am__dirstamp) +grabbag/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) grabbag/$(DEPDIR) + @: > grabbag/$(DEPDIR)/$(am__dirstamp) +grabbag/alloc.lo: grabbag/$(am__dirstamp) \ + grabbag/$(DEPDIR)/$(am__dirstamp) +grabbag/cuesheet.lo: grabbag/$(am__dirstamp) \ + grabbag/$(DEPDIR)/$(am__dirstamp) +grabbag/file.lo: grabbag/$(am__dirstamp) \ + grabbag/$(DEPDIR)/$(am__dirstamp) +grabbag/picture.lo: grabbag/$(am__dirstamp) \ + grabbag/$(DEPDIR)/$(am__dirstamp) +grabbag/replaygain.lo: grabbag/$(am__dirstamp) \ + grabbag/$(DEPDIR)/$(am__dirstamp) +grabbag/seektable.lo: grabbag/$(am__dirstamp) \ + grabbag/$(DEPDIR)/$(am__dirstamp) +grabbag/snprintf.lo: grabbag/$(am__dirstamp) \ + grabbag/$(DEPDIR)/$(am__dirstamp) + +grabbag/libgrabbag.la: $(grabbag_libgrabbag_la_OBJECTS) $(grabbag_libgrabbag_la_DEPENDENCIES) $(EXTRA_grabbag_libgrabbag_la_DEPENDENCIES) grabbag/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(grabbag_libgrabbag_la_OBJECTS) $(grabbag_libgrabbag_la_LIBADD) $(LIBS) +replaygain_analysis/$(am__dirstamp): + @$(MKDIR_P) replaygain_analysis + @: > replaygain_analysis/$(am__dirstamp) +replaygain_analysis/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) replaygain_analysis/$(DEPDIR) + @: > replaygain_analysis/$(DEPDIR)/$(am__dirstamp) +replaygain_analysis/replaygain_analysis.lo: \ + replaygain_analysis/$(am__dirstamp) \ + replaygain_analysis/$(DEPDIR)/$(am__dirstamp) + +replaygain_analysis/libreplaygain_analysis.la: $(replaygain_analysis_libreplaygain_analysis_la_OBJECTS) $(replaygain_analysis_libreplaygain_analysis_la_DEPENDENCIES) $(EXTRA_replaygain_analysis_libreplaygain_analysis_la_DEPENDENCIES) replaygain_analysis/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(replaygain_analysis_libreplaygain_analysis_la_OBJECTS) $(replaygain_analysis_libreplaygain_analysis_la_LIBADD) $(LIBS) +replaygain_synthesis/$(am__dirstamp): + @$(MKDIR_P) replaygain_synthesis + @: > replaygain_synthesis/$(am__dirstamp) +replaygain_synthesis/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) replaygain_synthesis/$(DEPDIR) + @: > replaygain_synthesis/$(DEPDIR)/$(am__dirstamp) +replaygain_synthesis/libreplaygain_synthesis_la-replaygain_synthesis.lo: \ + replaygain_synthesis/$(am__dirstamp) \ + replaygain_synthesis/$(DEPDIR)/$(am__dirstamp) + +replaygain_synthesis/libreplaygain_synthesis.la: $(replaygain_synthesis_libreplaygain_synthesis_la_OBJECTS) $(replaygain_synthesis_libreplaygain_synthesis_la_DEPENDENCIES) $(EXTRA_replaygain_synthesis_libreplaygain_synthesis_la_DEPENDENCIES) replaygain_synthesis/$(am__dirstamp) + $(AM_V_CCLD)$(replaygain_synthesis_libreplaygain_synthesis_la_LINK) $(replaygain_synthesis_libreplaygain_synthesis_la_OBJECTS) $(replaygain_synthesis_libreplaygain_synthesis_la_LIBADD) $(LIBS) +utf8/$(am__dirstamp): + @$(MKDIR_P) utf8 + @: > utf8/$(am__dirstamp) +utf8/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) utf8/$(DEPDIR) + @: > utf8/$(DEPDIR)/$(am__dirstamp) +utf8/charset.lo: utf8/$(am__dirstamp) utf8/$(DEPDIR)/$(am__dirstamp) +utf8/iconvert.lo: utf8/$(am__dirstamp) utf8/$(DEPDIR)/$(am__dirstamp) +utf8/utf8.lo: utf8/$(am__dirstamp) utf8/$(DEPDIR)/$(am__dirstamp) + +utf8/libutf8.la: $(utf8_libutf8_la_OBJECTS) $(utf8_libutf8_la_DEPENDENCIES) $(EXTRA_utf8_libutf8_la_DEPENDENCIES) utf8/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(utf8_libutf8_la_OBJECTS) $(utf8_libutf8_la_LIBADD) $(LIBS) +win_utf8_io/$(am__dirstamp): + @$(MKDIR_P) win_utf8_io + @: > win_utf8_io/$(am__dirstamp) +win_utf8_io/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) win_utf8_io/$(DEPDIR) + @: > win_utf8_io/$(DEPDIR)/$(am__dirstamp) +win_utf8_io/win_utf8_io.lo: win_utf8_io/$(am__dirstamp) \ + win_utf8_io/$(DEPDIR)/$(am__dirstamp) + +win_utf8_io/libwin_utf8_io.la: $(win_utf8_io_libwin_utf8_io_la_OBJECTS) $(win_utf8_io_libwin_utf8_io_la_DEPENDENCIES) $(EXTRA_win_utf8_io_libwin_utf8_io_la_DEPENDENCIES) win_utf8_io/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(am_win_utf8_io_libwin_utf8_io_la_rpath) $(win_utf8_io_libwin_utf8_io_la_OBJECTS) $(win_utf8_io_libwin_utf8_io_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f getopt/*.$(OBJEXT) + -rm -f getopt/*.lo + -rm -f grabbag/*.$(OBJEXT) + -rm -f grabbag/*.lo + -rm -f replaygain_analysis/*.$(OBJEXT) + -rm -f replaygain_analysis/*.lo + -rm -f replaygain_synthesis/*.$(OBJEXT) + -rm -f replaygain_synthesis/*.lo + -rm -f utf8/*.$(OBJEXT) + -rm -f utf8/*.lo + -rm -f win_utf8_io/*.$(OBJEXT) + -rm -f win_utf8_io/*.lo + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@getopt/$(DEPDIR)/getopt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@getopt/$(DEPDIR)/getopt1.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@grabbag/$(DEPDIR)/alloc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@grabbag/$(DEPDIR)/cuesheet.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@grabbag/$(DEPDIR)/file.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@grabbag/$(DEPDIR)/picture.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@grabbag/$(DEPDIR)/replaygain.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@grabbag/$(DEPDIR)/seektable.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@grabbag/$(DEPDIR)/snprintf.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@replaygain_analysis/$(DEPDIR)/replaygain_analysis.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@replaygain_synthesis/$(DEPDIR)/libreplaygain_synthesis_la-replaygain_synthesis.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@utf8/$(DEPDIR)/charset.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@utf8/$(DEPDIR)/iconvert.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@utf8/$(DEPDIR)/utf8.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@win_utf8_io/$(DEPDIR)/win_utf8_io.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +replaygain_synthesis/libreplaygain_synthesis_la-replaygain_synthesis.lo: replaygain_synthesis/replaygain_synthesis.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(replaygain_synthesis_libreplaygain_synthesis_la_CFLAGS) $(CFLAGS) -MT replaygain_synthesis/libreplaygain_synthesis_la-replaygain_synthesis.lo -MD -MP -MF replaygain_synthesis/$(DEPDIR)/libreplaygain_synthesis_la-replaygain_synthesis.Tpo -c -o replaygain_synthesis/libreplaygain_synthesis_la-replaygain_synthesis.lo `test -f 'replaygain_synthesis/replaygain_synthesis.c' || echo '$(srcdir)/'`replaygain_synthesis/replaygain_synthesis.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) replaygain_synthesis/$(DEPDIR)/libreplaygain_synthesis_la-replaygain_synthesis.Tpo replaygain_synthesis/$(DEPDIR)/libreplaygain_synthesis_la-replaygain_synthesis.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='replaygain_synthesis/replaygain_synthesis.c' object='replaygain_synthesis/libreplaygain_synthesis_la-replaygain_synthesis.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(replaygain_synthesis_libreplaygain_synthesis_la_CFLAGS) $(CFLAGS) -c -o replaygain_synthesis/libreplaygain_synthesis_la-replaygain_synthesis.lo `test -f 'replaygain_synthesis/replaygain_synthesis.c' || echo '$(srcdir)/'`replaygain_synthesis/replaygain_synthesis.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf getopt/.libs getopt/_libs + -rm -rf grabbag/.libs grabbag/_libs + -rm -rf replaygain_analysis/.libs replaygain_analysis/_libs + -rm -rf replaygain_synthesis/.libs replaygain_synthesis/_libs + -rm -rf utf8/.libs utf8/_libs + -rm -rf win_utf8_io/.libs win_utf8_io/_libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f getopt/$(DEPDIR)/$(am__dirstamp) + -rm -f getopt/$(am__dirstamp) + -rm -f grabbag/$(DEPDIR)/$(am__dirstamp) + -rm -f grabbag/$(am__dirstamp) + -rm -f replaygain_analysis/$(DEPDIR)/$(am__dirstamp) + -rm -f replaygain_analysis/$(am__dirstamp) + -rm -f replaygain_synthesis/$(DEPDIR)/$(am__dirstamp) + -rm -f replaygain_synthesis/$(am__dirstamp) + -rm -f utf8/$(DEPDIR)/$(am__dirstamp) + -rm -f utf8/$(am__dirstamp) + -rm -f win_utf8_io/$(DEPDIR)/$(am__dirstamp) + -rm -f win_utf8_io/$(am__dirstamp) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f getopt/$(DEPDIR)/getopt.Plo + -rm -f getopt/$(DEPDIR)/getopt1.Plo + -rm -f grabbag/$(DEPDIR)/alloc.Plo + -rm -f grabbag/$(DEPDIR)/cuesheet.Plo + -rm -f grabbag/$(DEPDIR)/file.Plo + -rm -f grabbag/$(DEPDIR)/picture.Plo + -rm -f grabbag/$(DEPDIR)/replaygain.Plo + -rm -f grabbag/$(DEPDIR)/seektable.Plo + -rm -f grabbag/$(DEPDIR)/snprintf.Plo + -rm -f replaygain_analysis/$(DEPDIR)/replaygain_analysis.Plo + -rm -f replaygain_synthesis/$(DEPDIR)/libreplaygain_synthesis_la-replaygain_synthesis.Plo + -rm -f utf8/$(DEPDIR)/charset.Plo + -rm -f utf8/$(DEPDIR)/iconvert.Plo + -rm -f utf8/$(DEPDIR)/utf8.Plo + -rm -f win_utf8_io/$(DEPDIR)/win_utf8_io.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f getopt/$(DEPDIR)/getopt.Plo + -rm -f getopt/$(DEPDIR)/getopt1.Plo + -rm -f grabbag/$(DEPDIR)/alloc.Plo + -rm -f grabbag/$(DEPDIR)/cuesheet.Plo + -rm -f grabbag/$(DEPDIR)/file.Plo + -rm -f grabbag/$(DEPDIR)/picture.Plo + -rm -f grabbag/$(DEPDIR)/replaygain.Plo + -rm -f grabbag/$(DEPDIR)/seektable.Plo + -rm -f grabbag/$(DEPDIR)/snprintf.Plo + -rm -f replaygain_analysis/$(DEPDIR)/replaygain_analysis.Plo + -rm -f replaygain_synthesis/$(DEPDIR)/libreplaygain_synthesis_la-replaygain_synthesis.Plo + -rm -f utf8/$(DEPDIR)/charset.Plo + -rm -f utf8/$(DEPDIR)/iconvert.Plo + -rm -f utf8/$(DEPDIR)/utf8.Plo + -rm -f win_utf8_io/$(DEPDIR)/win_utf8_io.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/share/README b/src/share/README new file mode 100644 index 0000000..1d4fede --- /dev/null +++ b/src/share/README @@ -0,0 +1,5 @@ +This directory contains several convenience libraries used by the rest of the +tools and plugins. Two of them (getopt and utf8) are shamelessly copied from +vorbistools, one for manipulating UTF-8 strings (GPL) and one for implementing +getopt (LGPL). libFLAC does not link to either; the only FLAC tools that do +are GPL'ed. diff --git a/src/share/getopt/CMakeLists.txt b/src/share/getopt/CMakeLists.txt new file mode 100644 index 0000000..d905615 --- /dev/null +++ b/src/share/getopt/CMakeLists.txt @@ -0,0 +1,13 @@ +check_include_file("string.h" HAVE_STRING_H) + +if(NOT WIN32) + find_package(Intl) +endif() + +add_library(getopt STATIC getopt.c getopt1.c) + +if(Intl_FOUND) + target_include_directories(getopt PRIVATE ${Intl_INCLUDE_DIRS}) + target_link_libraries(getopt PUBLIC ${Intl_LIBRARIES}) + target_compile_definitions(getopt PRIVATE HAVE_LIBINTL_H) +endif() diff --git a/src/share/getopt/getopt.c b/src/share/getopt/getopt.c new file mode 100644 index 0000000..39fab80 --- /dev/null +++ b/src/share/getopt/getopt.c @@ -0,0 +1,1053 @@ +/* + NOTE: + I cannot get the vanilla getopt code to work (i.e. compile only what + is needed and not duplicate symbols found in the standard library) + on all the platforms that FLAC supports. In particular the gating + of code with the ELIDE_CODE #define is not accurate enough on systems + that are POSIX but not glibc. If someone has a patch that works on + GNU/Linux, Darwin, AND Solaris please submit it on the project page: + https://sourceforge.net/p/flac/patches/ + + In the meantime I have munged the global symbols and removed gates + around code, while at the same time trying to touch the original as + little as possible. +*/ +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to drepper@gnu.org + before changing it! + + Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 + Free Software Foundation, Inc. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. + Ditto for AIX 3.2 and <stdlib.h>. */ +#ifndef _NO_PROTO +# define _NO_PROTO +#endif + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +# ifndef const +# define const +# endif +#endif + +#include <stdio.h> + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 +#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 +# include <gnu-versions.h> +# if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +# define ELIDE_CODE +# endif +#endif + +#if 1 +/*[JEC] was:#ifndef ELIDE_CODE*/ + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +# include <stdlib.h> +# include <unistd.h> +#endif /* GNU C library. */ + +#ifdef VMS +# include <unixlib.h> +# ifdef HAVE_STRING_H +# include <string.h> +# endif +#endif + +#ifndef _ +/* This is for other GNU distributions with internationalized messages. + When compiling libc, the _ macro is predefined. */ +# ifdef HAVE_LIBINTL_H +# include <libintl.h> +# define _(msgid) gettext (msgid) +# else +# define _(msgid) (msgid) +# endif +#endif + +/* This version of `share__getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `share__getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "share/getopt.h" +/*[JEC] was:#include "getopt.h"*/ + +/* For communication from `share__getopt' to the caller. + When `share__getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *share__optarg = 0; /*[JEC] initialize to avoid being a 'Common' symbol */ + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `share__getopt'. + + On entry to `share__getopt', zero means this is the first call; initialize. + + When `share__getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `share__optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* 1003.2 says this must be 1 before any call. */ +int share__optind = 1; + +/* Formerly, initialization of getopt depended on share__optind==0, which + causes problems with re-calling getopt as programs generally don't + know that. */ + +static int share____getopt_initialized = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int share__opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int share__optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `share__getopt' to return -1 with `share__optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +# include <string.h> +# define my_index strchr +#else + +#include <string.h> + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +#ifndef getenv +extern char *getenv (const char * name); +#endif + +static char * +my_index (const char *str, int chr) +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +/* If using GCC, we can safely declare strlen this way. + If not using GCC, it is ok not to declare it. */ +#ifdef __GNUC__ +/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. + That was relevant to code that was here before. */ +# if (!defined __STDC__ || !__STDC__) && !defined strlen +/* gcc with -traditional declares the built-in strlen to return int, + and has done so at least since version 2.4.5. -- rms. */ +extern int strlen (const char *); +# endif /* not __STDC__ */ +#endif /* __GNUC__ */ + +#endif /* not __GNU_LIBRARY__ */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +#ifdef _LIBC +/* Bash 2.0 gives us an environment variable containing flags + indicating ARGV elements that should not be considered arguments. */ + +/* Defined in getopt_init.c */ +extern char *__getopt_nonoption_flags; + +static int nonoption_flags_max_len; +static int nonoption_flags_len; + +static int original_argc; +static char *const *original_argv; + +/* Make sure the environment variable bash 2.0 puts in the environment + is valid for the getopt call we must make sure that the ARGV passed + to getopt is that one passed to the process. */ +static void +__attribute__ ((unused)) +store_args_and_env (int argc, char *const *argv) +{ + /* XXX This is no good solution. We should rather copy the args so + that we can compare them later. But we must not use malloc(3). */ + original_argc = argc; + original_argv = argv; +} +# ifdef text_set_element +text_set_element (__libc_subinit, store_args_and_env); +# endif /* text_set_element */ + +# define SWAP_FLAGS(ch1, ch2) \ + if (nonoption_flags_len > 0) \ + { \ + char __tmp = __getopt_nonoption_flags[ch1]; \ + __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ + __getopt_nonoption_flags[ch2] = __tmp; \ + } +#else /* !_LIBC */ +# define SWAP_FLAGS(ch1, ch2) +#endif /* _LIBC */ + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,share__optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +#if defined __STDC__ && __STDC__ +static void exchange (char **); +#endif + +static void +exchange (char **argv) +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = share__optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + +#ifdef _LIBC + /* First make sure the handling of the `__getopt_nonoption_flags' + string can work normally. Our top argument must be in the range + of the string. */ + if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) + { + /* We must extend the array. The user plays games with us and + presents new arguments. */ + char *new_str = malloc (top + 1); + if (new_str == NULL) + nonoption_flags_len = nonoption_flags_max_len = 0; + else + { + memset (__mempcpy (new_str, __getopt_nonoption_flags, + nonoption_flags_max_len), + '\0', top + 1 - nonoption_flags_max_len); + nonoption_flags_max_len = top + 1; + __getopt_nonoption_flags = new_str; + } + } +#endif + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS (bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (share__optind - last_nonopt); + last_nonopt = share__optind; +} + +/* Initialize the internal data when the first call is made. */ + +#if defined __STDC__ && __STDC__ +static const char *share___getopt_initialize (int, char *const *, const char *); +#endif +static const char * +share___getopt_initialize (int argc, char *const *argv, const char *optstring ) +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = share__optind; + + nextchar = NULL; + + posixly_correct = getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + +#ifdef _LIBC + if (posixly_correct == NULL + && argc == original_argc && argv == original_argv) + { + if (nonoption_flags_max_len == 0) + { + if (__getopt_nonoption_flags == NULL + || __getopt_nonoption_flags[0] == '\0') + nonoption_flags_max_len = -1; + else + { + const char *orig_str = __getopt_nonoption_flags; + int len = nonoption_flags_max_len = strlen (orig_str); + if (nonoption_flags_max_len < argc) + nonoption_flags_max_len = argc; + __getopt_nonoption_flags = + malloc (nonoption_flags_max_len); + if (__getopt_nonoption_flags == NULL) + nonoption_flags_max_len = -1; + else + memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), + '\0', nonoption_flags_max_len - len); + } + } + nonoption_flags_len = nonoption_flags_max_len; + } + else + nonoption_flags_len = 0; +#else + (void)argc, (void)argv; +#endif + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `share__getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `share__getopt' finds another option character, it returns that character, + updating `share__optind' and `nextchar' so that the next call to `share__getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `share__getopt' returns -1. + Then `share__optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `share__opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `share__optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `share__optarg', otherwise `share__optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `share__getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct share__option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +share___getopt_internal ( + int argc, + char *const *argv, + const char *optstring, + const struct share__option *longopts, + int *longind, + int long_only ) +{ + share__optarg = NULL; + + if (share__optind == 0 || !share____getopt_initialized) + { + if (share__optind == 0) + share__optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = share___getopt_initialize (argc, argv, optstring); + share____getopt_initialized = 1; + } + + /* Test whether ARGV[share__optind] points to a non-option argument. + Either it does not have option syntax, or there is an environment flag + from the shell indicating it is not an option. The later information + is only used when the used in the GNU libc. */ +#ifdef _LIBC +# define NONOPTION_P (argv[share__optind][0] != '-' || argv[share__optind][1] == '\0' \ + || (share__optind < nonoption_flags_len \ + && __getopt_nonoption_flags[share__optind] == '1')) +#else +# define NONOPTION_P (argv[share__optind][0] != '-' || argv[share__optind][1] == '\0') +#endif + + if (nextchar == NULL || *nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + moved back by the user (who may also have changed the arguments). */ + if (last_nonopt > share__optind) + last_nonopt = share__optind; + if (first_nonopt > share__optind) + first_nonopt = share__optind; + + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != share__optind) + exchange ((char **) argv); + else if (last_nonopt != share__optind) + first_nonopt = share__optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (share__optind < argc && NONOPTION_P) + share__optind++; + last_nonopt = share__optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (share__optind != argc && !strcmp (argv[share__optind], "--")) + { + share__optind++; + + if (first_nonopt != last_nonopt && last_nonopt != share__optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = share__optind; + last_nonopt = argc; + + share__optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (share__optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + share__optind = first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if (NONOPTION_P) + { + if (ordering == REQUIRE_ORDER) + return -1; + share__optarg = argv[share__optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[share__optind] + 1 + + (longopts != NULL && argv[share__optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[share__optind][1] == '-' + || (long_only && (argv[share__optind][2] || !my_index (optstring, argv[share__optind][1]))))) + { + char *nameend; + const struct share__option *p; + const struct share__option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((size_t) (nameend - nextchar) == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (share__opterr) + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[share__optind]); + nextchar += strlen (nextchar); + share__optind++; + share__optopt = 0; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + share__optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + share__optarg = nameend + 1; + else + { + if (share__opterr) + { + if (argv[share__optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + _("%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + _("%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[share__optind - 1][0], pfound->name); + } + + nextchar += strlen (nextchar); + + share__optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (share__optind < argc) + share__optarg = argv[share__optind++]; + else + { + if (share__opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[share__optind - 1]); + nextchar += strlen (nextchar); + share__optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not share__getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[share__optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + if (share__opterr) + { + if (argv[share__optind][1] == '-') + /* --option */ + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[share__optind][0], nextchar); + } + nextchar = (char *) ""; + share__optind++; + share__optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `share__optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++share__optind; + + if (temp == NULL || c == ':') + { + if (share__opterr) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: illegal option -- %c\n"), + argv[0], c); + else + fprintf (stderr, _("%s: invalid option -- %c\n"), + argv[0], c); + } + share__optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct share__option *p; + const struct share__option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + share__optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + share__optind++; + } + else if (share__optind == argc) + { + if (share__opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + share__optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + /* We already incremented `share__optind' once; + increment it again when taking next ARGV-elt as argument. */ + share__optarg = argv[share__optind++]; + + /* share__optarg is now the argument, see if it's in the + table of longopts. */ + + for (nextchar = nameend = share__optarg; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((size_t) (nameend - nextchar) == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) + { + if (share__opterr) + fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[share__optind]); + nextchar += strlen (nextchar); + share__optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + share__optarg = nameend + 1; + else + { + if (share__opterr) + fprintf (stderr, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name); + + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (share__optind < argc) + share__optarg = argv[share__optind++]; + else + { + if (share__opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[share__optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + share__optarg = nextchar; + share__optind++; + } + else + share__optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + share__optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + share__optind++; + } + else if (share__optind == argc) + { + if (share__opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + share__optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `share__optind' once; + increment it again when taking next ARGV-elt as argument. */ + share__optarg = argv[share__optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +share__getopt (int argc, char *const *argv, const char *optstring) +{ + return share___getopt_internal (argc, argv, optstring, + (const struct share__option *) 0, + (int *) 0, + 0); +} + +#endif /* Not ELIDE_CODE. */ + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `share__getopt'. */ + +int +main (int argc, char **argv) +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = share__optind ? share__optind : 1; + + c = share__getopt (argc, argv, "abc:d:0123456789"); + if (c == -1) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", share__optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (share__optind < argc) + { + printf ("non-option ARGV-elements: "); + while (share__optind < argc) + printf ("%s ", argv[share__optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/src/share/getopt/getopt1.c b/src/share/getopt/getopt1.c new file mode 100644 index 0000000..fc52678 --- /dev/null +++ b/src/share/getopt/getopt1.c @@ -0,0 +1,189 @@ +/* + NOTE: + I cannot get the vanilla getopt code to work (i.e. compile only what + is needed and not duplicate symbols found in the standard library) + on all the platforms that FLAC supports. In particular the gating + of code with the ELIDE_CODE #define is not accurate enough on systems + that are POSIX but not glibc. If someone has a patch that works on + GNU/Linux, Darwin, AND Solaris please submit it on the project page: + https://sourceforge.net/p/flac/patches/ + + In the meantime I have munged the global symbols and removed gates + around code, while at the same time trying to touch the original as + little as possible. +*/ +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include <stdio.h> + +#include "share/getopt.h" +/*[JEC] was:#include "getopt.h"*/ + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 +#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 +#include <gnu-versions.h> +#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +#define ELIDE_CODE +#endif +#endif + +#if 1 +/*[JEC] was:#ifndef ELIDE_CODE*/ + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include <stdlib.h> +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int share__getopt_long(int argc, char *const *argv, const char *options, const struct share__option *long_options, int *opt_index) +{ + return share___getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like share__getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int share__getopt_long_only(int argc, char *const *argv, const char *options, const struct share__option *long_options, int *opt_index) +{ + return share___getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* Not ELIDE_CODE. */ + +#ifdef TEST + +#include <stdio.h> + +int main(int argc, char **argv) +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = share__optind ? share__optind : 1; + int option_index = 0; + static struct share__option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = share__getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == -1) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (share__optarg) + printf (" with arg %s", share__optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", share__optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", share__optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (share__optind < argc) + { + printf ("non-option ARGV-elements: "); + while (share__optind < argc) + printf ("%s ", argv[share__optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/src/share/grabbag/CMakeLists.txt b/src/share/grabbag/CMakeLists.txt new file mode 100644 index 0000000..203ae3f --- /dev/null +++ b/src/share/grabbag/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(grabbag STATIC + alloc.c + cuesheet.c + file.c + picture.c + replaygain.c + seektable.c + snprintf.c) +target_link_libraries(grabbag PUBLIC + FLAC + replaygain_analysis) +if(TARGET win_utf8_io) + target_link_libraries(grabbag PUBLIC win_utf8_io) +endif() diff --git a/src/share/grabbag/alloc.c b/src/share/grabbag/alloc.c new file mode 100644 index 0000000..4e5fb60 --- /dev/null +++ b/src/share/grabbag/alloc.c @@ -0,0 +1,48 @@ +/* alloc - Convenience routines for safely allocating memory + * Copyright (C) 2007-2009 Josh Coalson + * Copyright (C) 2011-2023 Xiph.Org Foundation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Xiph.org Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> + +#include "share/alloc.h" + +void *safe_malloc_mul_2op_(size_t size1, size_t size2) +{ + if(!size1 || !size2) + return malloc(1); /* malloc(0) is undefined; FLAC src convention is to always allocate */ + if(size1 > SIZE_MAX / size2) + return 0; + return malloc(size1*size2); +} diff --git a/src/share/grabbag/cuesheet.c b/src/share/grabbag/cuesheet.c new file mode 100644 index 0000000..0d19ee7 --- /dev/null +++ b/src/share/grabbag/cuesheet.c @@ -0,0 +1,681 @@ +/* grabbag - Convenience lib for various routines common to several tools + * Copyright (C) 2002-2009 Josh Coalson + * Copyright (C) 2011-2023 Xiph.Org Foundation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include "FLAC/assert.h" +#include "share/compat.h" +#include "share/grabbag.h" +#include "share/safe_str.h" + +uint32_t grabbag__cuesheet_msf_to_frame(uint32_t minutes, uint32_t seconds, uint32_t frames) +{ + return ((minutes * 60) + seconds) * 75 + frames; +} + +void grabbag__cuesheet_frame_to_msf(uint32_t frame, uint32_t *minutes, uint32_t *seconds, uint32_t *frames) +{ + *frames = frame % 75; + frame /= 75; + *seconds = frame % 60; + frame /= 60; + *minutes = frame; +} + +/* since we only care about values >= 0 or error, returns < 0 for any illegal string, else value */ +static FLAC__int64 local__parse_int64_(const char *s) +{ + FLAC__int64 ret = 0; + char c; + + if(*s == '\0') + return -1; + + while('\0' != (c = *s++)) + if(c >= '0' && c <= '9') { + if(ret >= (INT64_MAX / 10)) + return -1; + else + ret = ret * 10 + (c - '0'); + } + else + return -1; + + return ret; +} + +/* since we only care about values >= 0 or error, returns < 0 for any illegal string, else value */ +static int local__parse_int_(const char *s) +{ + FLAC__int64 ret64 = local__parse_int64_(s); + if(ret64 < 0 || ret64 > INT_MAX) + return -1; + return ret64; +} + +/* accept minute:second:frame syntax of '[0-9]+:[0-9][0-9]?:[0-9][0-9]?', but max second of 59 and max frame of 74, e.g. 0:0:0, 123:45:67 + * return sample number or <0 for error + * WATCHOUT: if sample rate is not evenly divisible by 75, the resulting sample number will be approximate + */ +static FLAC__int64 local__parse_msf_(const char *s, uint32_t sample_rate) +{ + FLAC__int64 ret, field; + char c; + + if(sample_rate == 0) + return -1; + + c = *s++; + if(c >= '0' && c <= '9') + field = (c - '0'); + else + return -1; + while(':' != (c = *s++)) { + if(c >= '0' && c <= '9') { + if(field >= (INT64_MAX / 10)) + return -1; + else + field = field * 10 + (c - '0'); + } + else + return -1; + } + + if(field >= INT64_MAX / (60 * sample_rate)) + return -1; + ret = field * 60 * sample_rate; + + c = *s++; + if(c >= '0' && c <= '9') + field = (c - '0'); + else + return -1; + if(':' != (c = *s++)) { + if(c >= '0' && c <= '9') { + field = field * 10 + (c - '0'); + c = *s++; + if(c != ':') + return -1; + } + else + return -1; + } + + if(field >= 60) + return -1; + + { + FLAC__int64 tmp = ret; + ret += field * sample_rate; + if(ret < tmp) + return -1; + } + + c = *s++; + if(c >= '0' && c <= '9') + field = (c - '0'); + else + return -1; + if('\0' != (c = *s++)) { + if(c >= '0' && c <= '9') { + field = field * 10 + (c - '0'); + c = *s++; + } + else + return -1; + } + + if(c != '\0') + return -1; + + if(field >= 75) + return -1; + + { + FLAC__int64 tmp = ret; + ret += field * (sample_rate / 75); + if(ret < tmp) + return -1; + } + + return ret; +} + +/* accept minute:second syntax of '[0-9]+:[0-9][0-9]?{,.[0-9]+}', but second < 60, e.g. 0:0.0, 3:5, 15:31.731 + * return sample number or <0 for error + * WATCHOUT: depending on the sample rate, the resulting sample number may be approximate with fractional seconds + */ +static FLAC__int64 local__parse_ms_(const char *s, uint32_t sample_rate) +{ + FLAC__int64 ret, field; + double x; + char c, *end; + + if(sample_rate == 0) + return -1; + + c = *s++; + if(c >= '0' && c <= '9') + field = (c - '0'); + else + return -1; + while(':' != (c = *s++)) { + if(c >= '0' && c <= '9') { + if(field >= (INT64_MAX / 10)) + return -1; + else + field = field * 10 + (c - '0'); + } + else + return -1; + } + + if(field >= INT64_MAX / (60 * sample_rate)) + return -1; + ret = field * 60 * sample_rate; + + if(strspn(s, "0123456789.") != strlen(s)) + return -1; + x = strtod(s, &end); + if(*end || end == s) + return -1; + if(x < 0.0 || x >= 60.0) + return -1; + + ret += (FLAC__int64)(x * sample_rate); + + return ret; +} + +static char *local__get_field_(char **s, FLAC__bool allow_quotes) +{ + FLAC__bool has_quote = false; + char *p; + + FLAC__ASSERT(0 != s); + + if(0 == *s) + return 0; + + /* skip leading whitespace */ + while(**s && 0 != strchr(" \t\r\n", **s)) + (*s)++; + + if(**s == 0) { + *s = 0; + return 0; + } + + if(allow_quotes && (**s == '"')) { + has_quote = true; + (*s)++; + if(**s == 0) { + *s = 0; + return 0; + } + } + + p = *s; + + if(has_quote) { + *s = strchr(*s, '\"'); + /* if there is no matching end quote, it's an error */ + if(0 == *s) + p = *s = 0; + else { + **s = '\0'; + (*s)++; + } + } + else { + while(**s && 0 == strchr(" \t\r\n", **s)) + (*s)++; + if(**s) { + **s = '\0'; + (*s)++; + } + else + *s = 0; + } + + return p; +} + +static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message, uint32_t *last_line_read, FLAC__StreamMetadata *cuesheet, uint32_t sample_rate, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset) +{ + char buffer[4096], *line, *field; + uint32_t forced_leadout_track_num = 0; + FLAC__uint64 forced_leadout_track_offset = 0; + int in_track_num = -1, in_index_num = -1; + FLAC__bool disc_has_catalog = false, track_has_flags = false, track_has_isrc = false, has_forced_leadout = false; + FLAC__StreamMetadata_CueSheet *cs = &cuesheet->data.cue_sheet; + + FLAC__ASSERT(!is_cdda || sample_rate == 44100); + /* double protection */ + if(is_cdda && sample_rate != 44100) { + *error_message = "CD-DA cuesheet only allowed with 44.1kHz sample rate"; + return false; + } + + cs->lead_in = is_cdda? 2 * 44100 /* The default lead-in size for CD-DA */ : 0; + cs->is_cd = is_cdda; + + while(0 != fgets(buffer, sizeof(buffer), file)) { + (*last_line_read)++; + line = buffer; + + { + size_t linelen = strlen(line); + if((linelen == sizeof(buffer)-1) && line[linelen-1] != '\n') { + *error_message = "line too long"; + return false; + } + } + + if(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) { + if(0 == FLAC__STRCASECMP(field, "CATALOG")) { + if(disc_has_catalog) { + *error_message = "found multiple CATALOG commands"; + return false; + } + if(0 == (field = local__get_field_(&line, /*allow_quotes=*/true))) { + *error_message = "CATALOG is missing catalog number"; + return false; + } + if(strlen(field) >= sizeof(cs->media_catalog_number)) { + *error_message = "CATALOG number is too long"; + return false; + } + if(is_cdda && (strlen(field) != 13 || strspn(field, "0123456789") != 13)) { + *error_message = "CD-DA CATALOG number must be 13 decimal digits"; + return false; + } + safe_strncpy(cs->media_catalog_number, field, sizeof(cs->media_catalog_number)); + disc_has_catalog = true; + } + else if(0 == FLAC__STRCASECMP(field, "FLAGS")) { + if(track_has_flags) { + *error_message = "found multiple FLAGS commands"; + return false; + } + if(in_track_num < 0 || in_index_num >= 0) { + *error_message = "FLAGS command must come after TRACK but before INDEX"; + return false; + } + while(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) { + if(0 == FLAC__STRCASECMP(field, "PRE")) + cs->tracks[cs->num_tracks-1].pre_emphasis = 1; + } + track_has_flags = true; + } + else if(0 == FLAC__STRCASECMP(field, "INDEX")) { + FLAC__int64 xx; + FLAC__StreamMetadata_CueSheet_Track *track = &cs->tracks[cs->num_tracks-1]; + if(in_track_num < 0) { + *error_message = "found INDEX before any TRACK"; + return false; + } + if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { + *error_message = "INDEX is missing index number"; + return false; + } + in_index_num = local__parse_int_(field); + if(in_index_num < 0) { + *error_message = "INDEX has invalid index number"; + return false; + } + FLAC__ASSERT(cs->num_tracks > 0); + if(track->num_indices == 0) { + /* it's the first index point of the track */ + if(in_index_num > 1) { + *error_message = "first INDEX number of a TRACK must be 0 or 1"; + return false; + } + } + else { + if(in_index_num != track->indices[track->num_indices-1].number + 1) { + *error_message = "INDEX numbers must be sequential"; + return false; + } + } + if(is_cdda && in_index_num > 99) { + *error_message = "CD-DA INDEX number must be between 0 and 99, inclusive"; + return false; + } + /*@@@ search for duplicate track number? */ + if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { + *error_message = "INDEX is missing an offset after the index number"; + return false; + } + /* first parse as minute:second:frame format */ + xx = local__parse_msf_(field, sample_rate); + if(xx < 0) { + /* CD-DA must use only MM:SS:FF format */ + if(is_cdda) { + *error_message = "illegal INDEX offset (not of the form MM:SS:FF)"; + return false; + } + /* as an extension for non-CD-DA we allow MM:SS.SS or raw sample number */ + xx = local__parse_ms_(field, sample_rate); + if(xx < 0) { + xx = local__parse_int64_(field); + if(xx < 0) { + *error_message = "illegal INDEX offset"; + return false; + } + } + } + else if(sample_rate % 75 && xx) { + /* only sample zero is exact */ + *error_message = "illegal INDEX offset (MM:SS:FF form not allowed if sample rate is not a multiple of 75)"; + return false; + } + if(is_cdda && cs->num_tracks == 1 && cs->tracks[0].num_indices == 0 && xx != 0) { + *error_message = "first INDEX of first TRACK must have an offset of 00:00:00"; + return false; + } + if(is_cdda && track->num_indices > 0 && (FLAC__uint64)xx <= track->indices[track->num_indices-1].offset) { + *error_message = "CD-DA INDEX offsets must increase in time"; + return false; + } + /* fill in track offset if it's the first index of the track */ + if(track->num_indices == 0) + track->offset = (FLAC__uint64)xx; + if(is_cdda && cs->num_tracks > 1) { + const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-2]; + if((FLAC__uint64)xx <= prev->offset + prev->indices[prev->num_indices-1].offset) { + *error_message = "CD-DA INDEX offsets must increase in time"; + return false; + } + } + if(!FLAC__metadata_object_cuesheet_track_insert_blank_index(cuesheet, cs->num_tracks-1, track->num_indices)) { + *error_message = "memory allocation error"; + return false; + } + track->indices[track->num_indices-1].offset = (FLAC__uint64)xx - track->offset; + track->indices[track->num_indices-1].number = in_index_num; + } + else if(0 == FLAC__STRCASECMP(field, "ISRC")) { + char *l, *r; + if(track_has_isrc) { + *error_message = "found multiple ISRC commands"; + return false; + } + if(in_track_num < 0 || in_index_num >= 0) { + *error_message = "ISRC command must come after TRACK but before INDEX"; + return false; + } + if(0 == (field = local__get_field_(&line, /*allow_quotes=*/true))) { + *error_message = "ISRC is missing ISRC number"; + return false; + } + /* strip out dashes */ + for(l = r = field; *r; r++) { + if(*r != '-') + *l++ = *r; + } + *l = '\0'; + if(strlen(field) != 12 || strspn(field, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") < 5 || strspn(field+5, "1234567890") != 7) { + *error_message = "invalid ISRC number"; + return false; + } + safe_strncpy(cs->tracks[cs->num_tracks-1].isrc, field, sizeof(cs->tracks[cs->num_tracks-1].isrc)); + track_has_isrc = true; + } + else if(0 == FLAC__STRCASECMP(field, "TRACK")) { + if(cs->num_tracks > 0) { + const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-1]; + if( + prev->num_indices == 0 || + ( + is_cdda && + ( + (prev->num_indices == 1 && prev->indices[0].number != 1) || + (prev->num_indices == 2 && prev->indices[0].number != 1 && prev->indices[1].number != 1) + ) + ) + ) { + *error_message = is_cdda? + "previous TRACK must specify at least one INDEX 01" : + "previous TRACK must specify at least one INDEX"; + return false; + } + } + if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { + *error_message = "TRACK is missing track number"; + return false; + } + in_track_num = local__parse_int_(field); + if(in_track_num < 0) { + *error_message = "TRACK has invalid track number"; + return false; + } + if(in_track_num == 0) { + *error_message = "TRACK number must be greater than 0"; + return false; + } + if(is_cdda) { + if(in_track_num > 99) { + *error_message = "CD-DA TRACK number must be between 1 and 99, inclusive"; + return false; + } + } + else { + if(in_track_num == 255) { + *error_message = "TRACK number 255 is reserved for the lead-out"; + return false; + } + else if(in_track_num > 255) { + *error_message = "TRACK number must be between 1 and 254, inclusive"; + return false; + } + } + if(is_cdda && cs->num_tracks > 0 && in_track_num != cs->tracks[cs->num_tracks-1].number + 1) { + *error_message = "CD-DA TRACK numbers must be sequential"; + return false; + } + /*@@@ search for duplicate track number? */ + if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { + *error_message = "TRACK is missing a track type after the track number"; + return false; + } + if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) { + *error_message = "memory allocation error"; + return false; + } + cs->tracks[cs->num_tracks-1].number = in_track_num; + cs->tracks[cs->num_tracks-1].type = (0 == FLAC__STRCASECMP(field, "AUDIO"))? 0 : 1; /*@@@ should we be more strict with the value here? */ + in_index_num = -1; + track_has_flags = false; + track_has_isrc = false; + } + else if(0 == FLAC__STRCASECMP(field, "REM")) { + if(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) { + if(0 == strcmp(field, "FLAC__lead-in")) { + FLAC__int64 xx; + if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { + *error_message = "FLAC__lead-in is missing offset"; + return false; + } + xx = local__parse_int64_(field); + if(xx < 0) { + *error_message = "illegal FLAC__lead-in offset"; + return false; + } + if(is_cdda && xx % 588 != 0) { + *error_message = "illegal CD-DA FLAC__lead-in offset, must be even multiple of 588 samples"; + return false; + } + cs->lead_in = (FLAC__uint64)xx; + } + else if(0 == strcmp(field, "FLAC__lead-out")) { + int track_num; + FLAC__int64 offset; + if(has_forced_leadout) { + *error_message = "multiple FLAC__lead-out commands"; + return false; + } + if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { + *error_message = "FLAC__lead-out is missing track number"; + return false; + } + track_num = local__parse_int_(field); + if(track_num < 0) { + *error_message = "illegal FLAC__lead-out track number"; + return false; + } + forced_leadout_track_num = (uint32_t)track_num; + /*@@@ search for duplicate track number? */ + if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { + *error_message = "FLAC__lead-out is missing offset"; + return false; + } + offset = local__parse_int64_(field); + if(offset < 0) { + *error_message = "illegal FLAC__lead-out offset"; + return false; + } + forced_leadout_track_offset = (FLAC__uint64)offset; + if(forced_leadout_track_offset != lead_out_offset) { + *error_message = "FLAC__lead-out offset does not match end-of-stream offset"; + return false; + } + has_forced_leadout = true; + } + } + } + } + } + + if(cs->num_tracks == 0) { + *error_message = "there must be at least one TRACK command"; + return false; + } + else { + const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-1]; + if( + prev->num_indices == 0 || + ( + is_cdda && + ( + (prev->num_indices == 1 && prev->indices[0].number != 1) || + (prev->num_indices == 2 && prev->indices[0].number != 1 && prev->indices[1].number != 1) + ) + ) + ) { + *error_message = is_cdda? + "previous TRACK must specify at least one INDEX 01" : + "previous TRACK must specify at least one INDEX"; + return false; + } + } + + if(!has_forced_leadout) { + forced_leadout_track_num = is_cdda? 170 : 255; + forced_leadout_track_offset = lead_out_offset; + } + if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) { + *error_message = "memory allocation error"; + return false; + } + cs->tracks[cs->num_tracks-1].number = forced_leadout_track_num; + cs->tracks[cs->num_tracks-1].offset = forced_leadout_track_offset; + + if(!feof(file)) { + *error_message = "read error"; + return false; + } + return true; +} + +FLAC__StreamMetadata *grabbag__cuesheet_parse(FILE *file, const char **error_message, uint32_t *last_line_read, uint32_t sample_rate, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset) +{ + FLAC__StreamMetadata *cuesheet; + + FLAC__ASSERT(0 != file); + FLAC__ASSERT(0 != error_message); + FLAC__ASSERT(0 != last_line_read); + + *last_line_read = 0; + cuesheet = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET); + + if(0 == cuesheet) { + *error_message = "memory allocation error"; + return 0; + } + + if(!local__cuesheet_parse_(file, error_message, last_line_read, cuesheet, sample_rate, is_cdda, lead_out_offset)) { + FLAC__metadata_object_delete(cuesheet); + return 0; + } + + return cuesheet; +} + +void grabbag__cuesheet_emit(FILE *file, const FLAC__StreamMetadata *cuesheet, const char *file_reference) +{ + const FLAC__StreamMetadata_CueSheet *cs; + uint32_t track_num, index_num; + + FLAC__ASSERT(0 != file); + FLAC__ASSERT(0 != cuesheet); + FLAC__ASSERT(cuesheet->type == FLAC__METADATA_TYPE_CUESHEET); + + cs = &cuesheet->data.cue_sheet; + + if(*(cs->media_catalog_number)) + fprintf(file, "CATALOG %s\n", cs->media_catalog_number); + fprintf(file, "FILE %s\n", file_reference); + + FLAC__ASSERT(cs->num_tracks > 0); + + for(track_num = 0; track_num < cs->num_tracks-1; track_num++) { + const FLAC__StreamMetadata_CueSheet_Track *track = cs->tracks + track_num; + + fprintf(file, " TRACK %02u %s\n", (uint32_t)track->number, track->type == 0? "AUDIO" : "DATA"); + + if(track->pre_emphasis) + fprintf(file, " FLAGS PRE\n"); + if(*(track->isrc)) + fprintf(file, " ISRC %s\n", track->isrc); + + for(index_num = 0; index_num < track->num_indices; index_num++) { + const FLAC__StreamMetadata_CueSheet_Index *indx = track->indices + index_num; + + fprintf(file, " INDEX %02u ", (uint32_t)indx->number); + if(cs->is_cd) { + const uint32_t logical_frame = (uint32_t)((track->offset + indx->offset) / (44100 / 75)); + uint32_t m, s, f; + grabbag__cuesheet_frame_to_msf(logical_frame, &m, &s, &f); + fprintf(file, "%02u:%02u:%02u\n", m, s, f); + } + else + fprintf(file, "%" PRIu64 "\n", (track->offset + indx->offset)); + } + } + + fprintf(file, "REM FLAC__lead-in %" PRIu64 "\n", cs->lead_in); + fprintf(file, "REM FLAC__lead-out %u %" PRIu64 "\n", (uint32_t)cs->tracks[track_num].number, cs->tracks[track_num].offset); +} diff --git a/src/share/grabbag/file.c b/src/share/grabbag/file.c new file mode 100644 index 0000000..307645f --- /dev/null +++ b/src/share/grabbag/file.c @@ -0,0 +1,207 @@ +/* grabbag - Convenience lib for various routines common to several tools + * Copyright (C) 2002-2009 Josh Coalson + * Copyright (C) 2011-2023 Xiph.Org Foundation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined _MSC_VER || defined __MINGW32__ +#include <sys/utime.h> /* for utime() */ +#include <io.h> /* for chmod(), _setmode(), unlink() */ +#include <fcntl.h> /* for _O_BINARY */ +#else +#include <sys/types.h> /* some flavors of BSD (like OS X) require this to get time_t */ +#endif +#if defined __EMX__ +#include <io.h> /* for setmode(), O_BINARY */ +#include <fcntl.h> /* for _O_BINARY */ +#endif +#include <sys/stat.h> /* for stat(), maybe chmod() */ +#if defined _WIN32 && !defined __CYGWIN__ +#else +#include <unistd.h> /* for unlink() */ +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> /* for strrchr() */ +#if defined _WIN32 && !defined __CYGWIN__ +// for GetFileInformationByHandle() etc +#include <windows.h> +#include <winbase.h> +#endif +#include "share/grabbag.h" +#include "share/compat.h" + + +void grabbag__file_copy_metadata(const char *srcpath, const char *destpath) +{ + struct flac_stat_s srcstat; + + if(0 == flac_stat(srcpath, &srcstat)) { +#if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200809L) && !defined(_WIN32) + struct timespec srctime[2] = {}; + srctime[0].tv_sec = srcstat.st_atime; + srctime[1].tv_sec = srcstat.st_mtime; +#else + struct utimbuf srctime; + srctime.actime = srcstat.st_atime; + srctime.modtime = srcstat.st_mtime; +#endif + (void)flac_chmod(destpath, srcstat.st_mode); + (void)flac_utime(destpath, &srctime); + } +} + +FLAC__off_t grabbag__file_get_filesize(const char *srcpath) +{ + struct flac_stat_s srcstat; + + if(0 == flac_stat(srcpath, &srcstat)) + return srcstat.st_size; + else + return -1; +} + +const char *grabbag__file_get_basename(const char *srcpath) +{ + const char *p; + + p = strrchr(srcpath, '/'); + if(0 == p) { +#if defined _WIN32 && !defined __CYGWIN__ + p = strrchr(srcpath, '\\'); + if(0 == p) +#endif + return srcpath; + } + return ++p; +} + +FLAC__bool grabbag__file_change_stats(const char *filename, FLAC__bool read_only) +{ + struct flac_stat_s stats; + + if(0 == flac_stat(filename, &stats)) { +#if !defined _MSC_VER && !defined __MINGW32__ + if(read_only) { + stats.st_mode &= ~S_IWUSR; + stats.st_mode &= ~S_IWGRP; + stats.st_mode &= ~S_IWOTH; + } + else { + stats.st_mode |= S_IWUSR; + } +#else + if(read_only) + stats.st_mode &= ~S_IWRITE; + else + stats.st_mode |= S_IWRITE; +#endif + if(0 != flac_chmod(filename, stats.st_mode)) + return false; + } + else + return false; + + return true; +} + +FLAC__bool grabbag__file_are_same(const char *f1, const char *f2) +{ +#if defined _WIN32 && !defined __CYGWIN__ +#if !defined(WINAPI_FAMILY_PARTITION) +#define WINAPI_FAMILY_PARTITION(x) x +#define WINAPI_PARTITION_DESKTOP 1 +#endif + /* see + * http://www.hydrogenaudio.org/forums/index.php?showtopic=49439&pid=444300&st=0 + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/getfileinformationbyhandle.asp + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/by_handle_file_information_str.asp + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/createfile.asp + * apparently both the files have to be open at the same time for the comparison to work + */ + FLAC__bool same = false; + BY_HANDLE_FILE_INFORMATION info1, info2; + HANDLE h1, h2; + BOOL ok = 1; + h1 = CreateFile_utf8(f1, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + h2 = CreateFile_utf8(f2, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if(h1 == INVALID_HANDLE_VALUE || h2 == INVALID_HANDLE_VALUE) + ok = 0; +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + ok &= GetFileInformationByHandle(h1, &info1); + ok &= GetFileInformationByHandle(h2, &info2); + if(ok) + same = + info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber && + info1.nFileIndexHigh == info2.nFileIndexHigh && + info1.nFileIndexLow == info2.nFileIndexLow + ; +#else // !WINAPI_PARTITION_DESKTOP + FILE_ID_INFO id_info1, id_info2; + same = GetFileInformationByHandleEx(h1, FileIdInfo, &id_info1, sizeof (id_info1)) && + GetFileInformationByHandleEx(h2, FileIdInfo, &id_info2, sizeof (id_info2)) && + id_info1.VolumeSerialNumber == id_info2.VolumeSerialNumber && + memcmp(&id_info1.FileId, &id_info2.FileId, sizeof(id_info1.FileId)) == 0; +#endif // !WINAPI_PARTITION_DESKTOP + if(h1 != INVALID_HANDLE_VALUE) + CloseHandle(h1); + if(h2 != INVALID_HANDLE_VALUE) + CloseHandle(h2); + return same; +#else + struct flac_stat_s s1, s2; + return f1 && f2 && flac_stat(f1, &s1) == 0 && flac_stat(f2, &s2) == 0 && s1.st_ino == s2.st_ino && s1.st_dev == s2.st_dev; +#endif +} + +FLAC__bool grabbag__file_remove_file(const char *filename) +{ + return grabbag__file_change_stats(filename, /*read_only=*/false) && 0 == flac_unlink(filename); +} + +FILE *grabbag__file_get_binary_stdin(void) +{ + /* if something breaks here it is probably due to the presence or + * absence of an underscore before the identifiers 'setmode', + * 'fileno', and/or 'O_BINARY'; check your system header files. + */ +#if defined _MSC_VER || defined __MINGW32__ + _setmode(_fileno(stdin), _O_BINARY); +#elif defined __EMX__ + setmode(fileno(stdin), O_BINARY); +#endif + + return stdin; +} + +FILE *grabbag__file_get_binary_stdout(void) +{ + /* if something breaks here it is probably due to the presence or + * absence of an underscore before the identifiers 'setmode', + * 'fileno', and/or 'O_BINARY'; check your system header files. + */ +#if defined _MSC_VER || defined __MINGW32__ + _setmode(_fileno(stdout), _O_BINARY); +#elif defined __EMX__ + setmode(fileno(stdout), O_BINARY); +#endif + + return stdout; +} diff --git a/src/share/grabbag/picture.c b/src/share/grabbag/picture.c new file mode 100644 index 0000000..9a4aafe --- /dev/null +++ b/src/share/grabbag/picture.c @@ -0,0 +1,515 @@ +/* grabbag - Convenience lib for various routines common to several tools + * Copyright (C) 2006-2009 Josh Coalson + * Copyright (C) 2011-2023 Xiph.Org Foundation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "share/alloc.h" +#include "share/grabbag.h" +#include "FLAC/assert.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "share/compat.h" +#include "share/safe_str.h" + +/* slightly different that strndup(): this always copies 'size' bytes starting from s into a NUL-terminated string. */ +static char *local__strndup_(const char *s, size_t size) +{ + char *x = safe_malloc_add_2op_(size, /*+*/1); + if(x) { + memcpy(x, s, size); + x[size] = '\0'; + } + return x; +} + +static FLAC__bool local__parse_type_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture) +{ + size_t i; + FLAC__uint32 val = 0; + + picture->type = FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER; + + if(len == 0) + return true; /* empty string implies default to 'front cover' */ + + for(i = 0; i < len; i++) { + if(s[i] >= '0' && s[i] <= '9') + val = 10*val + (FLAC__uint32)(s[i] - '0'); + else + return false; + } + + if(i == len) + picture->type = val; + else + return false; + + return true; +} + +static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture) +{ + int state = 0; + size_t i; + FLAC__uint32 val = 0; + + picture->width = picture->height = picture->depth = picture->colors = 0; + + if(len == 0) + return true; /* empty string implies client wants to get info from the file itself */ + + for(i = 0; i < len; i++) { + if(s[i] == 'x') { + if(state == 0) + picture->width = val; + else if(state == 1) + picture->height = val; + else + return false; + state++; + val = 0; + } + else if(s[i] == '/') { + if(state == 2) + picture->depth = val; + else + return false; + state++; + val = 0; + } + else if(s[i] >= '0' && s[i] <= '9') + val = 10*val + (FLAC__uint32)(s[i] - '0'); + else + return false; + } + + if(state < 2) + return false; + else if(state == 2) + picture->depth = val; + else if(state == 3) + picture->colors = val; + else + return false; + if(picture->depth < 32 && 1u<<picture->depth < picture->colors) + return false; + + return true; +} + +static FLAC__bool local__extract_mime_type_(FLAC__StreamMetadata *obj) +{ + if(obj->data.picture.data_length >= 8 && 0 == memcmp(obj->data.picture.data, "\x89PNG\x0d\x0a\x1a\x0a", 8)) + return FLAC__metadata_object_picture_set_mime_type(obj, "image/png", /*copy=*/true); + else if(obj->data.picture.data_length >= 6 && (0 == memcmp(obj->data.picture.data, "GIF87a", 6) || 0 == memcmp(obj->data.picture.data, "GIF89a", 6))) + return FLAC__metadata_object_picture_set_mime_type(obj, "image/gif", /*copy=*/true); + else if(obj->data.picture.data_length >= 2 && 0 == memcmp(obj->data.picture.data, "\xff\xd8", 2)) + return FLAC__metadata_object_picture_set_mime_type(obj, "image/jpeg", /*copy=*/true); + return false; +} + +static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture) +{ + const FLAC__byte *data = picture->data; + FLAC__uint32 len = picture->data_length; + + if(0 == strcmp(picture->mime_type, "image/png")) { + /* c.f. http://www.w3.org/TR/PNG/ */ + FLAC__bool need_palette = false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */ + if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8)) + return false; + /* try to find IHDR chunk */ + data += 8; + len -= 8; + while(len > 12) { /* every PNG chunk must be at least 12 bytes long */ + const FLAC__uint32 clen = (FLAC__uint32)data[0] << 24 | (FLAC__uint32)data[1] << 16 | (FLAC__uint32)data[2] << 8 | (FLAC__uint32)data[3]; + /* First check whether clen makes sense or causes overflow in this bit of code */ + if(clen + 12 <= clen || clen + 12 > len) + return false; + else if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) { + uint32_t color_type = data[17]; + picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11]; + picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15]; + if(color_type == 3) { + /* even though the bit depth for color_type==3 can be 1,2,4,or 8, + * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the + * sample depth is always 8 + */ + picture->depth = 8 * 3u; + need_palette = true; + data += 12 + clen; + len -= 12 + clen; + } + else { + if(color_type == 0) /* greyscale, 1 sample per pixel */ + picture->depth = (FLAC__uint32)data[16]; + if(color_type == 2) /* truecolor, 3 samples per pixel */ + picture->depth = (FLAC__uint32)data[16] * 3u; + if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */ + picture->depth = (FLAC__uint32)data[16] * 2u; + if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */ + picture->depth = (FLAC__uint32)data[16] * 4u; + picture->colors = 0; + return true; + } + } + else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) { + picture->colors = clen / 3u; + return true; + } + else { + data += 12 + clen; + len -= 12 + clen; + } + } + } + else if(0 == strcmp(picture->mime_type, "image/jpeg")) { + /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */ + if(len < 2 || memcmp(data, "\xff\xd8", 2)) + return false; + data += 2; + len -= 2; + while(1) { + /* look for sync FF byte */ + for( ; len > 0; data++, len--) { + if(*data == 0xff) + break; + } + if(len == 0) + return false; + /* eat any extra pad FF bytes before marker */ + for( ; len > 0; data++, len--) { + if(*data != 0xff) + break; + } + if(len == 0) + return false; + /* if we hit SOS or EOI, bail */ + if(*data == 0xda || *data == 0xd9) + return false; + /* looking for some SOFn */ + else if(memchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data, 13)) { + data++; len--; /* skip marker byte */ + if(len < 2) + return false; + else { + const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1]; + if(clen < 8 || len < clen) + return false; + picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6]; + picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4]; + picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7]; + picture->colors = 0; + return true; + } + } + /* else skip it */ + else { + data++; len--; /* skip marker byte */ + if(len < 2) + return false; + else { + const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1]; + if(clen < 2 || len < clen) + return false; + data += clen; + len -= clen; + } + } + } + } + else if(0 == strcmp(picture->mime_type, "image/gif")) { + /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */ + if(len < 14) + return false; + if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6)) + return false; +#if 0 + /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */ + if(data[10] & 0x80 == 0) + return false; +#endif + picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8); + picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8); +#if 0 + /* this value doesn't seem to be reliable... */ + picture->depth = (((FLAC__uint32)(data[10] & 0x70) >> 4) + 1) * 3u; +#else + /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */ + picture->depth = 8u * 3u; +#endif + picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u); + return true; + } + return false; +} + +static const char *error_messages[] = { + "memory allocation error", + "invalid picture specification", + "invalid picture specification: can't parse resolution/color part", + "unable to extract resolution and color info from URL, user must set explicitly", + "unable to extract resolution and color info from file, user must set explicitly", + "error opening picture file", + "error reading picture file", + "invalid picture type", + "unable to guess MIME type from file, user must set explicitly", + "type 1 icon must be a 32x32 pixel PNG", + "file not found", /* currently unused */ + "file is too large", + "empty file" +}; + +static const char * read_file (const char * filepath, FLAC__StreamMetadata * obj) +{ + const FLAC__off_t size = grabbag__file_get_filesize(filepath); + FLAC__byte *buffer; + FILE *file; + const char *error_message=NULL; + + if (size < 0) + return error_messages[5]; + + if (size == 0) + return error_messages[12]; + + if (size >= (FLAC__off_t)(1u << FLAC__STREAM_METADATA_LENGTH_LEN)) /* actual limit is less because of other fields in the PICTURE metadata block */ + return error_messages[11]; + + if ((buffer = safe_malloc_(size)) == NULL) + return error_messages[0]; + + if ((file = flac_fopen(filepath, "rb")) == NULL) { + free(buffer); + return error_messages[5]; + } + + if (fread(buffer, 1, size, file) != (size_t) size) { + fclose(file); + free(buffer); + return error_messages[6]; + } + fclose(file); + + if (!FLAC__metadata_object_picture_set_data(obj, buffer, (FLAC__uint32)size, /*copy=*/false)) + error_message = error_messages[6]; + /* try to extract MIME type if user left it blank */ + else if (*obj->data.picture.mime_type == '\0' && !local__extract_mime_type_(obj)) + error_message = error_messages[8]; + /* try to extract resolution/color info if user left it blank */ + else if ((obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0) && !local__extract_resolution_color_info_(&obj->data.picture)) + error_message = error_messages[4]; + /* check metadata block size */ + else if (obj->length >= (1u << FLAC__STREAM_METADATA_LENGTH_LEN)) + error_message = error_messages[11]; + + return error_message; +} + +FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message) +{ + FLAC__StreamMetadata *obj; + int state = 0; + + FLAC__ASSERT(0 != spec); + FLAC__ASSERT(0 != error_message); + + /* double protection */ + if(0 == spec) + return 0; + if(0 == error_message) + return 0; + + *error_message = 0; + + if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE))) { + *error_message = error_messages[0]; + return obj; + } + + if(strchr(spec, '|')) { /* full format */ + const char *p; + char *q; + for(p = spec; *error_message==0 && *p; ) { + if(*p == '|') { + switch(state) { + case 0: /* type */ + if(!local__parse_type_(spec, p-spec, &obj->data.picture)) + *error_message = error_messages[7]; + break; + case 1: /* mime type */ + if(p-spec) { /* if blank, we'll try to guess later from the picture data */ + if(0 == (q = local__strndup_(spec, p-spec))) + *error_message = error_messages[0]; + else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false)) + *error_message = error_messages[0]; + } + break; + case 2: /* description */ + if(0 == (q = local__strndup_(spec, p-spec))) + *error_message = error_messages[0]; + else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false)) + *error_message = error_messages[0]; + break; + case 3: /* resolution/color (e.g. [300x300x16[/1234]] */ + if(!local__parse_resolution_(spec, p-spec, &obj->data.picture)) + *error_message = error_messages[2]; + break; + default: + *error_message = error_messages[1]; + break; + } + p++; + spec = p; + state++; + } + else + p++; + } + } + else { /* simple format, filename only, everything else guessed */ + if(!local__parse_type_("", 0, &obj->data.picture)) /* use default picture type */ + *error_message = error_messages[7]; + /* leave MIME type to be filled in later */ + /* leave description empty */ + /* leave the rest to be filled in later: */ + else if(!local__parse_resolution_("", 0, &obj->data.picture)) + *error_message = error_messages[2]; + else + state = 4; + } + + /* parse filename, read file, try to extract resolution/color info if needed */ + if(*error_message == 0) { + if(state != 4) + *error_message = error_messages[1]; + else { /* 'spec' points to filename/URL */ + if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */ + if(strlen(spec) == 0) + *error_message = error_messages[1]; + else if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true)) + *error_message = error_messages[0]; + else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0) + *error_message = error_messages[3]; + } + else { /* regular picture file */ + *error_message = read_file (spec, obj); + } + } + } + + if(*error_message == 0) { + if( + obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD && + ( + (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) || + obj->data.picture.width != 32 || + obj->data.picture.height != 32 + ) + ) + *error_message = error_messages[9]; + } + + if(*error_message && obj) { + FLAC__metadata_object_delete(obj); + obj = 0; + } + + return obj; +} + +FLAC__StreamMetadata *grabbag__picture_from_specification(int type, const char *mime_type_in, const char * description, + const PictureResolution * res, const char * filepath, const char **error_message) +{ + + FLAC__StreamMetadata *obj; + char mime_type [64] ; + + if (error_message == 0) + return 0; + + safe_strncpy(mime_type, mime_type_in, sizeof (mime_type)); + + *error_message = 0; + + if ((obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE)) == 0) { + *error_message = error_messages[0]; + return obj; + } + + /* Picture type if known. */ + obj->data.picture.type = type >= 0 ? type : FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER; + + /* Mime type if known. */ + if (mime_type_in && ! FLAC__metadata_object_picture_set_mime_type(obj, mime_type, /*copy=*/true)) { + *error_message = error_messages[0]; + return obj; + } + + /* Description if present. */ + if (description && ! FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*) description, /*copy=*/true)) { + *error_message = error_messages[0]; + return obj; + } + + if (res == NULL) { + obj->data.picture.width = 0; + obj->data.picture.height = 0; + obj->data.picture.depth = 0; + obj->data.picture.colors = 0; + } + else { + obj->data.picture.width = res->width; + obj->data.picture.height = res->height; + obj->data.picture.depth = res->depth; + obj->data.picture.colors = res->colors; + } + + if (strcmp(obj->data.picture.mime_type, "-->") == 0) { /* magic MIME type means URL */ + if (!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)filepath, strlen(filepath), /*copy=*/true)) + *error_message = error_messages[0]; + else if (obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0) + *error_message = error_messages[3]; + } + else { + *error_message = read_file (filepath, obj); + } + + if (*error_message == NULL) { + if ( + obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD && + ( + (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) || + obj->data.picture.width != 32 || + obj->data.picture.height != 32 + ) + ) + *error_message = error_messages[9]; + } + + if (*error_message && obj) { + FLAC__metadata_object_delete(obj); + obj = 0; + } + + return obj; +} diff --git a/src/share/grabbag/replaygain.c b/src/share/grabbag/replaygain.c new file mode 100644 index 0000000..32c9603 --- /dev/null +++ b/src/share/grabbag/replaygain.c @@ -0,0 +1,669 @@ +/* grabbag - Convenience lib for various routines common to several tools + * Copyright (C) 2002-2009 Josh Coalson + * Copyright (C) 2011-2023 Xiph.Org Foundation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <locale.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if defined _MSC_VER || defined __MINGW32__ +#include <io.h> /* for chmod() */ +#endif +#include <sys/stat.h> /* for stat(), maybe chmod() */ + +#include "FLAC/assert.h" +#include "FLAC/metadata.h" +#include "FLAC/stream_decoder.h" +#include "share/grabbag.h" +#include "share/replaygain_analysis.h" +#include "share/safe_str.h" + +#ifdef local_min +#undef local_min +#endif +#define local_min(a,b) ((a)<(b)?(a):(b)) + +#ifdef local_max +#undef local_max +#endif +#define local_max(a,b) ((a)>(b)?(a):(b)) + +#ifdef abs32 +#undef abs32 +#endif +#define abs32(a) (((a)==INT32_MIN)?INT32_MAX:abs(a)) + +static const char *reference_format_ = "%s=%2.1f dB"; +static const char *gain_format_ = "%s=%+2.2f dB"; +static const char *peak_format_ = "%s=%1.8f"; + +static double album_peak_, title_peak_; + +const uint32_t GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED = 190; +/* + FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 29 + 1 + 8 + + FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 10 + + FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 12 + + FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 10 + + FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 12 +*/ + +const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS = (const FLAC__byte * const)"REPLAYGAIN_REFERENCE_LOUDNESS"; +const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN = (const FLAC__byte * const)"REPLAYGAIN_TRACK_GAIN"; +const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK = (const FLAC__byte * const)"REPLAYGAIN_TRACK_PEAK"; +const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN = (const FLAC__byte * const)"REPLAYGAIN_ALBUM_GAIN"; +const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK = (const FLAC__byte * const)"REPLAYGAIN_ALBUM_PEAK"; + + +static FLAC__bool get_file_stats_(const char *filename, struct flac_stat_s *stats) +{ + FLAC__ASSERT(0 != filename); + FLAC__ASSERT(0 != stats); + return (0 == flac_stat(filename, stats)); +} + +static void set_file_stats_(const char *filename, struct flac_stat_s *stats) +{ + FLAC__ASSERT(0 != filename); + FLAC__ASSERT(0 != stats); + + (void)flac_chmod(filename, stats->st_mode); +} + +static FLAC__bool append_tag_(FLAC__StreamMetadata *block, const char *format, const FLAC__byte *name, float value) +{ + char buffer[256]; + char *saved_locale; + FLAC__StreamMetadata_VorbisComment_Entry entry; + + FLAC__ASSERT(0 != block); + FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); + FLAC__ASSERT(0 != format); + FLAC__ASSERT(0 != name); + + buffer[sizeof(buffer)-1] = '\0'; + /* + * We need to save the old locale and switch to "C" because the locale + * influences the formatting of %f and we want it a certain way. + */ + saved_locale = strdup(setlocale(LC_ALL, 0)); + if (0 == saved_locale) + return false; + setlocale(LC_ALL, "C"); + flac_snprintf(buffer, sizeof(buffer), format, name, value); + setlocale(LC_ALL, saved_locale); + free(saved_locale); + + entry.entry = (FLAC__byte *)buffer; + entry.length = strlen(buffer); + + return FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true); +} + +FLAC__bool grabbag__replaygain_is_valid_sample_frequency(uint32_t sample_frequency) +{ + return ValidGainFrequency( sample_frequency ); +} + +FLAC__bool grabbag__replaygain_init(uint32_t sample_frequency) +{ + title_peak_ = album_peak_ = 0.0; + return InitGainAnalysis((long)sample_frequency) == INIT_GAIN_ANALYSIS_OK; +} + +FLAC__bool grabbag__replaygain_analyze(const FLAC__int32 * const input[], FLAC__bool is_stereo, uint32_t bps, uint32_t samples) +{ + /* using a small buffer improves data locality; we'd like it to fit easily in the dcache */ + static flac_float_t lbuffer[2048], rbuffer[2048]; + static const uint32_t nbuffer = sizeof(lbuffer) / sizeof(lbuffer[0]); + FLAC__int32 block_peak = 0, s; + uint32_t i, j; + + FLAC__ASSERT(bps >= FLAC__MIN_BITS_PER_SAMPLE && bps <= FLAC__MAX_BITS_PER_SAMPLE); + FLAC__ASSERT(FLAC__MIN_BITS_PER_SAMPLE == 4 && FLAC__MAX_BITS_PER_SAMPLE == 32); + + if(bps == 16) { + if(is_stereo) { + j = 0; + while(samples > 0) { + const uint32_t n = local_min(samples, nbuffer); + for(i = 0; i < n; i++, j++) { + s = input[0][j]; + lbuffer[i] = (flac_float_t)s; + s = abs(s); + block_peak = local_max(block_peak, s); + + s = input[1][j]; + rbuffer[i] = (flac_float_t)s; + s = abs(s); + block_peak = local_max(block_peak, s); + } + samples -= n; + if(AnalyzeSamples(lbuffer, rbuffer, n, 2) != GAIN_ANALYSIS_OK) + return false; + } + } + else { + j = 0; + while(samples > 0) { + const uint32_t n = local_min(samples, nbuffer); + for(i = 0; i < n; i++, j++) { + s = input[0][j]; + lbuffer[i] = (flac_float_t)s; + s = abs(s); + block_peak = local_max(block_peak, s); + } + samples -= n; + if(AnalyzeSamples(lbuffer, 0, n, 1) != GAIN_ANALYSIS_OK) + return false; + } + } + } + else { + const double scale = ( + (bps > 16)? + (double)1. / (double)(1u << (bps - 16)) : + (double)(1u << (16 - bps)) + ); + + if(is_stereo) { + j = 0; + while(samples > 0) { + const uint32_t n = local_min(samples, nbuffer); + for(i = 0; i < n; i++, j++) { + s = input[0][j]; + lbuffer[i] = (flac_float_t)(scale * (double)s); + s = abs32(s); + block_peak = local_max(block_peak, s); + + s = input[1][j]; + rbuffer[i] = (flac_float_t)(scale * (double)s); + s = abs32(s); + block_peak = local_max(block_peak, s); + } + samples -= n; + if(AnalyzeSamples(lbuffer, rbuffer, n, 2) != GAIN_ANALYSIS_OK) + return false; + } + } + else { + j = 0; + while(samples > 0) { + const uint32_t n = local_min(samples, nbuffer); + for(i = 0; i < n; i++, j++) { + s = input[0][j]; + lbuffer[i] = (flac_float_t)(scale * (double)s); + s = abs32(s); + block_peak = local_max(block_peak, s); + } + samples -= n; + if(AnalyzeSamples(lbuffer, 0, n, 1) != GAIN_ANALYSIS_OK) + return false; + } + } + } + + { + const double peak_scale = (double)(1u << (bps - 1)); + double peak = (double)block_peak / peak_scale; + if(peak > title_peak_) + title_peak_ = peak; + if(peak > album_peak_) + album_peak_ = peak; + } + + return true; +} + +void grabbag__replaygain_get_album(float *gain, float *peak) +{ + *gain = (float)GetAlbumGain(); + *peak = (float)album_peak_; + album_peak_ = 0.0; +} + +void grabbag__replaygain_get_title(float *gain, float *peak) +{ + *gain = (float)GetTitleGain(); + *peak = (float)title_peak_; + title_peak_ = 0.0; +} + + +typedef struct { + uint32_t channels; + uint32_t bits_per_sample; + uint32_t sample_rate; + FLAC__bool error; +} DecoderInstance; + +static FLAC__StreamDecoderWriteStatus write_callback_(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) +{ + DecoderInstance *instance = (DecoderInstance*)client_data; + const uint32_t bits_per_sample = frame->header.bits_per_sample; + const uint32_t channels = frame->header.channels; + const uint32_t sample_rate = frame->header.sample_rate; + const uint32_t samples = frame->header.blocksize; + + (void)decoder; + + if( + !instance->error && + (channels == 2 || channels == 1) && + bits_per_sample == instance->bits_per_sample && + channels == instance->channels && + sample_rate == instance->sample_rate + ) { + instance->error = !grabbag__replaygain_analyze(buffer, channels==2, bits_per_sample, samples); + } + else { + instance->error = true; + } + + if(!instance->error) + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + else + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; +} + +static void metadata_callback_(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + DecoderInstance *instance = (DecoderInstance*)client_data; + + (void)decoder; + + if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { + instance->bits_per_sample = metadata->data.stream_info.bits_per_sample; + instance->channels = metadata->data.stream_info.channels; + instance->sample_rate = metadata->data.stream_info.sample_rate; + + if(instance->channels != 1 && instance->channels != 2) { + instance->error = true; + return; + } + + if(!grabbag__replaygain_is_valid_sample_frequency(instance->sample_rate)) { + instance->error = true; + return; + } + } +} + +static void error_callback_(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + DecoderInstance *instance = (DecoderInstance*)client_data; + + (void)decoder, (void)status; + + instance->error = true; +} + +const char *grabbag__replaygain_analyze_file(const char *filename, float *title_gain, float *title_peak) +{ + DecoderInstance instance; + FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new(); + + if(0 == decoder) + return "memory allocation error"; + + instance.error = false; + + /* It does these three by default but lets be explicit: */ + FLAC__stream_decoder_set_md5_checking(decoder, false); + FLAC__stream_decoder_set_metadata_ignore_all(decoder); + FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_STREAMINFO); + + if(FLAC__stream_decoder_init_file(decoder, filename, write_callback_, metadata_callback_, error_callback_, &instance) != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + FLAC__stream_decoder_delete(decoder); + return "initializing decoder"; + } + + if(!FLAC__stream_decoder_process_until_end_of_stream(decoder) || instance.error) { + FLAC__stream_decoder_delete(decoder); + return "decoding file"; + } + + FLAC__stream_decoder_delete(decoder); + + grabbag__replaygain_get_title(title_gain, title_peak); + + return 0; +} + +const char *grabbag__replaygain_store_to_vorbiscomment(FLAC__StreamMetadata *block, float album_gain, float album_peak, float title_gain, float title_peak) +{ + const char *error; + + if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_reference(block))) + return error; + + if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_title(block, title_gain, title_peak))) + return error; + + if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_album(block, album_gain, album_peak))) + return error; + + return 0; +} + +const char *grabbag__replaygain_store_to_vorbiscomment_reference(FLAC__StreamMetadata *block) +{ + FLAC__ASSERT(0 != block); + FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); + + if(FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS) < 0) + return "memory allocation error"; + + if(!append_tag_(block, reference_format_, GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS, ReplayGainReferenceLoudness)) + return "memory allocation error"; + + return 0; +} + +const char *grabbag__replaygain_store_to_vorbiscomment_album(FLAC__StreamMetadata *block, float album_gain, float album_peak) +{ + FLAC__ASSERT(0 != block); + FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); + + if( + FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN) < 0 || + FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK) < 0 + ) + return "memory allocation error"; + + if( + !append_tag_(block, gain_format_, GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN, album_gain) || + !append_tag_(block, peak_format_, GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK, album_peak) + ) + return "memory allocation error"; + + return 0; +} + +const char *grabbag__replaygain_store_to_vorbiscomment_title(FLAC__StreamMetadata *block, float title_gain, float title_peak) +{ + FLAC__ASSERT(0 != block); + FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); + + if( + FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN) < 0 || + FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK) < 0 + ) + return "memory allocation error"; + + if( + !append_tag_(block, gain_format_, GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN, title_gain) || + !append_tag_(block, peak_format_, GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK, title_peak) + ) + return "memory allocation error"; + + return 0; +} + +static const char *store_to_file_pre_(const char *filename, FLAC__Metadata_Chain **chain, FLAC__StreamMetadata **block) +{ + FLAC__Metadata_Iterator *iterator; + const char *error; + FLAC__bool found_vc_block = false; + + if(0 == (*chain = FLAC__metadata_chain_new())) + return "memory allocation error"; + + if(!FLAC__metadata_chain_read(*chain, filename)) { + error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; + FLAC__metadata_chain_delete(*chain); + return error; + } + + if(0 == (iterator = FLAC__metadata_iterator_new())) { + FLAC__metadata_chain_delete(*chain); + return "memory allocation error"; + } + + FLAC__metadata_iterator_init(iterator, *chain); + + do { + *block = FLAC__metadata_iterator_get_block(iterator); + if((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) + found_vc_block = true; + } while(!found_vc_block && FLAC__metadata_iterator_next(iterator)); + + if(!found_vc_block) { + /* create a new block */ + *block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); + if(0 == *block) { + FLAC__metadata_chain_delete(*chain); + FLAC__metadata_iterator_delete(iterator); + return "memory allocation error"; + } + while(FLAC__metadata_iterator_next(iterator)) + ; + if(!FLAC__metadata_iterator_insert_block_after(iterator, *block)) { + error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; + FLAC__metadata_chain_delete(*chain); + FLAC__metadata_iterator_delete(iterator); + return error; + } + /* iterator is left pointing to new block */ + FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == *block); + } + + FLAC__metadata_iterator_delete(iterator); + + FLAC__ASSERT(0 != *block); + FLAC__ASSERT((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); + + return 0; +} + +static const char *store_to_file_post_(const char *filename, FLAC__Metadata_Chain *chain, FLAC__bool preserve_modtime) +{ + struct flac_stat_s stats; + const FLAC__bool have_stats = get_file_stats_(filename, &stats); + + (void)grabbag__file_change_stats(filename, /*read_only=*/false); + + FLAC__metadata_chain_sort_padding(chain); + if(!FLAC__metadata_chain_write(chain, /*use_padding=*/true, preserve_modtime)) { + const char *error; + error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]; + FLAC__metadata_chain_delete(chain); + return error; + } + + FLAC__metadata_chain_delete(chain); + + if(have_stats) + set_file_stats_(filename, &stats); + + return 0; +} + +const char *grabbag__replaygain_store_to_file(const char *filename, float album_gain, float album_peak, float title_gain, float title_peak, FLAC__bool preserve_modtime) +{ + FLAC__Metadata_Chain *chain; + FLAC__StreamMetadata *block = NULL; + const char *error; + + if(0 != (error = store_to_file_pre_(filename, &chain, &block))) + return error; + + if(0 != (error = grabbag__replaygain_store_to_vorbiscomment(block, album_gain, album_peak, title_gain, title_peak))) { + FLAC__metadata_chain_delete(chain); + return error; + } + + if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) + return error; + + return 0; +} + +const char *grabbag__replaygain_store_to_file_reference(const char *filename, FLAC__bool preserve_modtime) +{ + FLAC__Metadata_Chain *chain; + FLAC__StreamMetadata *block = NULL; + const char *error; + + if(0 != (error = store_to_file_pre_(filename, &chain, &block))) + return error; + + if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_reference(block))) { + FLAC__metadata_chain_delete(chain); + return error; + } + + if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) + return error; + + return 0; +} + +const char *grabbag__replaygain_store_to_file_album(const char *filename, float album_gain, float album_peak, FLAC__bool preserve_modtime) +{ + FLAC__Metadata_Chain *chain; + FLAC__StreamMetadata *block = NULL; + const char *error; + + if(0 != (error = store_to_file_pre_(filename, &chain, &block))) + return error; + + if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_album(block, album_gain, album_peak))) { + FLAC__metadata_chain_delete(chain); + return error; + } + + if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) + return error; + + return 0; +} + +const char *grabbag__replaygain_store_to_file_title(const char *filename, float title_gain, float title_peak, FLAC__bool preserve_modtime) +{ + FLAC__Metadata_Chain *chain; + FLAC__StreamMetadata *block = NULL; + const char *error; + + if(0 != (error = store_to_file_pre_(filename, &chain, &block))) + return error; + + if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_title(block, title_gain, title_peak))) { + FLAC__metadata_chain_delete(chain); + return error; + } + + if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) + return error; + + return 0; +} + +static FLAC__bool parse_double_(const FLAC__StreamMetadata_VorbisComment_Entry *entry, double *val) +{ + char s[32], *end; + const char *p, *q; + double v; + + FLAC__ASSERT(0 != entry); + FLAC__ASSERT(0 != val); + + p = (const char *)entry->entry; + q = strchr(p, '='); + if(0 == q) + return false; + q++; + safe_strncpy(s, q, local_min(sizeof(s), (size_t) (entry->length - (q-p)))); + + v = strtod(s, &end); + if(end == s) + return false; + + *val = v; + return true; +} + +FLAC__bool grabbag__replaygain_load_from_vorbiscomment(const FLAC__StreamMetadata *block, FLAC__bool album_mode, FLAC__bool strict, double *reference, double *gain, double *peak) +{ + int reference_offset, gain_offset, peak_offset; + char *saved_locale; + FLAC__bool res = true; + + FLAC__ASSERT(0 != block); + FLAC__ASSERT(0 != reference); + FLAC__ASSERT(0 != gain); + FLAC__ASSERT(0 != peak); + FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); + + /* Default to current level until overridden by a detected tag; this + * will always be true until we change replaygain_analysis.c + */ + *reference = ReplayGainReferenceLoudness; + + /* + * We need to save the old locale and switch to "C" because the locale + * influences the behaviour of strtod and we want it a certain way. + */ + saved_locale = strdup(setlocale(LC_ALL, 0)); + if (0 == saved_locale) + return false; + setlocale(LC_ALL, "C"); + + if(0 <= (reference_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS))) + (void)parse_double_(block->data.vorbis_comment.comments + reference_offset, reference); + + if(0 > (gain_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)(album_mode? GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN : GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN)))) + res = false; + if(0 > (peak_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)(album_mode? GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK : GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK)))) + res = false; + + if(res && !parse_double_(block->data.vorbis_comment.comments + gain_offset, gain)) + res = false; + if(res && !parse_double_(block->data.vorbis_comment.comments + peak_offset, peak)) + res = false; + if(res && *peak < 0.0) + res = false; + + setlocale(LC_ALL, saved_locale); + free(saved_locale); + + /* something failed; retry with strict */ + if (!res && !strict) + res = grabbag__replaygain_load_from_vorbiscomment(block, !album_mode, /*strict=*/true, reference, gain, peak); + + return res; +} + +double grabbag__replaygain_compute_scale_factor(double peak, double gain, double preamp, FLAC__bool prevent_clipping) +{ + double scale; + FLAC__ASSERT(peak >= 0.0); + gain += preamp; + scale = (float) pow(10.0, gain * 0.05); + if(prevent_clipping && peak > 0.0) { + const double max_scale = (float)(1.0 / peak); + if(scale > max_scale) + scale = max_scale; + } + return scale; +} diff --git a/src/share/grabbag/seektable.c b/src/share/grabbag/seektable.c new file mode 100644 index 0000000..01caa99 --- /dev/null +++ b/src/share/grabbag/seektable.c @@ -0,0 +1,105 @@ +/* grabbag - Convenience lib for various routines common to several tools + * Copyright (C) 2002-2009 Josh Coalson + * Copyright (C) 2011-2023 Xiph.Org Foundation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "share/grabbag.h" +#include "share/compat.h" +#include "FLAC/assert.h" +#include <stdlib.h> /* for atoi() */ +#include <string.h> + +FLAC__bool grabbag__seektable_convert_specification_to_template(const char *spec, FLAC__bool only_explicit_placeholders, FLAC__uint64 total_samples_to_encode, uint32_t sample_rate, FLAC__StreamMetadata *seektable_template, FLAC__bool *spec_has_real_points) +{ + uint32_t i; + const char *pt; + + FLAC__ASSERT(0 != spec); + FLAC__ASSERT(0 != seektable_template); + FLAC__ASSERT(seektable_template->type == FLAC__METADATA_TYPE_SEEKTABLE); + + if(0 != spec_has_real_points) + *spec_has_real_points = false; + + for(pt = spec, i = 0; pt && *pt; i++) { + const char *q = strchr(pt, ';'); + FLAC__ASSERT(0 != q); + + if(q > pt) { + if(0 == strncmp(pt, "X;", 2)) { /* -S X */ + if(!FLAC__metadata_object_seektable_template_append_placeholders(seektable_template, 1)) + return false; + } + else if(q[-1] == 'x') { /* -S #x */ + if(total_samples_to_encode > 0) { /* we can only do these if we know the number of samples to encode up front */ + if(0 != spec_has_real_points) + *spec_has_real_points = true; + if(!only_explicit_placeholders) { + const int n = (uint32_t)atoi(pt); + if(n > 0) + if(!FLAC__metadata_object_seektable_template_append_spaced_points(seektable_template, (uint32_t)n, total_samples_to_encode)) + return false; + } + } + } + else if(q[-1] == 's') { /* -S #s */ + if(total_samples_to_encode > 0 && sample_rate > 0) { /* we can only do these if we know the number of samples and sample rate to encode up front */ + if(0 != spec_has_real_points) + *spec_has_real_points = true; + if(!only_explicit_placeholders) { + const double sec = atof(pt); + if(sec > 0.0) { + uint32_t samples = (uint32_t)(sec * (double)sample_rate); + /* Restrict seekpoints to two per second of audio. */ + samples = samples < sample_rate / 2 ? sample_rate / 2 : samples; + if(samples > 0) { + /* +1 for the initial point at sample 0 */ + if(!FLAC__metadata_object_seektable_template_append_spaced_points_by_samples(seektable_template, samples, total_samples_to_encode)) + return false; + } + } + } + } + } + else { /* -S # */ + if(0 != spec_has_real_points) + *spec_has_real_points = true; + if(!only_explicit_placeholders) { + char *endptr; + const FLAC__int64 n = (FLAC__int64)strtoll(pt, &endptr, 10); + if( + (n > 0 || (endptr > pt && *endptr == ';')) && /* is a valid number (extra check needed for "0") */ + (total_samples_to_encode == 0 || (FLAC__uint64)n < total_samples_to_encode) /* number is not >= the known total_samples_to_encode */ + ) + if(!FLAC__metadata_object_seektable_template_append_point(seektable_template, (FLAC__uint64)n)) + return false; + } + } + } + + pt = ++q; + } + + if(!FLAC__metadata_object_seektable_template_sort(seektable_template, /*compact=*/true)) + return false; + + return true; +} diff --git a/src/share/grabbag/snprintf.c b/src/share/grabbag/snprintf.c new file mode 100644 index 0000000..bd7ffba --- /dev/null +++ b/src/share/grabbag/snprintf.c @@ -0,0 +1,101 @@ +/* grabbag - Convenience lib for various routines common to several tools + * Copyright (C) 2013-2023 Xiph.Org Foundation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Xiph.org Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdio.h> +#include <stdarg.h> + +#include "share/compat.h" + +/* + * FLAC needs to compile and work correctly on systems with a normal ISO C99 + * snprintf as well as Microsoft Visual Studio which has an non-standards + * conformant snprint_s function. + * + * The important difference occurs when the resultant string (plus string + * terminator) would have been longer than the supplied size parameter. When + * this happens, ISO C's snprintf returns the length of resultant string, but + * does not over-write the end of the buffer. MS's snprintf_s in this case + * returns -1. + * + * The _MSC_VER code below attempts to modify the return code for vsnprintf_s + * to something that is more compatible with the behaviour of the ISO C version. + */ + +int +flac_snprintf(char *str, size_t size, const char *fmt, ...) +{ + va_list va; + int rc; + +#if defined _MSC_VER + if (size == 0) + return 1024; +#endif + + va_start (va, fmt); + +#if defined _MSC_VER + rc = vsnprintf_s (str, size, _TRUNCATE, fmt, va); + if (rc < 0) + rc = size - 1; +#elif defined __MINGW32__ + rc = __mingw_vsnprintf (str, size, fmt, va); +#else + rc = vsnprintf (str, size, fmt, va); +#endif + va_end (va); + + return rc; +} + +int +flac_vsnprintf(char *str, size_t size, const char *fmt, va_list va) +{ + int rc; + +#if defined _MSC_VER + if (size == 0) + return 1024; + rc = vsnprintf_s (str, size, _TRUNCATE, fmt, va); + if (rc < 0) + rc = size - 1; +#elif defined __MINGW32__ + rc = __mingw_vsnprintf (str, size, fmt, va); +#else + rc = vsnprintf (str, size, fmt, va); +#endif + + return rc; +} diff --git a/src/share/replaygain_analysis/CMakeLists.txt b/src/share/replaygain_analysis/CMakeLists.txt new file mode 100644 index 0000000..4362b90 --- /dev/null +++ b/src/share/replaygain_analysis/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(replaygain_analysis STATIC + replaygain_analysis.c) diff --git a/src/share/replaygain_analysis/replaygain_analysis.c b/src/share/replaygain_analysis/replaygain_analysis.c new file mode 100644 index 0000000..37b77ab --- /dev/null +++ b/src/share/replaygain_analysis/replaygain_analysis.c @@ -0,0 +1,575 @@ +/* + * ReplayGainAnalysis - analyzes input samples and give the recommended dB change + * Copyright (C) 2001 David Robinson and Glen Sawyer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * concept and filter values by David Robinson (David@Robinson.org) + * -- blame him if you think the idea is flawed + * original coding by Glen Sawyer (glensawyer@hotmail.com) + * -- blame him if you think this runs too slowly, or the coding is otherwise flawed + * + * lots of code improvements by Frank Klemm ( http://www.uni-jena.de/~pfk/mpp/ ) + * -- credit him for all the _good_ programming ;) + * + * minor cosmetic tweaks to integrate with FLAC by Josh Coalson + * + * + * For an explanation of the concepts and the basic algorithms involved, go to: + * http://www.replaygain.org/ + */ + +/* + * Here's the deal. Call + * + * InitGainAnalysis ( long samplefreq ); + * + * to initialize everything. Call + * + * AnalyzeSamples ( const flac_float_t* left_samples, + * const flac_float_t* right_samples, + * size_t num_samples, + * int num_channels ); + * + * as many times as you want, with as many or as few samples as you want. + * If mono, pass the sample buffer in through left_samples, leave + * right_samples NULL, and make sure num_channels = 1. + * + * GetTitleGain() + * + * will return the recommended dB level change for all samples analyzed + * SINCE THE LAST TIME you called GetTitleGain() OR InitGainAnalysis(). + * + * GetAlbumGain() + * + * will return the recommended dB level change for all samples analyzed + * since InitGainAnalysis() was called and finalized with GetTitleGain(). + * + * Pseudo-code to process an album: + * + * flac_float_t l_samples [4096]; + * flac_float_t r_samples [4096]; + * size_t num_samples; + * uint32_t num_songs; + * uint32_t i; + * + * InitGainAnalysis ( 44100 ); + * for ( i = 1; i <= num_songs; i++ ) { + * while ( ( num_samples = getSongSamples ( song[i], left_samples, right_samples ) ) > 0 ) + * AnalyzeSamples ( left_samples, right_samples, num_samples, 2 ); + * fprintf ("Recommended dB change for song %2d: %+6.2f dB\n", i, GetTitleGain() ); + * } + * fprintf ("Recommended dB change for whole album: %+6.2f dB\n", GetAlbumGain() ); + */ + +/* + * So here's the main source of potential code confusion: + * + * The filters applied to the incoming samples are IIR filters, + * meaning they rely on up to <filter order> number of previous samples + * AND up to <filter order> number of previous filtered samples. + * + * I set up the AnalyzeSamples routine to minimize memory usage and interface + * complexity. The speed isn't compromised too much (I don't think), but the + * internal complexity is higher than it should be for such a relatively + * simple routine. + * + * Optimization/clarity suggestions are welcome. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include "share/alloc.h" +#include "share/compat.h" +#include "share/replaygain_analysis.h" + +flac_float_t ReplayGainReferenceLoudness = 89.0; /* in dB SPL */ + +#define YULE_ORDER 10 +#define BUTTER_ORDER 2 +#define RMS_PERCENTILE 0.95 /* percentile which is louder than the proposed level */ +#define RMS_WINDOW_TIME 50 /* Time slice size [ms] */ +#define STEPS_per_dB 100. /* Table entries per dB */ +#define MAX_dB 120. /* Table entries for 0...MAX_dB (normal max. values are 70...80 dB) */ + +#define MAX_ORDER (BUTTER_ORDER > YULE_ORDER ? BUTTER_ORDER : YULE_ORDER) +#define PINK_REF 64.82 /* 298640883795 */ /* calibration value */ + +static flac_float_t linprebuf [MAX_ORDER * 2]; +static flac_float_t* linpre; /* left input samples, with pre-buffer */ +static flac_float_t* lstepbuf; +static flac_float_t* lstep; /* left "first step" (i.e. post first filter) samples */ +static flac_float_t* loutbuf; +static flac_float_t* lout; /* left "out" (i.e. post second filter) samples */ +static flac_float_t rinprebuf [MAX_ORDER * 2]; +static flac_float_t* rinpre; /* right input samples ... */ +static flac_float_t* rstepbuf; +static flac_float_t* rstep; +static flac_float_t* routbuf; +static flac_float_t* rout; +static uint32_t sampleWindow; /* number of samples required to reach number of milliseconds required for RMS window */ +static uint64_t totsamp; +static double lsum; +static double rsum; +#if 0 +static uint32_t A [(size_t)(STEPS_per_dB * MAX_dB)]; +static uint32_t B [(size_t)(STEPS_per_dB * MAX_dB)]; +#else +/* [JEC] Solaris Forte compiler doesn't like float calc in array indices */ +static uint32_t A [120 * 100]; +static uint32_t B [120 * 100]; +#endif + +#ifdef _MSC_VER +#pragma warning ( disable : 4305 ) +#endif + +struct ReplayGainFilter { + long rate; + uint32_t downsample; + flac_float_t BYule[YULE_ORDER+1]; + flac_float_t AYule[YULE_ORDER+1]; + flac_float_t BButter[BUTTER_ORDER+1]; + flac_float_t AButter[BUTTER_ORDER+1]; +}; + +static struct ReplayGainFilter *replaygainfilter; + +static const struct ReplayGainFilter ReplayGainFilters[] = { + + { + 48000, 0, /* ORIGINAL */ + { 0.03857599435200, -0.02160367184185, -0.00123395316851, -0.00009291677959, -0.01655260341619, 0.02161526843274, -0.02074045215285, 0.00594298065125, 0.00306428023191, 0.00012025322027, 0.00288463683916 }, + { 1.00000000000000, -3.84664617118067, 7.81501653005538, -11.34170355132042, 13.05504219327545, -12.28759895145294, 9.48293806319790, -5.87257861775999, 2.75465861874613, -0.86984376593551, 0.13919314567432 }, + { 0.98621192462708, -1.97242384925416, 0.98621192462708 }, + { 1.00000000000000, -1.97223372919527, 0.97261396931306 }, + }, + + { + 44100, 0, /* ORIGINAL */ + { 0.05418656406430, -0.02911007808948, -0.00848709379851, -0.00851165645469, -0.00834990904936, 0.02245293253339, -0.02596338512915, 0.01624864962975, -0.00240879051584, 0.00674613682247, -0.00187763777362 }, + { 1.00000000000000, -3.47845948550071, 6.36317777566148, -8.54751527471874, 9.47693607801280, -8.81498681370155, 6.85401540936998, -4.39470996079559, 2.19611684890774, -0.75104302451432, 0.13149317958808 }, + { 0.98500175787242, -1.97000351574484, 0.98500175787242 }, + { 1.00000000000000, -1.96977855582618, 0.97022847566350 }, + }, + + { + 37800, 0, + { 0.10296717174470, -0.04877975583256, -0.02878009075237, -0.03519509188311, 0.02888717172493, -0.00609872684844, 0.00209851217112, 0.00911704668543, 0.01154404718589, -0.00630293688700, 0.00107527155228 }, + { 1.00000000000000, -2.64848054923531, 3.58406058405771, -3.83794914179161, 3.90142345804575, -3.50179818637243, 2.67085284083076, -1.82581142372418, 1.09530368139801, -0.47689017820395, 0.11171431535905 }, + { 0.98252400815195, -1.96504801630391, 0.98252400815195 }, + { 1.00000000000000, -1.96474258269041, 0.96535344991740 }, + }, + + { + 36000, 0, + { 0.11572297028613, -0.04120916051252, -0.04977731768022, -0.01047308680426, 0.00750863219157, 0.00055507694408, 0.00140344192886, 0.01286095246036, 0.00998223033885, -0.00725013810661, 0.00326503346879 }, + { 1.00000000000000, -2.43606802820871, 3.01907406973844, -2.90372016038192, 2.67947188094303, -2.17606479220391, 1.44912956803015, -0.87785765549050, 0.53592202672557, -0.26469344817509, 0.07495878059717 }, + { 0.98165826840326, -1.96331653680652, 0.98165826840326 }, + { 1.00000000000000, -1.96298008938934, 0.96365298422371 }, + }, + + { + 32000, 0, /* ORIGINAL */ + { 0.15457299681924, -0.09331049056315, -0.06247880153653, 0.02163541888798, -0.05588393329856, 0.04781476674921, 0.00222312597743, 0.03174092540049, -0.01390589421898, 0.00651420667831, -0.00881362733839 }, + { 1.00000000000000, -2.37898834973084, 2.84868151156327, -2.64577170229825, 2.23697657451713, -1.67148153367602, 1.00595954808547, -0.45953458054983, 0.16378164858596, -0.05032077717131, 0.02347897407020 }, + { 0.97938932735214, -1.95877865470428, 0.97938932735214 }, + { 1.00000000000000, -1.95835380975398, 0.95920349965459 }, + }, + + { + 28000, 0, + { 0.23882392323383, -0.22007791534089, -0.06014581950332, 0.05004458058021, -0.03293111254977, 0.02348678189717, 0.04290549799671, -0.00938141862174, 0.00015095146303, -0.00712601540885, -0.00626520210162 }, + { 1.00000000000000, -2.06894080899139, 1.76944699577212, -0.81404732584187, 0.25418286850232, -0.30340791669762, 0.35616884070937, -0.14967310591258, -0.07024154183279, 0.11078404345174, -0.03551838002425 }, + { 0.97647981663949, -1.95295963327897, 0.97647981663949 }, + { 1.00000000000000, -1.95240635772520, 0.95351290883275 }, + + }, + + { + 24000, 0, /* ORIGINAL */ + { 0.30296907319327, -0.22613988682123, -0.08587323730772, 0.03282930172664, -0.00915702933434, -0.02364141202522, -0.00584456039913, 0.06276101321749, -0.00000828086748, 0.00205861885564, -0.02950134983287 }, + { 1.00000000000000, -1.61273165137247, 1.07977492259970, -0.25656257754070, -0.16276719120440, -0.22638893773906, 0.39120800788284, -0.22138138954925, 0.04500235387352, 0.02005851806501, 0.00302439095741 }, + { 0.97531843204928, -1.95063686409857, 0.97531843204928 }, + { 1.00000000000000, -1.95002759149878, 0.95124613669835 }, + }, + + { + 22050, 0, /* ORIGINAL */ + { 0.33642304856132, -0.25572241425570, -0.11828570177555, 0.11921148675203, -0.07834489609479, -0.00469977914380, -0.00589500224440, 0.05724228140351, 0.00832043980773, -0.01635381384540, -0.01760176568150 }, + { 1.00000000000000, -1.49858979367799, 0.87350271418188, 0.12205022308084, -0.80774944671438, 0.47854794562326, -0.12453458140019, -0.04067510197014, 0.08333755284107, -0.04237348025746, 0.02977207319925 }, + { 0.97316523498161, -1.94633046996323, 0.97316523498161 }, + { 1.00000000000000, -1.94561023566527, 0.94705070426118 }, + }, + + { + 18900, 0, + { 0.38412657295385, -0.44533729608120, 0.20426638066221, -0.28031676047946, 0.31484202614802, -0.26078311203207, 0.12925201224848, -0.01141164696062, 0.03036522115769, -0.03776339305406, 0.00692036603586 }, + { 1.00000000000000, -1.74403915585708, 1.96686095832499, -2.10081452941881, 1.90753918182846, -1.83814263754422, 1.36971352214969, -0.77883609116398, 0.39266422457649, -0.12529383592986, 0.05424760697665 }, + { 0.96535326815829, -1.93070653631658, 0.96535326815829 }, + { 1.00000000000000, -1.92950577983524, 0.93190729279793 }, + }, + + { + 16000, 0, /* ORIGINAL */ + { 0.44915256608450, -0.14351757464547, -0.22784394429749, -0.01419140100551, 0.04078262797139, -0.12398163381748, 0.04097565135648, 0.10478503600251, -0.01863887810927, -0.03193428438915, 0.00541907748707 }, + { 1.00000000000000, -0.62820619233671, 0.29661783706366, -0.37256372942400, 0.00213767857124, -0.42029820170918, 0.22199650564824, 0.00613424350682, 0.06747620744683, 0.05784820375801, 0.03222754072173 }, + { 0.96454515552826, -1.92909031105652, 0.96454515552826 }, + { 1.00000000000000, -1.92783286977036, 0.93034775234268 }, + }, + + { + 12000, 0, /* ORIGINAL */ + { 0.56619470757641, -0.75464456939302, 0.16242137742230, 0.16744243493672, -0.18901604199609, 0.30931782841830, -0.27562961986224, 0.00647310677246, 0.08647503780351, -0.03788984554840, -0.00588215443421 }, + { 1.00000000000000, -1.04800335126349, 0.29156311971249, -0.26806001042947, 0.00819999645858, 0.45054734505008, -0.33032403314006, 0.06739368333110, -0.04784254229033, 0.01639907836189, 0.01807364323573 }, + { 0.96009142950541, -1.92018285901082, 0.96009142950541 }, + { 1.00000000000000, -1.91858953033784, 0.92177618768381 }, + }, + + { + 11025, 0, /* ORIGINAL */ + { 0.58100494960553, -0.53174909058578, -0.14289799034253, 0.17520704835522, 0.02377945217615, 0.15558449135573, -0.25344790059353, 0.01628462406333, 0.06920467763959, -0.03721611395801, -0.00749618797172 }, + { 1.00000000000000, -0.51035327095184, -0.31863563325245, -0.20256413484477, 0.14728154134330, 0.38952639978999, -0.23313271880868, -0.05246019024463, -0.02505961724053, 0.02442357316099, 0.01818801111503 }, + { 0.95856916599601, -1.91713833199203, 0.95856916599601 }, + { 1.00000000000000, -1.91542108074780, 0.91885558323625 }, + }, + + { + 8000, 0, /* ORIGINAL */ + { 0.53648789255105, -0.42163034350696, -0.00275953611929, 0.04267842219415, -0.10214864179676, 0.14590772289388, -0.02459864859345, -0.11202315195388, -0.04060034127000, 0.04788665548180, -0.02217936801134 }, + { 1.00000000000000, -0.25049871956020, -0.43193942311114, -0.03424681017675, -0.04678328784242, 0.26408300200955, 0.15113130533216, -0.17556493366449, -0.18823009262115, 0.05477720428674, 0.04704409688120 }, + { 0.94597685600279, -1.89195371200558, 0.94597685600279 }, + { 1.00000000000000, -1.88903307939452, 0.89487434461664 }, + }, + +}; + +#ifdef _MSC_VER +#pragma warning ( default : 4305 ) +#endif + +/* When calling this procedure, make sure that ip[-order] and op[-order] point to real data! */ + +static void +filter ( const flac_float_t* input, flac_float_t* output, size_t nSamples, const flac_float_t* a, const flac_float_t* b, size_t order, uint32_t downsample ) +{ + double y; + size_t i; + size_t k; + + const flac_float_t* input_head = input; + const flac_float_t* input_tail; + + flac_float_t* output_head = output; + flac_float_t* output_tail; + + for ( i = 0; i < nSamples; i++, input_head += downsample, ++output_head ) { + + input_tail = input_head; + output_tail = output_head; + + y = *input_head * b[0]; + + for ( k = 1; k <= order; k++ ) { + input_tail -= downsample; + --output_tail; + y += *input_tail * b[k] - *output_tail * a[k]; + } + + output[i] = (flac_float_t)y; + } +} + +/* returns a INIT_GAIN_ANALYSIS_OK if successful, INIT_GAIN_ANALYSIS_ERROR if not */ + +static struct ReplayGainFilter* +CreateGainFilter ( long samplefreq ) +{ + uint32_t i; + long maxrate = 0; + uint32_t downsample = 1; + struct ReplayGainFilter* gainfilter = malloc(sizeof(*gainfilter)); + + if ( !gainfilter ) + return 0; + + while (1) { + for ( i = 0; i < sizeof(ReplayGainFilters)/sizeof(ReplayGainFilters[0]); ++i ) { + if (maxrate < ReplayGainFilters[i].rate) + maxrate = ReplayGainFilters[i].rate; + + if ( ReplayGainFilters[i].rate == samplefreq ) { + *gainfilter = ReplayGainFilters[i]; + gainfilter->downsample = downsample; + return gainfilter; + } + } + + if (samplefreq < maxrate) + break; + + while (samplefreq > maxrate) { + downsample *= 2; + samplefreq /= 2; + } + } + + free(gainfilter); + + return 0; +} + +static void* +ReallocateWindowBuffer(uint32_t window_size, flac_float_t **window_buffer) +{ + *window_buffer = safe_realloc_(*window_buffer, sizeof(**window_buffer) * (window_size + MAX_ORDER)); + return *window_buffer; +} + +static int +ResetSampleFrequency ( long samplefreq ) { + int i; + + free(replaygainfilter); + + replaygainfilter = CreateGainFilter( samplefreq ); + + if ( ! replaygainfilter) + return INIT_GAIN_ANALYSIS_ERROR; + + sampleWindow = + (replaygainfilter->rate * RMS_WINDOW_TIME + 1000-1) / 1000; + + if ( ! ReallocateWindowBuffer(sampleWindow, &lstepbuf) || + ! ReallocateWindowBuffer(sampleWindow, &rstepbuf) || + ! ReallocateWindowBuffer(sampleWindow, &loutbuf) || + ! ReallocateWindowBuffer(sampleWindow, &routbuf) ) { + + return INIT_GAIN_ANALYSIS_ERROR; + } + + /* zero out initial values */ + for ( i = 0; i < MAX_ORDER; i++ ) + linprebuf[i] = lstepbuf[i] = loutbuf[i] = rinprebuf[i] = rstepbuf[i] = routbuf[i] = 0.; + + lsum = 0.; + rsum = 0.; + totsamp = 0; + + memset ( A, 0, sizeof(A) ); + + return INIT_GAIN_ANALYSIS_OK; +} + +int +ValidGainFrequency ( long samplefreq ) +{ + struct ReplayGainFilter* gainfilter = CreateGainFilter( samplefreq ); + + if (gainfilter == 0) { + return 0; + } else { + free(gainfilter); + return 1; + } +} + +int +InitGainAnalysis ( long samplefreq ) +{ + if (ResetSampleFrequency(samplefreq) != INIT_GAIN_ANALYSIS_OK) { + return INIT_GAIN_ANALYSIS_ERROR; + } + + linpre = linprebuf + MAX_ORDER; + rinpre = rinprebuf + MAX_ORDER; + lstep = lstepbuf + MAX_ORDER; + rstep = rstepbuf + MAX_ORDER; + lout = loutbuf + MAX_ORDER; + rout = routbuf + MAX_ORDER; + + memset ( B, 0, sizeof(B) ); + + return INIT_GAIN_ANALYSIS_OK; +} + +/* returns GAIN_ANALYSIS_OK if successful, GAIN_ANALYSIS_ERROR if not */ + +int +AnalyzeSamples ( const flac_float_t* left_samples, const flac_float_t* right_samples, size_t num_samples, int num_channels ) +{ + uint32_t downsample = replaygainfilter->downsample; + const flac_float_t* curleft; + const flac_float_t* curright; + long prebufsamples; + long batchsamples; + long cursamples; + long cursamplepos; + int i; + + num_samples /= downsample; + + if ( num_samples == 0 ) + return GAIN_ANALYSIS_OK; + + cursamplepos = 0; + batchsamples = num_samples; + + switch ( num_channels) { + case 1: right_samples = left_samples; + case 2: break; + default: return GAIN_ANALYSIS_ERROR; + } + + prebufsamples = MAX_ORDER; + if ((size_t) prebufsamples > num_samples) + prebufsamples = num_samples; + + for ( i = 0; i < prebufsamples; ++i ) { + linprebuf[i+MAX_ORDER] = left_samples [i * downsample]; + rinprebuf[i+MAX_ORDER] = right_samples[i * downsample]; + } + + while ( batchsamples > 0 ) { + cursamples = batchsamples > (long)(sampleWindow-totsamp) ? (long)(sampleWindow - totsamp) : batchsamples; + if ( cursamplepos < MAX_ORDER ) { + downsample = 1; + curleft = linpre+cursamplepos; + curright = rinpre+cursamplepos; + if (cursamples > MAX_ORDER - cursamplepos ) + cursamples = MAX_ORDER - cursamplepos; + } + else { + downsample = replaygainfilter->downsample; + curleft = left_samples + cursamplepos * downsample; + curright = right_samples + cursamplepos * downsample; + } + + filter ( curleft , lstep + totsamp, cursamples, replaygainfilter->AYule, replaygainfilter->BYule, YULE_ORDER, downsample ); + filter ( curright, rstep + totsamp, cursamples, replaygainfilter->AYule, replaygainfilter->BYule, YULE_ORDER, downsample ); + + filter ( lstep + totsamp, lout + totsamp, cursamples, replaygainfilter->AButter, replaygainfilter->BButter, BUTTER_ORDER, 1 ); + filter ( rstep + totsamp, rout + totsamp, cursamples, replaygainfilter->AButter, replaygainfilter->BButter, BUTTER_ORDER, 1 ); + + for ( i = 0; i < cursamples; i++ ) { /* Get the squared values */ + lsum += lout [totsamp+i] * lout [totsamp+i]; + rsum += rout [totsamp+i] * rout [totsamp+i]; + } + + batchsamples -= cursamples; + cursamplepos += cursamples; + totsamp += cursamples; + if ( totsamp == sampleWindow ) { /* Get the Root Mean Square (RMS) for this set of samples */ + double val = STEPS_per_dB * 10. * log10 ( (lsum+rsum) / totsamp * 0.5 + 1.e-37 ); + int ival = (int) val; + if ( ival < 0 ) ival = 0; + if ( ival >= (int)(sizeof(A)/sizeof(*A)) ) ival = (int)(sizeof(A)/sizeof(*A)) - 1; + A [ival]++; + lsum = rsum = 0.; + memmove ( loutbuf , loutbuf + totsamp, MAX_ORDER * sizeof(flac_float_t) ); + memmove ( routbuf , routbuf + totsamp, MAX_ORDER * sizeof(flac_float_t) ); + memmove ( lstepbuf, lstepbuf + totsamp, MAX_ORDER * sizeof(flac_float_t) ); + memmove ( rstepbuf, rstepbuf + totsamp, MAX_ORDER * sizeof(flac_float_t) ); + totsamp = 0; + } + if ( totsamp > sampleWindow ) /* somehow I really screwed up: Error in programming! Contact author about totsamp > sampleWindow */ + return GAIN_ANALYSIS_ERROR; + } + + if ( num_samples < MAX_ORDER ) { + memmove ( linprebuf, linprebuf + num_samples, (MAX_ORDER-num_samples) * sizeof(flac_float_t) ); + memmove ( rinprebuf, rinprebuf + num_samples, (MAX_ORDER-num_samples) * sizeof(flac_float_t) ); + memcpy ( linprebuf + MAX_ORDER - num_samples, left_samples, num_samples * sizeof(flac_float_t) ); + memcpy ( rinprebuf + MAX_ORDER - num_samples, right_samples, num_samples * sizeof(flac_float_t) ); + } + else { + downsample = replaygainfilter->downsample; + + left_samples += (num_samples - MAX_ORDER) * downsample; + right_samples += (num_samples - MAX_ORDER) * downsample; + + for ( i = 0; i < MAX_ORDER; ++i ) { + linprebuf[i] = left_samples [i * downsample]; + rinprebuf[i] = right_samples[i * downsample]; + } + } + + return GAIN_ANALYSIS_OK; +} + + +static flac_float_t +analyzeResult ( uint32_t* Array, size_t len ) +{ + uint32_t elems; + int32_t upper; + size_t i; + + elems = 0; + for ( i = 0; i < len; i++ ) + elems += Array[i]; + if ( elems == 0 ) + return GAIN_NOT_ENOUGH_SAMPLES; + +/* workaround for GCC bug #61423: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61423 */ +#if 0 + upper = (int32_t) ceil (elems * (1. - RMS_PERCENTILE)); +#else + upper = (int32_t) (elems / 20 + ((elems % 20) ? 1 : 0)); +#endif + for ( i = len; i-- > 0; ) { + if ( (upper -= Array[i]) <= 0 ) + break; + } + + return (flac_float_t) ((flac_float_t)PINK_REF - (flac_float_t)i / (flac_float_t)STEPS_per_dB); +} + + +flac_float_t +GetTitleGain ( void ) +{ + flac_float_t retval; + uint32_t i; + + retval = analyzeResult ( A, sizeof(A)/sizeof(*A) ); + + for ( i = 0; i < sizeof(A)/sizeof(*A); i++ ) { + B[i] += A[i]; + A[i] = 0; + } + + for ( i = 0; i < MAX_ORDER; i++ ) + linprebuf[i] = lstepbuf[i] = loutbuf[i] = rinprebuf[i] = rstepbuf[i] = routbuf[i] = 0.f; + + totsamp = 0; + lsum = rsum = 0.; + return retval; +} + + +flac_float_t +GetAlbumGain ( void ) +{ + return analyzeResult ( B, sizeof(B)/sizeof(*B) ); +} + +/* end of replaygain_analysis.c */ diff --git a/src/share/replaygain_synthesis/CMakeLists.txt b/src/share/replaygain_synthesis/CMakeLists.txt new file mode 100644 index 0000000..0736f4f --- /dev/null +++ b/src/share/replaygain_synthesis/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(replaygain_synthesis STATIC + replaygain_synthesis.c) diff --git a/src/share/replaygain_synthesis/replaygain_synthesis.c b/src/share/replaygain_synthesis/replaygain_synthesis.c new file mode 100644 index 0000000..8d4fda6 --- /dev/null +++ b/src/share/replaygain_synthesis/replaygain_synthesis.c @@ -0,0 +1,429 @@ +/* replaygain_synthesis - Routines for applying ReplayGain to a signal + * Copyright (C) 2002-2009 Josh Coalson + * Copyright (C) 2011-2023 Xiph.Org Foundation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +/* + * This is an aggregation of pieces of code from John Edwards' WaveGain + * program. Mostly cosmetic changes were made; otherwise, the dithering + * code is almost untouched and the gain processing was converted from + * processing a whole file to processing chunks of samples. + * + * The original copyright notices for WaveGain's dither.c and wavegain.c + * appear below: + */ +/* + * (c) 2002 John Edwards + * mostly lifted from work by Frank Klemm + * random functions for dithering. + */ +/* + * Copyright (C) 2002 John Edwards + * Additional code by Magnus Holmgren and Gian-Carlo Pascutto + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> /* for memset() */ +#include <math.h> +#include "share/compat.h" +#include "share/replaygain_synthesis.h" +#include "FLAC/assert.h" + +#define FLAC__I64L(x) x##LL + + +/* + * the following is based on parts of dither.c + */ + + +/* + * This is a simple random number generator with good quality for audio purposes. + * It consists of two polycounters with opposite rotation direction and different + * periods. The periods are coprime, so the total period is the product of both. + * + * ------------------------------------------------------------------------------------------------- + * +-> |31:30:29:28:27:26:25:24:23:22:21:20:19:18:17:16:15:14:13:12:11:10: 9: 8: 7: 6: 5: 4: 3: 2: 1: 0| + * | ------------------------------------------------------------------------------------------------- + * | | | | | | | + * | +--+--+--+-XOR-+--------+ + * | | + * +--------------------------------------------------------------------------------------+ + * + * ------------------------------------------------------------------------------------------------- + * |31:30:29:28:27:26:25:24:23:22:21:20:19:18:17:16:15:14:13:12:11:10: 9: 8: 7: 6: 5: 4: 3: 2: 1: 0| <-+ + * ------------------------------------------------------------------------------------------------- | + * | | | | | + * +--+----XOR----+--+ | + * | | + * +----------------------------------------------------------------------------------------+ + * + * + * The first has an period of 3*5*17*257*65537, the second of 7*47*73*178481, + * which gives a period of 18.410.713.077.675.721.215. The result is the + * XORed values of both generators. + */ + +static uint32_t random_int_(void) +{ + static const uint8_t parity_[256] = { + 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1, + 1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0, + 1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0, + 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1, + 1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0, + 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1, + 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1, + 1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0 + }; + static uint32_t r1_ = 1; + static uint32_t r2_ = 1; + + uint32_t t1, t2, t3, t4; + + /* Parity calculation is done via table lookup, this is also available + * on CPUs without parity, can be implemented in C and avoid unpredictable + * jumps and slow rotate through the carry flag operations. + */ + t3 = t1 = r1_; t4 = t2 = r2_; + t1 &= 0xF5; t2 >>= 25; + t1 = parity_[t1]; t2 &= 0x63; + t1 <<= 31; t2 = parity_[t2]; + + return (r1_ = (t3 >> 1) | t1 ) ^ (r2_ = (t4 + t4) | t2 ); +} + +/* gives a equal distributed random number */ +/* between -2^31*mult and +2^31*mult */ +static double random_equi_(double mult) +{ + return mult * (int) random_int_(); +} + +/* gives a triangular distributed random number */ +/* between -2^32*mult and +2^32*mult */ +static double random_triangular_(double mult) +{ + return mult * ( (double) (int) random_int_() + (double) (int) random_int_() ); +} + + +static const float F44_0 [16 + 32] = { + (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, + (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, + + (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, + (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, + + (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, + (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0, (float)0 +}; + + +static const float F44_1 [16 + 32] = { /* SNR(w) = 4.843163 dB, SNR = -3.192134 dB */ + (float) 0.85018292704024355931, (float) 0.29089597350995344721, (float)-0.05021866022121039450, (float)-0.23545456294599161833, + (float)-0.58362726442227032096, (float)-0.67038978965193036429, (float)-0.38566861572833459221, (float)-0.15218663390367969967, + (float)-0.02577543084864530676, (float) 0.14119295297688728127, (float) 0.22398848581628781612, (float) 0.15401727203382084116, + (float) 0.05216161232906000929, (float)-0.00282237820999675451, (float)-0.03042794608323867363, (float)-0.03109780942998826024, + + (float) 0.85018292704024355931, (float) 0.29089597350995344721, (float)-0.05021866022121039450, (float)-0.23545456294599161833, + (float)-0.58362726442227032096, (float)-0.67038978965193036429, (float)-0.38566861572833459221, (float)-0.15218663390367969967, + (float)-0.02577543084864530676, (float) 0.14119295297688728127, (float) 0.22398848581628781612, (float) 0.15401727203382084116, + (float) 0.05216161232906000929, (float)-0.00282237820999675451, (float)-0.03042794608323867363, (float)-0.03109780942998826024, + + (float) 0.85018292704024355931, (float) 0.29089597350995344721, (float)-0.05021866022121039450, (float)-0.23545456294599161833, + (float)-0.58362726442227032096, (float)-0.67038978965193036429, (float)-0.38566861572833459221, (float)-0.15218663390367969967, + (float)-0.02577543084864530676, (float) 0.14119295297688728127, (float) 0.22398848581628781612, (float) 0.15401727203382084116, + (float) 0.05216161232906000929, (float)-0.00282237820999675451, (float)-0.03042794608323867363, (float)-0.03109780942998826024, +}; + + +static const float F44_2 [16 + 32] = { /* SNR(w) = 10.060213 dB, SNR = -12.766730 dB */ + (float) 1.78827593892108555290, (float) 0.95508210637394326553, (float)-0.18447626783899924429, (float)-0.44198126506275016437, + (float)-0.88404052492547413497, (float)-1.42218907262407452967, (float)-1.02037566838362314995, (float)-0.34861755756425577264, + (float)-0.11490230170431934434, (float) 0.12498899339968611803, (float) 0.38065885268563131927, (float) 0.31883491321310506562, + (float) 0.10486838686563442765, (float)-0.03105361685110374845, (float)-0.06450524884075370758, (float)-0.02939198261121969816, + + (float) 1.78827593892108555290, (float) 0.95508210637394326553, (float)-0.18447626783899924429, (float)-0.44198126506275016437, + (float)-0.88404052492547413497, (float)-1.42218907262407452967, (float)-1.02037566838362314995, (float)-0.34861755756425577264, + (float)-0.11490230170431934434, (float) 0.12498899339968611803, (float) 0.38065885268563131927, (float) 0.31883491321310506562, + (float) 0.10486838686563442765, (float)-0.03105361685110374845, (float)-0.06450524884075370758, (float)-0.02939198261121969816, + + (float) 1.78827593892108555290, (float) 0.95508210637394326553, (float)-0.18447626783899924429, (float)-0.44198126506275016437, + (float)-0.88404052492547413497, (float)-1.42218907262407452967, (float)-1.02037566838362314995, (float)-0.34861755756425577264, + (float)-0.11490230170431934434, (float) 0.12498899339968611803, (float) 0.38065885268563131927, (float) 0.31883491321310506562, + (float) 0.10486838686563442765, (float)-0.03105361685110374845, (float)-0.06450524884075370758, (float)-0.02939198261121969816, +}; + + +static const float F44_3 [16 + 32] = { /* SNR(w) = 15.382598 dB, SNR = -29.402334 dB */ + (float) 2.89072132015058161445, (float) 2.68932810943698754106, (float) 0.21083359339410251227, (float)-0.98385073324997617515, + (float)-1.11047823227097316719, (float)-2.18954076314139673147, (float)-2.36498032881953056225, (float)-0.95484132880101140785, + (float)-0.23924057925542965158, (float)-0.13865235703915925642, (float) 0.43587843191057992846, (float) 0.65903257226026665927, + (float) 0.24361815372443152787, (float)-0.00235974960154720097, (float) 0.01844166574603346289, (float) 0.01722945988740875099, + + (float) 2.89072132015058161445, (float) 2.68932810943698754106, (float) 0.21083359339410251227, (float)-0.98385073324997617515, + (float)-1.11047823227097316719, (float)-2.18954076314139673147, (float)-2.36498032881953056225, (float)-0.95484132880101140785, + (float)-0.23924057925542965158, (float)-0.13865235703915925642, (float) 0.43587843191057992846, (float) 0.65903257226026665927, + (float) 0.24361815372443152787, (float)-0.00235974960154720097, (float) 0.01844166574603346289, (float) 0.01722945988740875099, + + (float) 2.89072132015058161445, (float) 2.68932810943698754106, (float) 0.21083359339410251227, (float)-0.98385073324997617515, + (float)-1.11047823227097316719, (float)-2.18954076314139673147, (float)-2.36498032881953056225, (float)-0.95484132880101140785, + (float)-0.23924057925542965158, (float)-0.13865235703915925642, (float) 0.43587843191057992846, (float) 0.65903257226026665927, + (float) 0.24361815372443152787, (float)-0.00235974960154720097, (float) 0.01844166574603346289, (float) 0.01722945988740875099 +}; + + +static double scalar16_(const float* x, const float* y) +{ + return + x[ 0]*y[ 0] + x[ 1]*y[ 1] + x[ 2]*y[ 2] + x[ 3]*y[ 3] + + x[ 4]*y[ 4] + x[ 5]*y[ 5] + x[ 6]*y[ 6] + x[ 7]*y[ 7] + + x[ 8]*y[ 8] + x[ 9]*y[ 9] + x[10]*y[10] + x[11]*y[11] + + x[12]*y[12] + x[13]*y[13] + x[14]*y[14] + x[15]*y[15]; +} + + +void FLAC__replaygain_synthesis__init_dither_context(DitherContext *d, int bits, int shapingtype) +{ + static uint8_t default_dither [] = { 92, 92, 88, 84, 81, 78, 74, 67, 0, 0 }; + static const float* F [] = { F44_0, F44_1, F44_2, F44_3 }; + + int indx; + + if (shapingtype < 0) shapingtype = 0; + if (shapingtype > 3) shapingtype = 3; + d->ShapingType = (NoiseShaping)shapingtype; + indx = bits - 11 - shapingtype; + if (indx < 0) indx = 0; + if (indx > 9) indx = 9; + + memset ( d->ErrorHistory , 0, sizeof (d->ErrorHistory ) ); + memset ( d->DitherHistory, 0, sizeof (d->DitherHistory) ); + + d->FilterCoeff = F [shapingtype]; + d->Mask = ((FLAC__uint64)-1) << (32 - bits); + d->Add = 0.5 * ((1L << (32 - bits)) - 1); + d->Dither = 0.01f*default_dither[indx] / (((FLAC__int64)1) << bits); + d->LastHistoryIndex = 0; +} + +static inline int64_t +ROUND64 (DitherContext *d, double x) +{ + union { + double d; + int64_t i; + } doubletmp; + + doubletmp.d = x + d->Add + (int64_t)FLAC__I64L(0x001FFFFD80000000); + + return doubletmp.i - (int64_t)FLAC__I64L(0x433FFFFD80000000); +} + +/* + * the following is based on parts of wavegain.c + */ + +static int64_t dither_output_(DitherContext *d, FLAC__bool do_dithering, int shapingtype, int i, double Sum, int k) +{ + double Sum2; + int64_t val; + + if(do_dithering) { + if(shapingtype == 0) { + double tmp = random_equi_(d->Dither); + Sum2 = tmp - d->LastRandomNumber [k]; + d->LastRandomNumber [k] = (int)tmp; + Sum2 = Sum += Sum2; + val = ROUND64(d, Sum2) & d->Mask; + } + else { + Sum2 = random_triangular_(d->Dither) - scalar16_(d->DitherHistory[k], d->FilterCoeff + i); + Sum += d->DitherHistory [k] [(-1-i)&15] = (float)Sum2; + Sum2 = Sum + scalar16_(d->ErrorHistory [k], d->FilterCoeff + i); + val = ROUND64(d, Sum2) & d->Mask; + d->ErrorHistory [k] [(-1-i)&15] = (float)(Sum - val); + } + return val; + } + + return ROUND64(d, Sum); +} + +#if 0 + float peak = 0.f, + new_peak, + factor_clip + double scale, + dB; + + ... + + peak is in the range -32768.0 .. 32767.0 + + /* calculate factors for ReplayGain and ClippingPrevention */ + *track_gain = GetTitleGain() + settings->man_gain; + scale = (float) pow(10., *track_gain * 0.05); + if(settings->clip_prev) { + factor_clip = (float) (32767./( peak + 1)); + if(scale < factor_clip) + factor_clip = 1.f; + else + factor_clip /= scale; + scale *= factor_clip; + } + new_peak = (float) peak * scale; + + dB = 20. * log10(scale); + *track_gain = (float) dB; + + const double scale = pow(10., (double)gain * 0.05); +#endif + + +size_t FLAC__replaygain_synthesis__apply_gain(FLAC__byte *data_out, FLAC__bool little_endian_data_out, FLAC__bool uint32_t_data_out, const FLAC__int32 * const input[], uint32_t wide_samples, uint32_t channels, const uint32_t source_bps, const uint32_t target_bps, const double scale, const FLAC__bool hard_limit, FLAC__bool do_dithering, DitherContext *dither_context) +{ + static const FLAC__int64 hard_clip_factors_[33] = { + 0, /* 0 bits-per-sample (not supported) */ + 0, /* 1 bits-per-sample (not supported) */ + 0, /* 2 bits-per-sample (not supported) */ + 0, /* 3 bits-per-sample (not supported) */ + -8, /* 4 bits-per-sample */ + -16, /* 5 bits-per-sample */ + -32, /* 6 bits-per-sample */ + -64, /* 7 bits-per-sample */ + -128, /* 8 bits-per-sample */ + -256, /* 9 bits-per-sample */ + -512, /* 10 bits-per-sample */ + -1024, /* 11 bits-per-sample */ + -2048, /* 12 bits-per-sample */ + -4096, /* 13 bits-per-sample */ + -8192, /* 14 bits-per-sample */ + -16384, /* 15 bits-per-sample */ + -32768, /* 16 bits-per-sample */ + -65536, /* 17 bits-per-sample */ + -131072, /* 18 bits-per-sample */ + -262144, /* 19 bits-per-sample */ + -524288, /* 20 bits-per-sample */ + -1048576, /* 21 bits-per-sample */ + -2097152, /* 22 bits-per-sample */ + -4194304, /* 23 bits-per-sample */ + -8388608, /* 24 bits-per-sample */ + -16777216, /* 25 bits-per-sample */ + -33554432, /* 26 bits-per-sample */ + -67108864, /* 27 bits-per-sample */ + -134217728, /* 28 bits-per-sample */ + -268435456, /* 29 bits-per-sample */ + -536870912, /* 30 bits-per-sample */ + -1073741824, /* 31 bits-per-sample */ + (FLAC__int64)(-1073741824) * 2 /* 32 bits-per-sample */ + }; + const FLAC__int32 conv_shift = 32 - target_bps; + const FLAC__int64 hard_clip_factor = hard_clip_factors_[target_bps]; + /* + * The integer input coming in has a varying range based on the + * source_bps. We want to normalize it to [-1.0, 1.0) so instead + * of doing two multiplies on each sample, we just multiple + * 'scale' by 1/(2^(source_bps-1)) + */ + const double multi_scale = scale / (double)(1u << (source_bps-1)); + + FLAC__byte * const start = data_out; + uint32_t i, channel; + const FLAC__int32 *input_; + double sample; + const uint32_t bytes_per_sample = target_bps / 8; + const uint32_t last_history_index = dither_context->LastHistoryIndex; + NoiseShaping noise_shaping = dither_context->ShapingType; + FLAC__int64 val64; + FLAC__int32 val32; + FLAC__int32 uval32; + const FLAC__uint32 twiggle = 1u << (target_bps - 1); + + FLAC__ASSERT(channels > 0 && channels <= FLAC_SHARE__MAX_SUPPORTED_CHANNELS); + FLAC__ASSERT(source_bps >= 4); + FLAC__ASSERT(target_bps >= 4); + FLAC__ASSERT(source_bps <= 32); + FLAC__ASSERT(target_bps < 32); + FLAC__ASSERT((target_bps & 7) == 0); + + for(channel = 0; channel < channels; channel++) { + const uint32_t incr = bytes_per_sample * channels; + data_out = start + bytes_per_sample * channel; + input_ = input[channel]; + for(i = 0; i < wide_samples; i++, data_out += incr) { + sample = (double)input_[i] * multi_scale; + + if(hard_limit) { + /* hard 6dB limiting */ + if(sample < -0.5) + sample = tanh((sample + 0.5) / (1-0.5)) * (1-0.5) - 0.5; + else if(sample > 0.5) + sample = tanh((sample - 0.5) / (1-0.5)) * (1-0.5) + 0.5; + } + sample *= 2147483647.; + + val64 = dither_output_(dither_context, do_dithering, noise_shaping, (i + last_history_index) % 32, sample, channel) >> conv_shift; + + val32 = (FLAC__int32)val64; + if(val64 >= -hard_clip_factor) + val32 = (FLAC__int32)(-(hard_clip_factor+1)); + else if(val64 < hard_clip_factor) + val32 = (FLAC__int32)hard_clip_factor; + + uval32 = (FLAC__uint32)val32; + if (uint32_t_data_out) + uval32 ^= twiggle; + + if (little_endian_data_out) { + switch(target_bps) { + case 24: + data_out[2] = (FLAC__byte)(uval32 >> 16); + /* fall through */ + case 16: + data_out[1] = (FLAC__byte)(uval32 >> 8); + /* fall through */ + case 8: + data_out[0] = (FLAC__byte)uval32; + break; + } + } + else { + switch(target_bps) { + case 24: + data_out[0] = (FLAC__byte)(uval32 >> 16); + data_out[1] = (FLAC__byte)(uval32 >> 8); + data_out[2] = (FLAC__byte)uval32; + break; + case 16: + data_out[0] = (FLAC__byte)(uval32 >> 8); + data_out[1] = (FLAC__byte)uval32; + break; + case 8: + data_out[0] = (FLAC__byte)uval32; + break; + } + } + } + } + dither_context->LastHistoryIndex = (last_history_index + wide_samples) % 32; + + return wide_samples * channels * (target_bps/8); +} diff --git a/src/share/utf8/CMakeLists.txt b/src/share/utf8/CMakeLists.txt new file mode 100644 index 0000000..389b09e --- /dev/null +++ b/src/share/utf8/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.12) + +add_library(utf8 STATIC + charset.c + iconvert.c + utf8.c) + +target_link_libraries(utf8 PUBLIC grabbag $<TARGET_NAME_IF_EXISTS:Iconv::Iconv>) diff --git a/src/share/utf8/charmaps.h b/src/share/utf8/charmaps.h new file mode 100644 index 0000000..16d049a --- /dev/null +++ b/src/share/utf8/charmaps.h @@ -0,0 +1,57 @@ + +/* + * If you need to generate more maps, use makemap.c on a system + * with a decent iconv. + */ + +static const uint16_t mapping_iso_8859_2[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x02d8, 0x0141, 0x00a4, 0x013d, 0x015a, 0x00a7, + 0x00a8, 0x0160, 0x015e, 0x0164, 0x0179, 0x00ad, 0x017d, 0x017b, + 0x00b0, 0x0105, 0x02db, 0x0142, 0x00b4, 0x013e, 0x015b, 0x02c7, + 0x00b8, 0x0161, 0x015f, 0x0165, 0x017a, 0x02dd, 0x017e, 0x017c, + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, + 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, + 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9 +}; + +static struct { + const char *name; + const uint16_t *map; + struct charset *charset; +} maps[] = { + { "ISO-8859-2", mapping_iso_8859_2, 0 }, + { 0, 0, 0 } +}; + +static const struct { + const char *bad; + const char *good; +} names[] = { + { "ANSI_X3.4-1968", "us-ascii" }, + { 0, 0 } +}; diff --git a/src/share/utf8/charset.c b/src/share/utf8/charset.c new file mode 100644 index 0000000..5c5693d --- /dev/null +++ b/src/share/utf8/charset.c @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2001 Edmund Grimley Evans <edmundo@rano.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * See the corresponding header file for a description of the functions + * that this file provides. + * + * This was first written for Ogg Vorbis but could be of general use. + * + * The only deliberate assumption about data sizes is that a short has + * at least 16 bits, but this code has only been tested on systems with + * 8-bit char, 16-bit short and 32-bit int. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if !defined _WIN32 && !defined HAVE_ICONV /* should be && defined USE_CHARSET_CONVERT */ + +#include <stdlib.h> + +#include "share/alloc.h" +#include "charset.h" + +#include "charmaps.h" + +/* + * This is like the standard strcasecmp, but it does not depend + * on the locale. Locale-dependent functions can be dangerous: + * we once had a bug involving strcasecmp("iso", "ISO") in a + * Turkish locale! + * + * (I'm not really sure what the official standard says + * about the sign of strcasecmp("Z", "["), but usually + * we're only interested in whether it's zero.) + */ + +static int ascii_strcasecmp(const char *s1, const char *s2) +{ + char c1, c2; + + for (;; s1++, s2++) { + if (!*s1 || !*s2) + break; + if (*s1 == *s2) + continue; + c1 = *s1; + if ('a' <= c1 && c1 <= 'z') + c1 += 'A' - 'a'; + c2 = *s2; + if ('a' <= c2 && c2 <= 'z') + c2 += 'A' - 'a'; + if (c1 != c2) + break; + } + return (uint8_t)*s1 - (uint8_t)*s2; +} + +/* + * UTF-8 equivalents of the C library's wctomb() and mbtowc(). + */ + +int utf8_mbtowc(int *pwc, const char *s, size_t n) +{ + uint8_t c; + int wc, i, k; + + if (!n || !s) + return 0; + + c = *s; + if (c < 0x80) { + if (pwc) + *pwc = c; + return c ? 1 : 0; + } + else if (c < 0xc2) + return -1; + else if (c < 0xe0) { + if (n >= 2 && (s[1] & 0xc0) == 0x80) { + if (pwc) + *pwc = ((c & 0x1f) << 6) | (s[1] & 0x3f); + return 2; + } + else + return -1; + } + else if (c < 0xf0) + k = 3; + else if (c < 0xf8) + k = 4; + else if (c < 0xfc) + k = 5; + else if (c < 0xfe) + k = 6; + else + return -1; + + if (n < (size_t)k) + return -1; + wc = *s++ & ((1 << (7 - k)) - 1); + for (i = 1; i < k; i++) { + if ((*s & 0xc0) != 0x80) + return -1; + wc = (wc << 6) | (*s++ & 0x3f); + } + if (wc < (1 << (5 * k - 4))) + return -1; + if (pwc) + *pwc = wc; + return k; +} + +int utf8_wctomb(char *s, int wc1) +{ + uint32_t wc = wc1; + + if (!s) + return 0; + if (wc < (1u << 7)) { + *s++ = wc; + return 1; + } + else if (wc < (1u << 11)) { + *s++ = 0xc0 | (wc >> 6); + *s++ = 0x80 | (wc & 0x3f); + return 2; + } + else if (wc < (1u << 16)) { + *s++ = 0xe0 | (wc >> 12); + *s++ = 0x80 | ((wc >> 6) & 0x3f); + *s++ = 0x80 | (wc & 0x3f); + return 3; + } + else if (wc < (1u << 21)) { + *s++ = 0xf0 | (wc >> 18); + *s++ = 0x80 | ((wc >> 12) & 0x3f); + *s++ = 0x80 | ((wc >> 6) & 0x3f); + *s++ = 0x80 | (wc & 0x3f); + return 4; + } + else if (wc < (1u << 26)) { + *s++ = 0xf8 | (wc >> 24); + *s++ = 0x80 | ((wc >> 18) & 0x3f); + *s++ = 0x80 | ((wc >> 12) & 0x3f); + *s++ = 0x80 | ((wc >> 6) & 0x3f); + *s++ = 0x80 | (wc & 0x3f); + return 5; + } + else if (wc < (1u << 31)) { + *s++ = 0xfc | (wc >> 30); + *s++ = 0x80 | ((wc >> 24) & 0x3f); + *s++ = 0x80 | ((wc >> 18) & 0x3f); + *s++ = 0x80 | ((wc >> 12) & 0x3f); + *s++ = 0x80 | ((wc >> 6) & 0x3f); + *s++ = 0x80 | (wc & 0x3f); + return 6; + } + else + return -1; +} + +/* + * The charset "object" and methods. + */ + +struct charset { + int max; + int (*mbtowc)(void *table, int *pwc, const char *s, size_t n); + int (*wctomb)(void *table, char *s, int wc); + void *map; +}; + +int charset_mbtowc(struct charset *charset, int *pwc, const char *s, size_t n) +{ + return (*charset->mbtowc)(charset->map, pwc, s, n); +} + +int charset_wctomb(struct charset *charset, char *s, int wc) +{ + return (*charset->wctomb)(charset->map, s, wc); +} + +int charset_max(struct charset *charset) +{ + return charset->max; +} + +/* + * Implementation of UTF-8. + */ + +static int mbtowc_utf8(void *map, int *pwc, const char *s, size_t n) +{ + (void)map; + return utf8_mbtowc(pwc, s, n); +} + +static int wctomb_utf8(void *map, char *s, int wc) +{ + (void)map; + return utf8_wctomb(s, wc); +} + +/* + * Implementation of US-ASCII. + * Probably on most architectures this compiles to less than 256 bytes + * of code, so we can save space by not having a table for this one. + */ + +static int mbtowc_ascii(void *map, int *pwc, const char *s, size_t n) +{ + int wc; + + (void)map; + if (!n || !s) + return 0; + wc = (uint8_t)*s; + if (wc & ~0x7f) + return -1; + if (pwc) + *pwc = wc; + return wc ? 1 : 0; +} + +static int wctomb_ascii(void *map, char *s, int wc) +{ + (void)map; + if (!s) + return 0; + if (wc & ~0x7f) + return -1; + *s = wc; + return 1; +} + +/* + * Implementation of ISO-8859-1. + * Probably on most architectures this compiles to less than 256 bytes + * of code, so we can save space by not having a table for this one. + */ + +static int mbtowc_iso1(void *map, int *pwc, const char *s, size_t n) +{ + int wc; + + (void)map; + if (!n || !s) + return 0; + wc = (uint8_t)*s; + if (wc & ~0xff) + return -1; + if (pwc) + *pwc = wc; + return wc ? 1 : 0; +} + +static int wctomb_iso1(void *map, char *s, int wc) +{ + (void)map; + if (!s) + return 0; + if (wc & ~0xff) + return -1; + *s = wc; + return 1; +} + +/* + * Implementation of any 8-bit charset. + */ + +struct map { + const uint16_t *from; + struct inverse_map *to; +}; + +static int mbtowc_8bit(void *map1, int *pwc, const char *s, size_t n) +{ + struct map *map = map1; + uint16_t wc; + + if (!n || !s) + return 0; + wc = map->from[(uint8_t)*s]; + if (wc == 0xffff) + return -1; + if (pwc) + *pwc = (int)wc; + return wc ? 1 : 0; +} + +/* + * For the inverse map we use a hash table, which has the advantages + * of small constant memory requirement and simple memory allocation, + * but the disadvantage of slow conversion in the worst case. + * If you need real-time performance while letting a potentially + * malicious user define their own map, then the method used in + * linux/drivers/char/consolemap.c would be more appropriate. + */ + +struct inverse_map { + uint8_t first[256]; + uint8_t next[256]; +}; + +/* + * The simple hash is good enough for this application. + * Use the alternative trivial hashes for testing. + */ +#define HASH(i) ((i) & 0xff) +/* #define HASH(i) 0 */ +/* #define HASH(i) 99 */ + +static struct inverse_map *make_inverse_map(const uint16_t *from) +{ + struct inverse_map *to; + char used[256]; + int i, j, k; + + to = malloc(sizeof(struct inverse_map)); + if (!to) + return 0; + for (i = 0; i < 256; i++) + to->first[i] = to->next[i] = used[i] = 0; + for (i = 255; i >= 0; i--) + if (from[i] != 0xffff) { + k = HASH(from[i]); + to->next[i] = to->first[k]; + to->first[k] = i; + used[k] = 1; + } + + /* Point the empty buckets at an empty list. */ + for (i = 0; i < 256; i++) + if (!to->next[i]) + break; + if (i < 256) + for (j = 0; j < 256; j++) + if (!used[j]) + to->first[j] = i; + + return to; +} + +static int wctomb_8bit(void *map1, char *s, int wc1) +{ + struct map *map = map1; + uint16_t wc = wc1; + int i; + + if (!s) + return 0; + + if (wc1 & ~0xffff) + return -1; + + if (1) /* Change 1 to 0 to test the case where malloc fails. */ + if (!map->to) + map->to = make_inverse_map(map->from); + + if (map->to) { + /* Use the inverse map. */ + i = map->to->first[HASH(wc)]; + for (;;) { + if (map->from[i] == wc) { + *s = i; + return 1; + } + if (!(i = map->to->next[i])) + break; + } + } + else { + /* We don't have an inverse map, so do a linear search. */ + for (i = 0; i < 256; i++) + if (map->from[i] == wc) { + *s = i; + return 1; + } + } + + return -1; +} + +/* + * The "constructor" charset_find(). + */ + +struct charset charset_utf8 = { + 6, + &mbtowc_utf8, + &wctomb_utf8, + 0 +}; + +struct charset charset_iso1 = { + 1, + &mbtowc_iso1, + &wctomb_iso1, + 0 +}; + +struct charset charset_ascii = { + 1, + &mbtowc_ascii, + &wctomb_ascii, + 0 +}; + +struct charset *charset_find(const char *code) +{ + int i; + + /* Find good (MIME) name. */ + for (i = 0; names[i].bad; i++) + if (!ascii_strcasecmp(code, names[i].bad)) { + code = names[i].good; + break; + } + + /* Recognise some charsets for which we avoid using a table. */ + if (!ascii_strcasecmp(code, "UTF-8")) + return &charset_utf8; + if (!ascii_strcasecmp(code, "US-ASCII")) + return &charset_ascii; + if (!ascii_strcasecmp(code, "ISO-8859-1")) + return &charset_iso1; + + /* Look for a mapping for a simple 8-bit encoding. */ + for (i = 0; maps[i].name; i++) + if (!ascii_strcasecmp(code, maps[i].name)) { + if (!maps[i].charset) { + maps[i].charset = malloc(sizeof(struct charset)); + if (maps[i].charset) { + struct map *map = malloc(sizeof(struct map)); + if (!map) { + free(maps[i].charset); + maps[i].charset = 0; + } + else { + maps[i].charset->max = 1; + maps[i].charset->mbtowc = &mbtowc_8bit; + maps[i].charset->wctomb = &wctomb_8bit; + maps[i].charset->map = map; + map->from = maps[i].map; + map->to = 0; /* inverse mapping is created when required */ + } + } + } + return maps[i].charset; + } + + return 0; +} + +/* + * Function to convert a buffer from one encoding to another. + * Invalid bytes are replaced by '#', and characters that are + * not available in the target encoding are replaced by '?'. + * Each of TO and TOLEN may be zero, if the result is not needed. + * The output buffer is null-terminated, so it is all right to + * use charset_convert(fromcode, tocode, s, strlen(s), &t, 0). + */ + +int charset_convert(const char *fromcode, const char *tocode, + const char *from, size_t fromlen, + char **to, size_t *tolen) +{ + int ret = 0; + struct charset *charset1, *charset2; + char *tobuf, *p; + int i, j, wc; + + charset1 = charset_find(fromcode); + charset2 = charset_find(tocode); + if (!charset1 || !charset2 ) + return -1; + + tobuf = safe_malloc_mul2add_(fromlen, /*times*/charset2->max, /*+*/1); + if (!tobuf) + return -2; + + for (p = tobuf; fromlen; from += i, fromlen -= i, p += j) { + i = charset_mbtowc(charset1, &wc, from, fromlen); + if (!i) + i = 1; + else if (i == -1) { + i = 1; + wc = '#'; + ret = 2; + } + j = charset_wctomb(charset2, p, wc); + if (j == -1) { + if (!ret) + ret = 1; + j = charset_wctomb(charset2, p, '?'); + if (j == -1) + j = 0; + } + } + + if (tolen) + *tolen = p - tobuf; + *p++ = '\0'; + if (to) { + char *tobuf_saved = tobuf; + *to = realloc(tobuf, p - tobuf); + if (*to == NULL) + *to = tobuf_saved; + } + else + free(tobuf); + + return ret; +} + +#endif /* USE_CHARSET_ICONV */ diff --git a/src/share/utf8/charset.h b/src/share/utf8/charset.h new file mode 100644 index 0000000..ea8e31e --- /dev/null +++ b/src/share/utf8/charset.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2001 Edmund Grimley Evans <edmundo@rano.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> + +/* + * These functions are like the C library's mbtowc() and wctomb(), + * but instead of depending on the locale they always work in UTF-8, + * and they use int instead of wchar_t. + */ + +int utf8_mbtowc(int *pwc, const char *s, size_t n); +int utf8_wctomb(char *s, int wc); + +/* + * This is an object-oriented version of mbtowc() and wctomb(). + * The caller first uses charset_find() to get a pointer to struct + * charset, then uses the mbtowc() and wctomb() methods on it. + * The function charset_max() gives the maximum length of a + * multibyte character in that encoding. + * This API is only appropriate for stateless encodings like UTF-8 + * or ISO-8859-3, but I have no intention of implementing anything + * other than UTF-8 and 8-bit encodings. + * + * MINOR BUG: If there is no memory charset_find() may return 0 and + * there is no way to distinguish this case from an unknown encoding. + */ + +struct charset; + +struct charset *charset_find(const char *code); + +int charset_mbtowc(struct charset *charset, int *pwc, const char *s, size_t n); +int charset_wctomb(struct charset *charset, char *s, int wc); +int charset_max(struct charset *charset); + +/* + * Function to convert a buffer from one encoding to another. + * Invalid bytes are replaced by '#', and characters that are + * not available in the target encoding are replaced by '?'. + * Each of TO and TOLEN may be zero if the result is not wanted. + * The input or output may contain null bytes, but the output + * buffer is also null-terminated, so it is all right to + * use charset_convert(fromcode, tocode, s, strlen(s), &t, 0). + * + * Return value: + * + * -2 : memory allocation failed + * -1 : unknown encoding + * 0 : data was converted exactly + * 1 : valid data was converted approximately (using '?') + * 2 : input was invalid (but still converted, using '#') + */ + +int charset_convert(const char *fromcode, const char *tocode, + const char *from, size_t fromlen, + char **to, size_t *tolen); diff --git a/src/share/utf8/charset_test.c b/src/share/utf8/charset_test.c new file mode 100644 index 0000000..6761100 --- /dev/null +++ b/src/share/utf8/charset_test.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2001 Edmund Grimley Evans <edmundo@rano.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <assert.h> +#include <string.h> + +#include "charset.h" + +void test_any(struct charset *charset) +{ + int wc; + char s[2]; + + assert(charset); + + /* Decoder */ + + assert(charset_mbtowc(charset, 0, 0, 0) == 0); + assert(charset_mbtowc(charset, 0, 0, 1) == 0); + assert(charset_mbtowc(charset, 0, (char *)(-1), 0) == 0); + + assert(charset_mbtowc(charset, 0, "a", 0) == 0); + assert(charset_mbtowc(charset, 0, "", 1) == 0); + assert(charset_mbtowc(charset, 0, "b", 1) == 1); + assert(charset_mbtowc(charset, 0, "", 2) == 0); + assert(charset_mbtowc(charset, 0, "c", 2) == 1); + + wc = 'x'; + assert(charset_mbtowc(charset, &wc, "a", 0) == 0 && wc == 'x'); + assert(charset_mbtowc(charset, &wc, "", 1) == 0 && wc == 0); + assert(charset_mbtowc(charset, &wc, "b", 1) == 1 && wc == 'b'); + assert(charset_mbtowc(charset, &wc, "", 2) == 0 && wc == 0); + assert(charset_mbtowc(charset, &wc, "c", 2) == 1 && wc == 'c'); + + /* Encoder */ + + assert(charset_wctomb(charset, 0, 0) == 0); + + s[0] = s[1] = '.'; + assert(charset_wctomb(charset, s, 0) == 1 && + s[0] == '\0' && s[1] == '.'); + assert(charset_wctomb(charset, s, 'x') == 1 && + s[0] == 'x' && s[1] == '.'); +} + +void test_utf8() +{ + struct charset *charset; + int wc; + char s[8]; + + charset = charset_find("UTF-8"); + test_any(charset); + + /* Decoder */ + wc = 0; + assert(charset_mbtowc(charset, &wc, "\177", 1) == 1 && wc == 127); + assert(charset_mbtowc(charset, &wc, "\200", 2) == -1); + assert(charset_mbtowc(charset, &wc, "\301\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\302\200", 1) == -1); + assert(charset_mbtowc(charset, &wc, "\302\200", 2) == 2 && wc == 128); + assert(charset_mbtowc(charset, &wc, "\302\200", 3) == 2 && wc == 128); + assert(charset_mbtowc(charset, &wc, "\340\237\200", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\340\240\200", 9) == 3 && + wc == 1 << 11); + assert(charset_mbtowc(charset, &wc, "\360\217\277\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\360\220\200\200", 9) == 4 && + wc == 1 << 16); + assert(charset_mbtowc(charset, &wc, "\370\207\277\277\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\370\210\200\200\200", 9) == 5 && + wc == 1 << 21); + assert(charset_mbtowc(charset, &wc, "\374\203\277\277\277\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\374\204\200\200\200\200", 9) == 6 && + wc == 1 << 26); + assert(charset_mbtowc(charset, &wc, "\375\277\277\277\277\277", 9) == 6 && + wc == 0x7fffffff); + + assert(charset_mbtowc(charset, &wc, "\302\000", 2) == -1); + assert(charset_mbtowc(charset, &wc, "\302\300", 2) == -1); + assert(charset_mbtowc(charset, &wc, "\340\040\200", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\340\340\200", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\340\240\000", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\340\240\300", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\360\020\200\200", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\360\320\200\200", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\360\220\000\200", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\360\220\300\200", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\360\220\200\000", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\360\220\200\300", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\375\077\277\277\277\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\375\377\277\277\277\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\375\277\077\277\277\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\375\277\377\277\277\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\375\277\277\277\077\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\375\277\277\277\377\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\375\277\277\277\277\077", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\375\277\277\277\277\377", 9) == -1); + + assert(charset_mbtowc(charset, &wc, "\376\277\277\277\277\277", 9) == -1); + assert(charset_mbtowc(charset, &wc, "\377\277\277\277\277\277", 9) == -1); + + /* Encoder */ + safe_strncpy(s, ".......", sizeof(s)); + assert(charset_wctomb(charset, s, 1u << 31) == -1 && + !strcmp(s, ".......")); + assert(charset_wctomb(charset, s, 127) == 1 && + !strcmp(s, "\177......")); + assert(charset_wctomb(charset, s, 128) == 2 && + !strcmp(s, "\302\200.....")); + assert(charset_wctomb(charset, s, 0x7ff) == 2 && + !strcmp(s, "\337\277.....")); + assert(charset_wctomb(charset, s, 0x800) == 3 && + !strcmp(s, "\340\240\200....")); + assert(charset_wctomb(charset, s, 0xffff) == 3 && + !strcmp(s, "\357\277\277....")); + assert(charset_wctomb(charset, s, 0x10000) == 4 && + !strcmp(s, "\360\220\200\200...")); + assert(charset_wctomb(charset, s, 0x1fffff) == 4 && + !strcmp(s, "\367\277\277\277...")); + assert(charset_wctomb(charset, s, 0x200000) == 5 && + !strcmp(s, "\370\210\200\200\200..")); + assert(charset_wctomb(charset, s, 0x3ffffff) == 5 && + !strcmp(s, "\373\277\277\277\277..")); + assert(charset_wctomb(charset, s, 0x4000000) == 6 && + !strcmp(s, "\374\204\200\200\200\200.")); + assert(charset_wctomb(charset, s, 0x7fffffff) == 6 && + !strcmp(s, "\375\277\277\277\277\277.")); +} + +void test_ascii() +{ + struct charset *charset; + int wc; + char s[3]; + + charset = charset_find("us-ascii"); + test_any(charset); + + /* Decoder */ + wc = 0; + assert(charset_mbtowc(charset, &wc, "\177", 2) == 1 && wc == 127); + assert(charset_mbtowc(charset, &wc, "\200", 2) == -1); + + /* Encoder */ + safe_strncpy(s, "..", sizeof(s)); + assert(charset_wctomb(charset, s, 256) == -1 && !strcmp(s, "..")); + assert(charset_wctomb(charset, s, 255) == -1); + assert(charset_wctomb(charset, s, 128) == -1); + assert(charset_wctomb(charset, s, 127) == 1 && !strcmp(s, "\177.")); +} + +void test_iso1() +{ + struct charset *charset; + int wc; + char s[3]; + + charset = charset_find("iso-8859-1"); + test_any(charset); + + /* Decoder */ + wc = 0; + assert(charset_mbtowc(charset, &wc, "\302\200", 9) == 1 && wc == 0xc2); + + /* Encoder */ + safe_strncpy(s, "..", sizeof(s)); + assert(charset_wctomb(charset, s, 256) == -1 && !strcmp(s, "..")); + assert(charset_wctomb(charset, s, 255) == 1 && !strcmp(s, "\377.")); + assert(charset_wctomb(charset, s, 128) == 1 && !strcmp(s, "\200.")); +} + +void test_iso2() +{ + struct charset *charset; + int wc; + char s[3]; + + charset = charset_find("iso-8859-2"); + test_any(charset); + + /* Decoder */ + wc = 0; + assert(charset_mbtowc(charset, &wc, "\302\200", 9) == 1 && wc == 0xc2); + assert(charset_mbtowc(charset, &wc, "\377", 2) == 1 && wc == 0x2d9); + + /* Encoder */ + safe_strncpy(s, "..", sizeof(s)); + assert(charset_wctomb(charset, s, 256) == -1 && !strcmp(s, "..")); + assert(charset_wctomb(charset, s, 255) == -1 && !strcmp(s, "..")); + assert(charset_wctomb(charset, s, 258) == 1 && !strcmp(s, "\303.")); + assert(charset_wctomb(charset, s, 128) == 1 && !strcmp(s, "\200.")); +} + +void test_convert() +{ + const char *p; + char *q, *r; + char s[256]; + size_t n, n2; + int i; + + p = "\000x\302\200\375\277\277\277\277\277"; + assert(charset_convert("UTF-8", "UTF-8", p, 10, &q, &n) == 0 && + n == 10 && !strcmp(p, q)); + assert(charset_convert("UTF-8", "UTF-8", "x\301\277y", 4, &q, &n) == 2 && + n == 4 && !strcmp(q, "x##y")); + assert(charset_convert("UTF-8", "UTF-8", "x\301\277y", 4, 0, &n) == 2 && + n == 4); + assert(charset_convert("UTF-8", "UTF-8", "x\301\277y", 4, &q, 0) == 2 && + !strcmp(q, "x##y")); + assert(charset_convert("UTF-8", "iso-8859-1", + "\302\200\304\200x", 5, &q, &n) == 1 && + n == 3 && !strcmp(q, "\200?x")); + assert(charset_convert("iso-8859-1", "UTF-8", + "\000\200\377", 3, &q, &n) == 0 && + n == 5 && !memcmp(q, "\000\302\200\303\277", 5)); + assert(charset_convert("iso-8859-1", "iso-8859-1", + "\000\200\377", 3, &q, &n) == 0 && + n == 3 && !memcmp(q, "\000\200\377", 3)); + + assert(charset_convert("iso-8859-2", "utf-8", "\300", 1, &q, &n) == 0 && + n == 2 && !strcmp(q, "\305\224")); + assert(charset_convert("utf-8", "iso-8859-2", "\305\224", 2, &q, &n) == 0 && + n == 1 && !strcmp(q, "\300")); + + for (i = 0; i < 256; i++) + s[i] = i; + + assert(charset_convert("iso-8859-2", "utf-8", s, 256, &q, &n) == 0); + assert(charset_convert("utf-8", "iso-8859-2", q, n, &r, &n2) == 0); + assert(n2 == 256 && !memcmp(r, s, n2)); +} + +int main() +{ + test_utf8(); + test_ascii(); + test_iso1(); + test_iso2(); + + test_convert(); + + return 0; +} diff --git a/src/share/utf8/iconvert.c b/src/share/utf8/iconvert.c new file mode 100644 index 0000000..9a1e3f6 --- /dev/null +++ b/src/share/utf8/iconvert.c @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2001 Edmund Grimley Evans <edmundo@rano.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if !defined _WIN32 && defined HAVE_ICONV + +#include <assert.h> +#include <errno.h> +#include <iconv.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "iconvert.h" +#include "share/alloc.h" +#include "share/safe_str.h" + +/* + * Convert data from one encoding to another. Return: + * + * -2 : memory allocation failed + * -1 : unknown encoding + * 0 : data was converted exactly + * 1 : data was converted inexactly + * 2 : data was invalid (but still converted) + * + * We convert in two steps, via UTF-8, as this is the only + * reliable way of distinguishing between invalid input + * and valid input which iconv refuses to transliterate. + * We convert from UTF-8 twice, because we have no way of + * knowing whether the conversion was exact if iconv returns + * E2BIG (due to a bug in the specification of iconv). + * An alternative approach is to assume that the output of + * iconv is never more than 4 times as long as the input, + * but I prefer to avoid that assumption if possible. + */ + +int iconvert(const char *fromcode, const char *tocode, + const char *from, size_t fromlen, + char **to, size_t *tolen) +{ + int ret = 0; + iconv_t cd1, cd2; + char *ib; + char *ob; + char *utfbuf = 0, *outbuf, *newbuf; + size_t utflen, outlen, ibl, obl, obp, k; + char tbuf[2048]; + + cd1 = iconv_open("UTF-8", fromcode); + if (cd1 == (iconv_t)(-1)) + return -1; + + cd2 = (iconv_t)(-1); + /* Don't use strcasecmp() as it's locale-dependent. */ + if (!strchr("Uu", tocode[0]) || + !strchr("Tt", tocode[1]) || + !strchr("Ff", tocode[2]) || + tocode[3] != '-' || + tocode[4] != '8' || + tocode[5] != '\0') { + char *tocode1; + int rc; + /* + * Try using this non-standard feature of glibc and libiconv. + * This is deliberately not a config option as people often + * change their iconv library without rebuilding applications. + */ + + rc = asprintf(&tocode1, "%s//TRANSLIT", tocode); + if (rc < 0 || ! tocode1) + goto fail; + + cd2 = iconv_open(tocode1, "UTF-8"); + free(tocode1); + + if (cd2 == (iconv_t)(-1)) + cd2 = iconv_open(tocode, fromcode); + + if (cd2 == (iconv_t)(-1)) { + iconv_close(cd1); + return -1; + } + } + + utflen = 1; /*fromlen * 2 + 1; XXX */ + utfbuf = malloc(utflen); + if (!utfbuf) + goto fail; + + /* Convert to UTF-8 */ + ib = (char *)from; + ibl = fromlen; + ob = utfbuf; + obl = utflen; + for (;;) { + k = iconv(cd1, &ib, &ibl, &ob, &obl); + assert((!k && !ibl) || + (k == (size_t)(-1) && errno == E2BIG && ibl && obl < 6) || + (k == (size_t)(-1) && + (errno == EILSEQ || errno == EINVAL) && ibl)); + if (!ibl) + break; + if (obl < 6) { + /* Enlarge the buffer */ + if(utflen*2 < utflen) /* overflow check */ + goto fail; + utflen *= 2; + obp = ob - utfbuf; /* save position */ + newbuf = realloc(utfbuf, utflen); + if (!newbuf) + goto fail; + ob = newbuf + obp; + obl = utflen - obp; + utfbuf = newbuf; + } + else { + /* Invalid input */ + ib++, ibl--; + *ob++ = '#', obl--; + ret = 2; + iconv(cd1, 0, 0, 0, 0); + } + } + + if (cd2 == (iconv_t)(-1)) { + /* The target encoding was UTF-8 */ + if (tolen) + *tolen = ob - utfbuf; + if (!to) { + free(utfbuf); + iconv_close(cd1); + return ret; + } + newbuf = safe_realloc_nofree_add_2op_(utfbuf, (ob - utfbuf), /*+*/1); + if (!newbuf) + goto fail; + ob = (ob - utfbuf) + newbuf; + *ob = '\0'; + *to = newbuf; + iconv_close(cd1); + return ret; + } + + /* Truncate the buffer to be tidy */ + utflen = ob - utfbuf; + if (utflen == 0) + goto fail; + newbuf = realloc(utfbuf, utflen); + if (!newbuf) + goto fail; + utfbuf = newbuf; + + /* Convert from UTF-8 to discover how long the output is */ + outlen = 0; + ib = utfbuf; + ibl = utflen; + while (ibl) { + ob = tbuf; + obl = sizeof(tbuf); + k = iconv(cd2, &ib, &ibl, &ob, &obl); + assert((k != (size_t)(-1) && !ibl) || + (k == (size_t)(-1) && errno == E2BIG && ibl) || + (k == (size_t)(-1) && errno == EILSEQ && ibl)); + if (ibl && !(k == (size_t)(-1) && errno == E2BIG)) { + /* Replace one character */ + char *tb = "?"; + size_t tbl = 1; + + outlen += ob - tbuf; + ob = tbuf; + obl = sizeof(tbuf); + k = iconv(cd2, &tb, &tbl, &ob, &obl); + assert((!k && !tbl) || + (k == (size_t)(-1) && errno == EILSEQ && tbl)); + for (++ib, --ibl; ibl && (*ib & 0x80); ib++, ibl--) + ; + } + outlen += ob - tbuf; + } + ob = tbuf; + obl = sizeof(tbuf); + k = iconv(cd2, 0, 0, &ob, &obl); + assert(!k); + outlen += ob - tbuf; + + /* Convert from UTF-8 for real */ + outbuf = safe_malloc_add_2op_(outlen, /*+*/1); + if (!outbuf) + goto fail; + ib = utfbuf; + ibl = utflen; + ob = outbuf; + obl = outlen; + while (ibl) { + k = iconv(cd2, &ib, &ibl, &ob, &obl); + assert((k != (size_t)(-1) && !ibl) || + (k == (size_t)(-1) && errno == EILSEQ && ibl)); + if (k && !ret) + ret = 1; + if (ibl && !(k == (size_t)(-1) && errno == E2BIG)) { + /* Replace one character */ + char *tb = "?"; + size_t tbl = 1; + + k = iconv(cd2, &tb, &tbl, &ob, &obl); + assert((!k && !tbl) || + (k == (size_t)(-1) && errno == EILSEQ && tbl)); + for (++ib, --ibl; ibl && (*ib & 0x80); ib++, ibl--) + ; + } + } + k = iconv(cd2, 0, 0, &ob, &obl); + assert(!k); + assert(!obl); + *ob = '\0'; + + free(utfbuf); + iconv_close(cd1); + iconv_close(cd2); + if (tolen) + *tolen = outlen; + if (!to) { + free(outbuf); + return ret; + } + *to = outbuf; + return ret; + + fail: + if(0 != utfbuf) + free(utfbuf); + iconv_close(cd1); + if (cd2 != (iconv_t)(-1)) + iconv_close(cd2); + return -2; +} + +#endif /* HAVE_ICONV */ diff --git a/src/share/utf8/iconvert.h b/src/share/utf8/iconvert.h new file mode 100644 index 0000000..a2d75a2 --- /dev/null +++ b/src/share/utf8/iconvert.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2001 Edmund Grimley Evans <edmundo@rano.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifdef HAVE_ICONV + +/* + * Convert data from one encoding to another. Return: + * + * -2 : memory allocation failed + * -1 : unknown encoding + * 0 : data was converted exactly + * 1 : data was converted inexactly + * 2 : data was invalid (but still converted) + * + * We convert in two steps, via UTF-8, as this is the only + * reliable way of distinguishing between invalid input + * and valid input which iconv refuses to transliterate. + * We convert from UTF-8 twice, because we have no way of + * knowing whether the conversion was exact if iconv returns + * E2BIG (due to a bug in the specification of iconv). + * An alternative approach is to assume that the output of + * iconv is never more than 4 times as long as the input, + * but I prefer to avoid that assumption if possible. + */ + +int iconvert(const char *fromcode, const char *tocode, + const char *from, size_t fromlen, + char **to, size_t *tolen) ; + +#endif /* HAVE_ICONV */ diff --git a/src/share/utf8/makemap.c b/src/share/utf8/makemap.c new file mode 100644 index 0000000..790021c --- /dev/null +++ b/src/share/utf8/makemap.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2001 Edmund Grimley Evans <edmundo@rano.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <errno.h> +#include <iconv.h> +#include <stdio.h> + +int main(int argc, char *argv[]) +{ + iconv_t cd; + const char *ib; + char *ob; + size_t ibl, obl, k; + uint8_t c, buf[4]; + int i, wc; + + if (argc != 2) { + printf("Usage: %s ENCODING\n", argv[0]); + printf("Output a charset map for the 8-bit ENCODING.\n"); + return 1; + } + + cd = iconv_open("UCS-4", argv[1]); + if (cd == (iconv_t)(-1)) { + perror("iconv_open"); + return 1; + } + + for (i = 0; i < 256; i++) { + c = i; + ib = &c; + ibl = 1; + ob = buf; + obl = 4; + k = iconv(cd, &ib, &ibl, &ob, &obl); + if (!k && !ibl && !obl) { + wc = (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]; + if (wc >= 0xffff) { + printf("Dodgy value.\n"); + return 1; + } + } + else if (k == (size_t)(-1) && errno == EILSEQ) + wc = 0xffff; + else { + printf("Non-standard iconv.\n"); + return 1; + } + + if (i % 8 == 0) + printf(" "); + printf("0x%04x", wc); + if (i == 255) + printf("\n"); + else if (i % 8 == 7) + printf(",\n"); + else + printf(", "); + } + + return 0; +} diff --git a/src/share/utf8/utf8.c b/src/share/utf8/utf8.c new file mode 100644 index 0000000..34af187 --- /dev/null +++ b/src/share/utf8/utf8.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2001 Peter Harris <peter.harris@hummingbird.com> + * Copyright (C) 2001 Edmund Grimley Evans <edmundo@rano.org> + * + * Buffer overflow checking added: Josh Coalson, 9/9/2007 + * + * Win32 part rewritten: lvqcl, 2/2/2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Convert a string between UTF-8 and the locale's charset. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#include "share/alloc.h" +#include "share/utf8.h" + +#ifdef _WIN32 + +#include <windows.h> + +int utf8_encode(const char *from, char **to) +{ + wchar_t *unicode = NULL; + char *utf8 = NULL; + int ret = -1; + + do { + int len; + + len = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, from, -1, NULL, 0); + if(len == 0) break; + unicode = (wchar_t*) safe_malloc_mul_2op_((size_t)len, sizeof(wchar_t)); + if(unicode == NULL) break; + len = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, from, -1, unicode, len); + if(len == 0) break; + + len = WideCharToMultiByte(CP_UTF8, 0, unicode, -1, NULL, 0, NULL, NULL); + if(len == 0) break; + utf8 = (char*) safe_malloc_mul_2op_((size_t)len, sizeof(char)); + if(utf8 == NULL) break; + len = WideCharToMultiByte(CP_UTF8, 0, unicode, -1, utf8, len, NULL, NULL); + if(len == 0) break; + + ret = 0; + + } while(0); + + free(unicode); + + if(ret == 0) { + *to = utf8; + } else { + free(utf8); + *to = NULL; + } + + return ret; +} + +int utf8_decode(const char *from, char **to) +{ + wchar_t *unicode = NULL; + char *acp = NULL; + int ret = -1; + + do { + int len; + + len = MultiByteToWideChar(CP_UTF8, 0, from, -1, NULL, 0); + if(len == 0) break; + unicode = (wchar_t*) safe_malloc_mul_2op_((size_t)len, sizeof(wchar_t)); + if(unicode == NULL) break; + len = MultiByteToWideChar(CP_UTF8, 0, from, -1, unicode, len); + if(len == 0) break; + + len = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode, -1, NULL, 0, NULL, NULL); + if(len == 0) break; + acp = (char*) safe_malloc_mul_2op_((size_t)len, sizeof(char)); + if(acp == NULL) break; + len = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode, -1, acp, len, NULL, NULL); + if(len == 0) break; + + ret = 0; + + } while(0); + + free(unicode); + + if(ret == 0) { + *to = acp; + } else { + free(acp); + *to = NULL; + } + + return ret; +} + +#else /* End win32. Rest is for real operating systems */ + + +#ifdef HAVE_LANGINFO_CODESET +#include <langinfo.h> +#endif + +#include <string.h> + +#include "share/safe_str.h" +#include "iconvert.h" +#include "charset.h" + +static const char *current_charset(void) +{ + const char *c = 0; +#ifdef HAVE_LANGINFO_CODESET + c = nl_langinfo(CODESET); +#endif + + if (!c) + c = getenv("CHARSET"); + + return c? c : "US-ASCII"; +} + +static int convert_buffer(const char *fromcode, const char *tocode, + const char *from, size_t fromlen, + char **to, size_t *tolen) +{ + int ret = -1; + +#ifdef HAVE_ICONV + ret = iconvert(fromcode, tocode, from, fromlen, to, tolen); + if (ret != -1) + return ret; +#endif + +#ifndef HAVE_ICONV /* should be ifdef USE_CHARSET_CONVERT */ + ret = charset_convert(fromcode, tocode, from, fromlen, to, tolen); + if (ret != -1) + return ret; +#endif + + return ret; +} + +static int convert_string(const char *fromcode, const char *tocode, + const char *from, char **to, char replace) +{ + int ret; + size_t fromlen; + char *s; + + fromlen = strlen(from); + ret = convert_buffer(fromcode, tocode, from, fromlen, to, 0); + if (ret == -2) + return -1; + if (ret != -1) + return ret; + + s = safe_malloc_add_2op_(fromlen, /*+*/1); + if (!s) + return -1; + snprintf(s, fromlen + 1, "%s", from); + *to = s; + for (; *s; s++) + if (*s & ~0x7f) + *s = replace; + return 3; +} + +int utf8_encode(const char *from, char **to) +{ + return convert_string(current_charset(), "UTF-8", from, to, '#'); +} + +int utf8_decode(const char *from, char **to) +{ + return convert_string("UTF-8", current_charset(), from, to, '?'); +} + +#endif diff --git a/src/share/win_utf8_io/win_utf8_io.c b/src/share/win_utf8_io/win_utf8_io.c new file mode 100644 index 0000000..3ae35b3 --- /dev/null +++ b/src/share/win_utf8_io/win_utf8_io.c @@ -0,0 +1,398 @@ +/* libFLAC - Free Lossless Audio Codec library + * Copyright (C) 2013-2023 Xiph.Org Foundation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Xiph.org Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <io.h> +#include <windows.h> +#include "share/win_utf8_io.h" + +#define UTF8_BUFFER_SIZE 32768 + +/* detect whether it is Windows APP (UWP) or standard Win32 envionment */ +#ifdef WINAPI_FAMILY_PARTITION + #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + #define FLAC_WINDOWS_APP 1 + #else + #define FLAC_WINDOWS_APP 0 + #endif +#else + #define FLAC_WINDOWS_APP 0 +#endif + +static int local_vsnprintf(char *str, size_t size, const char *fmt, va_list va) +{ + int rc; + +#if defined _MSC_VER + if (size == 0) + return 1024; + rc = vsnprintf_s(str, size, _TRUNCATE, fmt, va); + if (rc < 0) + rc = size - 1; +#elif defined __MINGW32__ + rc = __mingw_vsnprintf(str, size, fmt, va); +#else + rc = vsnprintf(str, size, fmt, va); +#endif + + return rc; +} + +/* convert WCHAR stored Unicode string to UTF-8. Caller is responsible for freeing memory */ +static char *utf8_from_wchar(const wchar_t *wstr) +{ + char *utf8str; + int len; + + if (!wstr) + return NULL; + if ((len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL)) == 0) + return NULL; + if ((utf8str = (char *)malloc(len)) == NULL) + return NULL; + if (WideCharToMultiByte(CP_UTF8, 0, wstr, -1, utf8str, len, NULL, NULL) == 0) { + free(utf8str); + utf8str = NULL; + } + + return utf8str; +} + +/* convert UTF-8 back to WCHAR. Caller is responsible for freeing memory */ +static wchar_t *wchar_from_utf8(const char *str) +{ + wchar_t *widestr; + int len; + + if (!str) + return NULL; + if ((len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0)) == 0) + return NULL; + if ((widestr = (wchar_t *)malloc(len*sizeof(wchar_t))) == NULL) + return NULL; + if (MultiByteToWideChar(CP_UTF8, 0, str, -1, widestr, len) == 0) { + free(widestr); + widestr = NULL; + } + + return widestr; +} + +/* retrieve WCHAR commandline, expand wildcards and convert everything to UTF-8 */ +int get_utf8_argv(int *argc, char ***argv) +{ +#if !FLAC_WINDOWS_APP + typedef int (__cdecl *wgetmainargs_t)(int*, wchar_t***, wchar_t***, int, int*); + wgetmainargs_t wgetmainargs; + HMODULE handle; +#endif // !FLAC_WINDOWS_APP + int wargc; + wchar_t **wargv; + wchar_t **wenv; + char **utf8argv; + int ret, i; + +#if FLAC_WINDOWS_APP + wargc = __argc; + wargv = __wargv; + wenv = _wenviron; +#else // !FLAC_WINDOWS_APP + if ((handle = LoadLibraryW(L"msvcrt.dll")) == NULL) return 1; + if ((wgetmainargs = (wgetmainargs_t)GetProcAddress(handle, "__wgetmainargs")) == NULL) { + FreeLibrary(handle); + return 1; + } + i = 0; + /* when the 4th argument is 1, __wgetmainargs expands wildcards but also erroneously converts \\?\c:\path\to\file.flac to \\file.flac */ + if (wgetmainargs(&wargc, &wargv, &wenv, 1, &i) != 0) { + FreeLibrary(handle); + return 1; + } +#endif // !FLAC_WINDOWS_APP + if ((utf8argv = (char **)calloc(wargc, sizeof(char*))) == NULL) { + #if !FLAC_WINDOWS_APP + FreeLibrary(handle); + #endif // !FLAC_WINDOWS_APP + return 1; + } + + ret = 0; + for (i=0; i<wargc; i++) { + if ((utf8argv[i] = utf8_from_wchar(wargv[i])) == NULL) { + ret = 1; + break; + } + } + +#if !FLAC_WINDOWS_APP + FreeLibrary(handle); /* do not free it when wargv or wenv are still in use */ +#endif // !FLAC_WINDOWS_APP + + if (ret == 0) { + *argc = wargc; + *argv = utf8argv; + } else { + for (i=0; i<wargc; i++) + free(utf8argv[i]); + free(utf8argv); + } + + return ret; +} + +/* similar to CreateFileW but accepts UTF-8 encoded lpFileName */ +HANDLE WINAPI CreateFile_utf8(const char *lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) +{ + wchar_t *wname; + HANDLE handle = INVALID_HANDLE_VALUE; + + if ((wname = wchar_from_utf8(lpFileName)) != NULL) { +#if !FLAC_WINDOWS_APP + handle = CreateFileW(wname, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); +#else // FLAC_WINDOWS_APP + CREATEFILE2_EXTENDED_PARAMETERS params; + params.dwSize = sizeof(params); + params.dwFileAttributes = dwFlagsAndAttributes & 0xFFFF; + params.dwFileFlags = dwFlagsAndAttributes & 0xFFF00000; + params.dwSecurityQosFlags = dwFlagsAndAttributes & 0x000F0000; + params.lpSecurityAttributes = lpSecurityAttributes; + params.hTemplateFile = hTemplateFile; + handle = CreateFile2(wname, dwDesiredAccess, dwShareMode, dwCreationDisposition, ¶ms); +#endif // FLAC_WINDOWS_APP + free(wname); + } + + return handle; +} + +/* return number of characters in the UTF-8 string */ +size_t strlen_utf8(const char *str) +{ + size_t len; + len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); /* includes terminating null */ + if (len != 0) + return len-1; + else + return strlen(str); +} + +/* get the console width in characters */ +int win_get_console_width(void) +{ + int width = 80; +#if !FLAC_WINDOWS_APP + CONSOLE_SCREEN_BUFFER_INFO csbi; + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if(hOut != INVALID_HANDLE_VALUE && hOut != NULL) + if (GetConsoleScreenBufferInfo(hOut, &csbi) != 0) + width = csbi.dwSize.X; +#endif // !FLAC_WINDOWS_APP + return width; +} + +/* print functions */ + +#if !FLAC_WINDOWS_APP +static int wprint_console(FILE *stream, const wchar_t *text, size_t len) +{ + DWORD out; + int ret; + + do { + if (stream == stdout) { + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut == INVALID_HANDLE_VALUE || hOut == NULL || GetFileType(hOut) != FILE_TYPE_CHAR) + break; + if (WriteConsoleW(hOut, text, len, &out, NULL) == 0) + return -1; + return out; + } + if (stream == stderr) { + HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE); + if (hErr == INVALID_HANDLE_VALUE || hErr == NULL || GetFileType(hErr) != FILE_TYPE_CHAR) + break; + if (WriteConsoleW(hErr, text, len, &out, NULL) == 0) + return -1; + return out; + } + } while(0); + + ret = fputws(text, stream); + if (ret < 0) + return ret; + return len; +} +#endif // !FLAC_WINDOWS_APP + +int printf_utf8(const char *format, ...) +{ + int ret; + va_list argptr; + va_start(argptr, format); + + ret = vfprintf_utf8(stdout, format, argptr); + + va_end(argptr); + + return ret; +} + +int fprintf_utf8(FILE *stream, const char *format, ...) +{ + int ret; + va_list argptr; + va_start(argptr, format); + + ret = vfprintf_utf8(stream, format, argptr); + + va_end(argptr); + + return ret; +} + +int vfprintf_utf8(FILE *stream, const char *format, va_list argptr) +{ + char *utmp = NULL; + wchar_t *wout = NULL; + int ret = -1; + + do { + if (!(utmp = (char *)malloc(UTF8_BUFFER_SIZE))) break; + if ((ret = local_vsnprintf(utmp, UTF8_BUFFER_SIZE, format, argptr)) <= 0) break; + if (!(wout = wchar_from_utf8(utmp))) { + ret = -1; + break; + } +#if !FLAC_WINDOWS_APP + ret = wprint_console(stream, wout, wcslen(wout)); +#else // FLAC_WINDOWS_APP + OutputDebugStringW(wout); + ret = 0; +#endif // FLAC_WINDOWS_APP + } while(0); + + free(utmp); + free(wout); + + return ret; +} + +/* file functions */ + +FILE* fopen_utf8(const char *filename, const char *mode) +{ + wchar_t *wname = NULL; + wchar_t *wmode = NULL; + FILE *f = NULL; + + do { + if (!(wname = wchar_from_utf8(filename))) break; + if (!(wmode = wchar_from_utf8(mode))) break; + f = _wfopen(wname, wmode); + } while(0); + + free(wname); + free(wmode); + + return f; +} + +int stat64_utf8(const char *path, struct __stat64 *buffer) +{ + wchar_t *wpath; + int ret; + + if (!(wpath = wchar_from_utf8(path))) return -1; + ret = _wstat64(wpath, buffer); + free(wpath); + + return ret; +} + +int chmod_utf8(const char *filename, int pmode) +{ + wchar_t *wname; + int ret; + + if (!(wname = wchar_from_utf8(filename))) return -1; + ret = _wchmod(wname, pmode); + free(wname); + + return ret; +} + +int utime_utf8(const char *filename, struct utimbuf *times) +{ + wchar_t *wname; + struct __utimbuf64 ut; + int ret; + + if (!(wname = wchar_from_utf8(filename))) return -1; + ut.actime = times->actime; + ut.modtime = times->modtime; + ret = _wutime64(wname, &ut); + free(wname); + + return ret; +} + +int unlink_utf8(const char *filename) +{ + wchar_t *wname; + int ret; + + if (!(wname = wchar_from_utf8(filename))) return -1; + ret = _wunlink(wname); + free(wname); + + return ret; +} + +int rename_utf8(const char *oldname, const char *newname) +{ + wchar_t *wold = NULL; + wchar_t *wnew = NULL; + int ret = -1; + + do { + if (!(wold = wchar_from_utf8(oldname))) break; + if (!(wnew = wchar_from_utf8(newname))) break; + ret = _wrename(wold, wnew); + } while(0); + + free(wold); + free(wnew); + + return ret; +} |