pax_global_header00006660000000000000000000000064145263342330014517gustar00rootroot0000000000000052 comment=263ae121219abf90c11a9d54c67bd2e2aae5502c cyanrip-0.9.2/000077500000000000000000000000001452633423300131745ustar00rootroot00000000000000cyanrip-0.9.2/.github/000077500000000000000000000000001452633423300145345ustar00rootroot00000000000000cyanrip-0.9.2/.github/mingw-build.sh000066400000000000000000000113251452633423300173100ustar00rootroot00000000000000#!/bin/bash buildCyanrip() { cyan_prepare build_curl build_neon build_libmusicbrainz build_ffmpeg build_cyanrip } cyan_prepare() { local rootdir="$(pwd)" CYANRIPREPODIR="${GITHUB_WORKSPACE:-$(realpath "$rootdir/..")}" CYANRIPINSTALLDIR="$(realpath "$rootdir/_install")" CYANRIPBUILDDIR="$(realpath "$rootdir/_build")" PKG_CONFIG_PATH=$CYANRIPINSTALLDIR/lib/pkgconfig:$PKG_CONFIG_PATH CPATH="$(cygpath -pm $CYANRIPINSTALLDIR/include:$MINGW_PREFIX/include)" LIBRARY_PATH="$(cygpath -pm $CYANRIPINSTALLDIR/lib:$MINGW_PREFIX/lib)" CPPFLAGS="-D_FORTIFY_SOURCE=0 -D__USE_MINGW_ANSI_STDIO=1" CFLAGS="-mthreads -mtune=generic -O2 -pipe" CXXFLAGS="${CFLAGS}" LDFLAGS="-pipe -static-libgcc -static-libstdc++" export CYANRIPREPODIR CYANRIPBUILDDIR CYANRIPINSTALLDIR PKG_CONFIG_PATH CPATH LIBRARY_PATH CPPFLAGS CFLAGS CXXFLAGS LDFLAGS mkdir -p "$CYANRIPBUILDDIR" "$CYANRIPINSTALLDIR" cyan_do_hide_all_sharedlibs } cyan_do_vcs() { local vcsURL=${1#*::} vcsFolder=$2 local vcsBranch=${vcsURL#*#} ref=origin/HEAD : "${vcsFolder:=$(basename "$vcsURL" .git)}" cd "$CYANRIPBUILDDIR" git clone --depth 1 "$vcsURL" "$vcsFolder" cd "$vcsFolder" } cyan_do_hide_all_sharedlibs() { local files files="$(find /mingw{32,64}/lib /mingw{32/i686,64/x86_64}-w64-mingw32/lib -name "*.dll.a" 2> /dev/null)" local tomove=() for file in $files; do [[ -f ${file%*.dll.a}.a ]] && tomove+=("$file") done printf '%s\0' "${tomove[@]}" | xargs -0ri mv -f '{}' '{}.dyn' } cyan_hide_files() { for opt; do [[ -f $opt ]] && mv -f "$opt" "$opt.bak"; done } cyan_hide_conflicting_libs() { local -a installed mapfile -t installed < <(find "$CYANRIPINSTALLDIR/lib" -maxdepth 1 -name "*.a") cyan_hide_files "${installed[@]//$CYANRIPINSTALLDIR/$MINGW_PREFIX}" } cyan_do_separate_confmakeinstall() { rm -rf ./_build && mkdir _build && cd _build && ../configure --disable-shared --enable-static --prefix="$CYANRIPINSTALLDIR" "$@" && make && make install } cyan_do_cmakeinstall() { PKG_CONFIG=pkg-config cmake -B _build -G Ninja -DBUILD_SHARED_LIBS=off \ -DCMAKE_INSTALL_PREFIX="$CYANRIPINSTALLDIR" -DUNIX=on \ -DCMAKE_FIND_ROOT_PATH="$(cygpath -pm "$CYANRIPINSTALLDIR:$MINGW_PREFIX:$MINGW_PREFIX/$MINGW_CHOST")" \ -DCMAKE_PREFIX_PATH="$(cygpath -pm "$CYANRIPINSTALLDIR:$MINGW_PREFIX:$MINGW_PREFIX/$MINGW_CHOST")" \ -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY \ -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY \ -DCMAKE_BUILD_TYPE=Release "$@" && ninja -C _build && ninja -C _build install } build_curl() { cyan_do_vcs "https://github.com/curl/curl.git" && autoreconf -fi && sed -ri "s;(^SUBDIRS = lib) src (include) scripts;\1 \2;" Makefile.in && # CPPFLAGS+=" -DNGHTTP2_STATICLIB" \ cyan_do_separate_confmakeinstall --with-{winssl,winidn,nghttp2} \ --without-{ssl,gnutls,mbedtls,libssh2,random,ca-bundle,ca-path,librtmp,brotli,debug,libpsl,zstd,nghttp2,ldap,ldaps,ldap-lib} } build_neon() { cyan_do_vcs "https://github.com/notroj/neon.git" && ./autogen.sh && cyan_do_separate_confmakeinstall --disable-{nls,debug,webdav} } build_libmusicbrainz() { cyan_do_vcs "https://github.com/wiiaboo/libmusicbrainz.git" && cyan_do_cmakeinstall } build_ffmpeg() { cyan_do_vcs "https://git.ffmpeg.org/ffmpeg.git" cyan_do_separate_confmakeinstall --pkg-config-flags=--static \ --disable-{programs,devices,filters,decoders,hwaccels,encoders,muxers} \ --disable-{debug,protocols,demuxers,parsers,doc,swscale,postproc,network} \ --disable-{avdevice,autodetect} \ --disable-bsfs --enable-protocol=file,data \ --enable-encoder=flac,tta,aac,wavpack,alac,pcm_s16le,pcm_s32le \ --enable-muxer=flac,tta,ipod,wv,mp3,opus,ogg,wav,pcm_s16le,pcm_s32le,image2,singlejpeg \ --enable-parser=png,mjpeg --enable-decoder=mjpeg,png \ --enable-demuxer={image2,image_jpeg_pipe,image_png_pipe} \ --enable-{bzlib,zlib,lzma,iconv} \ --enable-filter={hdcd,aemphasis,ebur128,anullsink,aresample} \ --enable-lib{mp3lame,vorbis,opus} \ --enable-encoder={libmp3lame,libvorbis,libopus} } build_cyanrip() { cd "$CYANRIPREPODIR" cyan_hide_conflicting_libs PKG_CONFIG=pkg-config \ CFLAGS+=" -DLIBXML_STATIC -DCURL_STATICLIB $(printf ' -I%s' "$(cygpath -m "$CYANRIPINSTALLDIR/include")")" \ LDFLAGS+="$(printf ' -L%s' "$(cygpath -m "$CYANRIPINSTALLDIR/lib")")" \ meson build --default-library=static --buildtype=release --prefix="$CYANRIPINSTALLDIR" --backend=ninja && ninja -C build && strip --strip-all build/src/cyanrip.exe -o cyanrip.exe } cd "$(dirname "$0")" buildCyanrip cyanrip-0.9.2/.github/workflows/000077500000000000000000000000001452633423300165715ustar00rootroot00000000000000cyanrip-0.9.2/.github/workflows/main.yml000066400000000000000000000067051452633423300202500ustar00rootroot00000000000000name: CI on: push: branches: [ master, workflow ] release: types: [ released ] jobs: mingw: runs-on: windows-latest env: MSYSTEM: MINGW64 defaults: run: shell: msys2 {0} steps: - uses: actions/checkout@v2 - uses: msys2/setup-msys2@v2 with: install: git base-devel zip autoconf automake libtool mingw-w64-x86_64-toolchain mingw-w64-x86_64-meson mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-nasm mingw-w64-x86_64-libcdio-paranoia mingw-w64-x86_64-opus mingw-w64-x86_64-lame mingw-w64-x86_64-libvorbis mingw-w64-x86_64-libxml2 mingw-w64-x86_64-nghttp2 update: true - name: Build run: | .github/mingw-build.sh zip cyanrip cyanrip.exe - name: "Upload artifact" uses: actions/upload-artifact@v2 with: name: cyanrip path: cyanrip.exe - name: Get Release id: get_release shell: bash run: | echo "::set-output name=upload_url_release::$(curl -sL https://api.github.com/repos/${{ github.repository }}/releases/tags/$(cut -d/ -f3 <<< ${{ github.ref }}) | jq -r ."upload_url")" echo "::set-output name=upload_url_nightly::$(curl -sL https://api.github.com/repos/${{ github.repository }}/releases/tags/nightly | jq -r ."upload_url")" echo "::set-output name=nightly_id::$(curl -sL https://api.github.com/repos/${{ github.repository }}/releases/tags/nightly | jq -r ."id")" - name: Upload a Release Asset if: steps.get_release.outputs.upload_url_release != 'null' uses: actions/upload-release-asset@v1.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.get_release.outputs.upload_url_release }} asset_path: ./cyanrip.exe asset_name: cyanrip-win64.exe asset_content_type: application/vnd.microsoft.portable-executable - name: Upload versioned Nightly asset if: steps.get_release.outputs.upload_url_release == 'null' && steps.get_release.outputs.upload_url_nightly != 'null' uses: WebFreak001/deploy-nightly@v1.1.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.get_release.outputs.upload_url_nightly }} release_id: ${{ steps.get_release.outputs.nightly_id }} asset_path: ./cyanrip.exe asset_name: cyanrip-win64-$$.exe asset_content_type: application/vnd.microsoft.portable-executable max_releases: 4 - name: Delete existing nightly latest id: del_nightly_release if: steps.get_release.outputs.upload_url_release == 'null' && steps.get_release.outputs.upload_url_nightly != 'null' uses: mknejp/delete-release-assets@v1 with: token: ${{ secrets.GITHUB_TOKEN }} tag: nightly assets: cyanrip-win64-latest.exe fail-if-no-assets: false - name: Upload latest Nightly Asset if: steps.del_nightly_release.conclusion == 'success' && steps.get_release.outputs.upload_url_nightly != 'null' uses: actions/upload-release-asset@v1.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.get_release.outputs.upload_url_nightly }} asset_path: ./cyanrip.exe asset_name: cyanrip-win64-latest.exe asset_content_type: application/vnd.microsoft.portable-executable cyanrip-0.9.2/.gitignore000066400000000000000000000000541452633423300151630ustar00rootroot00000000000000build *.log .github/_build .github/_install cyanrip-0.9.2/Chanelog.md000066400000000000000000000042521452633423300152410ustar00rootroot000000000000000.9.2 ===== - ReplayGain 2.0 scanning and tagging - Preemphasis detection via both TOC and subchannel - Automatic deemphasis - CUE file writing - Repeat ripping mode for affirmation or badly damaged discs - Tagging improvements (setting the media_type tag) - Logfile reorganization and checksumming - Windows compatibility improvements - Migration to new FFmpeg 6.0 APIs 0.9.0 ===== - Improve MusicBrainz query result handling and detect stub releases - For unknown discs, add an ID to the album name - Better error reporting when opening logfile - Fix crash when MCN is missing - Silence warning when writing cover art to a file - Fix compilation with FFmpeg 6.0 0.8.1 ===== - __No need to rerip anything.__ - Fix Musicbrainz album name setting. 0.8.0 ===== - __No need to rerip anything.__ - ETA printout - Minor bugfixes - Big endian fixes - Default bitrate for lossy files set to 256kbps - Fix minor compilation warnings - Fix compilation warnings with FFmpeg 5.0 0.7 === - __No need to rerip anything.__ - Automated CD drive offset finding - Verification of partially damaged tracks - Tagging usability improvements - Even faster ripping - Arbitrary directory/file structure - Automatic cover art image downloading - ...and more 0.6.0 ===== - __No change in actual audio data ripped, rerip if you want to verify with accurip.__ - Fill disc count and disc number from musicbrainz. - Able to choose the MusicBrainz release to use for albums with multiple releases. - Tag improvements (discname is set when available for multi-disc releases). - Fix accurip v1 and v2 checksums (were calculated incorrectly). EAC CRC has always been correct. - Fix some minor and one large memory leak. 0.5.2 ===== - __No need to rerip anything.__ - Fix encoding while ripping from a real drive (broken by 0.5.0). 0.5.1 ===== - __No need to rerip anything.__ - Reduce FFmpeg library version requirements 0.5.0 ===== - __No need to rerip anything.__ - Rewritten audio muxing * Now properly sets the time base in all cases - Rewritten encoding code - Rewritten FIFO code * No longer deadlocks - Rewritten build system Previous versions ================= No history. cyanrip-0.9.2/LICENSE.md000066400000000000000000000633421452633423300146100ustar00rootroot00000000000000### GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] ### Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. ### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION **0.** This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. **1.** You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. **2.** You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: - **a)** The modified work must itself be a software library. - **b)** You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. - **c)** You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. - **d)** If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. **3.** You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. **4.** You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. **5.** A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. **6.** As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: - **a)** Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) - **b)** Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. - **c)** Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. - **d)** If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. - **e)** Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. **7.** You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: - **a)** Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. - **b)** Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. **8.** You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. **9.** You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. **10.** Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. **11.** If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. **12.** If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. **13.** The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. **14.** If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. **NO WARRANTY** **15.** BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. **16.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. ### END OF TERMS AND CONDITIONS ### How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. one line to give the library's name and an idea of what it does. Copyright (C) year name of author 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 Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. signature of Ty Coon, 1 April 1990 Ty Coon, President of Vice That's all there is to it!cyanrip-0.9.2/README.md000066400000000000000000000532451452633423300144640ustar00rootroot00000000000000[cyanrip](#cyanrip) =================== Fully featured CD ripping program able to take out most of the tedium. Fully accurate, has advanced features most rippers don't, yet has no bloat and is cross-platform. Features -------- * Automatic tag lookup from the MusicBrainz database * Encoded and muxed via FFmpeg (currently supports flac, opus, mp3, tta, wavpack, alac, vorbis and aac) * Drive offset compensation and error recovery via cd-paranoia * [Full pregap handling](#pregap-handling) * [HDCD detection and decoding](#hdcd-decoding) * [CD Deemphasis (TOC + subcode)](#deemphasis) * [Multi-disc album ripping](#multi-disc-albums) * [ReplayGain v2 tagging](#replaygain) * Able to encode to multiple formats in parallel * [Cover image embedding](#cover-art-embedding) in mp3, flac, aac and opus * Automatic [cover art image downloading](#cover-art-downloading) * Provides and automatically verifies EAC CRC32, AccurateRip V1 and V2 checksums * Accurate ripping verification of partially damaged tracks * Automatic drive offset finding Installation ------------ ### Alpine Linux ```bash apk add cyanrip ``` ### Archlinux ```bash pacaur -S cyanrip ``` Or use your favorite AUR installation method. ### Void Linux ```bash xbps-install -S cyanrip ``` ### FreeBSD ```bash pkg install cyanrip ``` Or via ports: `cd /usr/ports/audio/cyanrip && make install clean`. ### NixOS ```bash nix-env -iA nixos.cyanrip ``` ### Docker ```bash docker pull ep76/cyanrip ``` ### Automated Windows builds ![Windows CI](https://github.com/cyanreg/cyanrip/workflows/CI/badge.svg) [Latest release Windows build](https://github.com/cyanreg/cyanrip/releases/latest/download/cyanrip-win64.exe) [Latest Windows build](https://github.com/cyanreg/cyanrip/releases/download/nightly/cyanrip-win64-latest.exe) If the latest build is broken, you can find older ones in the [nightly release page](https://github.com/cyanreg/cyanrip/releases/tag/nightly) ### Compiling Complete list of dependencies: * FFmpeg (at least 4.0, libavcodec, libswresample, libavutil, libavformat, libavfilter) * libcdio-paranoia * libmusicbrainz5 * libcurl All are available on any up-to-date Linux distribution's package repositories. To compile and install on any *NIX platform: `meson build` `ninja -C build` `sudo ninja -C build install` cyanrip can be also built and ran under Windows using MinGW CLI --- Arguments are optional, except `-s`. By default cyanrip will rip all tracks from the default CD drive, output to flac only, enables all cd-paranoia error checking, performs a MusicBrainz lookup, and downloads and embeds the cover art if one is found. | Argument | Description | |----------------------|---------------------------------------------------------------------------------------------| | | **Ripping options** | | -d `string` | The path or name for a specific device, otherwise uses the default device | | -s `int` | Specifies the CD drive offset in samples (same as EAC, default is 0) | | -r `int` | Specifies how many times to retry a frame/ripping if it fails, (default is 10) | | -Z `int` | Rips tracks until their checksums match `` number of times. For very damaged CDs. | | -S `int` | Sets the drive speed if possible (default is unset, usually maximum) | | -p `number=string` | Specifies what to do with the pregap, syntax is described below | | -P `int` | Sets the paranoia level to use, by default its max, 0 disables all checking completely | | -O | Overread into lead-in/lead-out areas, if unsupported by drive may freeze ripping | | -H | Enable HDCD decoding, read below for details | | -E | Force CD deemphasis, for CDs mastered with preemphasis without actually signalling it | | -W | Disable automatic CD deemphasis. Read [below](#deemphasis) for details. | | -K | Disable ReplayGain tag generation. Read [replaygain](#replaygain) for details. | | | **Output options** | | -o `list` | Comma separated list of output formats (encodings). Use "help" to list all. Default is flac | | -b `int` | Bitrate in kbps for lossy formats, 256 by default | | -D `string` | Directory naming scheme, see [below](#naming-scheme) | | -F `string` | File naming scheme, see [below](#naming-scheme) | | -L `string` | Log naming scheme, see [below](#naming-scheme) | | -M `string` | CUE file naming scheme, see [below](#naming-scheme) | | -l `list` | Comma separated list of track numbers to rip, (default is it rips all) | | -T `string` | Filename sanitation, default is unicode, see [below](#filename-sanitation) | | | **Metadata options** | | -I | Only print CD metadata and information, will not rip or eject the CD | | -a `string` | Album metadata, syntax is described below | | -t `number=string` | Track metadata, syntax is described below | | -R `int` or `string` | Sets the MusicBrainz release to use, either as an index starting from 1 or an ID string | | -c `int/int` | Tag multi-disc albums as such, syntax is `disc/totaldiscs`, read below | | -C `path` or `url` | Sets cover image to embed into each track, syntax is described below | | -N | Disables MusicBrainz lookup and ignores lack of manual metadata to continue | | -A | Disables AccurateRip database query and comparison | | -U | Disables Cover art DB database query and retrieval | | -G | Disables embedding of cover art images | | | **Misc. options** | | -Q | Eject CD tray if ripping has been successfully completed | | -V | Print version | | -h | Print usage (this) | | -f | Find drive offset (requires a disc with an AccuRip DB entry) | Metadata -------- In case the MusicBrainz database doesn't contain the disc information, is incomplete, or incorrect, you can manually add metadata via the -a argument for album metadata and -t argument for track metadata: `-a album="Name":album_artist="Artist":date="2018":random_tag="Value"` `-t 1=artist="Track Artist":lyrics="Name":random_tag="Value" -t 3=artist="Someone Else"` All key=value pair tags must be separated by `:`. For track tags, the syntax is `-t track_number=key=value:key=value`. You need to specify the -t argument separately for each track. For convenience, if any of the first 2 metadata tags of tracks are missing a key, such as with `-t 2=some_title:some_artist:key=value`, cyanrip will automatically prepend `title=` and `artist=` such that it becomes `-t title=some_title:artist=some_artist:key=value`. A missing key in tag 1 is always considered a title while a missing key in tag 2 is always considered artist, so either can be skipped with no effect. The same goes for album tags, with `album=` and `album_artist=` being omitable. For album tags, if either `artist` or `album_artist` are unset, their values will be mirrored if one is available. The precedence of tags is Track tags > Album tags > MusicBrainz tags. Output ------ The output encoding(s) can be set via the `-o` option as a comma-separated list. Currently, the following formats are available: | Format name | Description | Extension | Cover art embedding | Notes | |-------------|-------------------------------------------------|-----------|---------------------|-----------------------------------------------------------| | `flac` | Standard FLAC files | `.flac` | :heavy_check_mark: | Always uses maximum compression | | `tta` | TTA (True Audio) files | `.tta` | ⬜ | Always uses maximum compression | | `opus` | Standard Opus files (in an Ogg container) | `.opus` | ⬜ | VBR, use -b to adjust the bitrate, default is 256 (kbps) | | `aac` | Standard AAC files | `.aac` | ⬜ | Use -b to adjust the bitrate, default is 256 (kbps) | | `wavpack` | Standard lossless WavPack files | `.wv` | ⬜ | Always uses maximum compression | | `alac` | Standard ALAC files | `.alac` | ⬜ | Always uses maximum compression | | `mp3` | Standard MP3 files | `.mp3` | :heavy_check_mark: | VBR, use -b to adjust the bitrate, default is 256 (kbps) | | `vorbis` | Standard Ogg/Vorbis files (in an OGG container) | `.ogg` | ⬜ | Use -b to adjust the bitrate, default is 256 (kbps) | | `wav` | Standard WAV files | `.wav` | ⬜ | 16-bit little endian signed audio, or 32-bit in HDCD mode | | `aac_mp4` | Standard MP4 files (with AAC encoding) | `.mp4` | :heavy_check_mark: | Use -b to adjust the bitrate, default is 256 (kbps) | | `opus_mp4` | Standard MP4 files (with Opus encoding) | `.mp4` | :heavy_check_mark: | Use -b to adjust the bitrate, default is 256 (kbps) | | `pcm` | Raw audio, 16-bits, two channel, little-endian | `.raw` | ⬜ | | For example, to make both FLAC and MP3 files simultaneously, use `-o flac,mp3`. Encodings are done in parallel during ripping, so adding more does not slow down the process. To adjust the directories and filenames, read the [naming scheme](#naming-scheme) section below. Pregap handling --------------- By default, track 1 pregap is ignored, while any other track's pregap is merged into the previous track. This is identical to EAC's default behaviour. You can override what's done with each pregap on a per-track basis using the `-p track_number=action` argument. This argument must be specified separately for each track. | *action* | Description | |----------|--------------------------------------------| | default | Merge into previous track, drop on track 1 | | drop | Drop the pregap entirely | | merge | Merge into current track | | track | Split into a new track before the current | If the pregap offset isn't available for a given track, this argument will do nothing. cyanrip guarantees that there will be no discontinuities between tracks, unless the drop action is used to delete a pregap. Cover art embedding ------------------- cyanrip supports embedding album and track cover art. To embed cover art for the whole album, specify it with the `-C path` or `-C destination=path` parameter. `path` can be a URL, in which case it will be automatically downloaded. If `destination` is a number, the cover art will be embedded only for the track with that number. Otherwise, `destination` should be a descriptor like `Front` or `Back` or `Disc`. If `destination` is omitted, `Front`, then `Back` will be used. Cover arts which are not attached to a track will be copied to each output directory as `destination` with an autodetected extension. If multiple cover arts are present and no track cover art is specified, only the cover art with `Front` destination will be embedded, or the first cover art specified if no `Front` exists. Otherwise, only the specified track cover art will be embedded. Cover art downloading --------------------- If a release ID is specified or detected, and no `Front` or `Back` cover arts were specified, a query will be made to the [Cover Art Archive](https://coverartarchive.org/) and if found, the archive cover art will be downloaded and used. Multi-disc albums ----------------- cyanrip supports ripping multi-disc albums in the same output folder. To enable this manually, specify the `-c` argument followed by `disc/totaldiscs` (`/totaldiscs` is optional), otherwise it'll be done automatically if the MusicBrainz tags indicate so. The track filenames will be `disc.track - title.ext`. The logfile will be `Album name CD.log`. As well as using the `-c` argument, you can also specify the `disc=number:totaldiscs=number` in the album/track metadata. If each disc has a title, you should use the `discname` tag if you're manually setting tags, which is what MusicBrainz will set if available. HDCD decoding ------------- cyanrip can decode and detect HDCD encoded discs. To check if a suspected disc contains HDCD audio, rip a single track using the `-l 1` argument and look at the log. A non-HDCD encoded disc will have: ``` HDCD detected: no ``` If the CD does contain HDCD audio something similar to the following will be printed: ``` HDCD detected: yes, peak_extend: never enabled, max_gain_adj: 0.0 dB, transient_filter: not detected, detectable errors: 0 ``` Should a track be detected as HDCD, it would be safe to proceed decoding all of the disc. The resulting encoded files will have a bit depth of at least 24 bits. Deemphasis ---------- Old (and not so old) CDs may require deeemphasis. cyanrip is able to detect such discs (using both the TOC and track subcode) and will apply a correction filter automatically. To check whether the CD contains emphasised audio, you can simply run `cyanrip -I` and read the printout. ``` Preemphasis: ``` for each track. `status` can be either `none detected` or `present (TOC)` or `present (subcode)`. Action may be either `(deemphasis applied)`, which applies a deemphasis filter to the audio (default), `(deemphasis forced)` which does the same, even if no preemphasis is signalled (can be activated via the `-E` flag, or blank, which deactivates automatic deemphasis, preserving audio data as-is (activated via the `-W` flag). The `-E` flag can be useful for some CDs, which may have been mastered with preemphasis, but don't actually signal it via the track substream properties. An incomplete list of releases mastered with preemphasis can be found at [this wiki page](https://www.studio-nibble.com/cd/index.php?title=Pre-emphasis_(release_list)). ReplayGain ---------- cyanrip will automatically compute ReplayGain tags and add them to all files while ripping. Note, that this requires to hold all **compressed** audio data for the entire CD in RAM while ripping, which may be from 300 to 600 megabytes, depending on the output used. Low-power and low-RAM devices can turn this off via the `-K` switch. The tags generated are ReplayGain 2.0 compliant, which is backwards-compatible with ReplayGain 1.0. The **true peak** value is calculated and used. Paranoia status count --------------------- At the end of the ripping process, cyanrip will print and log a summary of cdparanoia's status during ripping. This can be used to estimate the disc/drive's health. An idealized disc and drive will only log `READ: $number$` and nothing else. This happens while ripping from a file. In general `READ`/`VERIFY`/`OVERLAP` are normal and will happen when ripping a brand new CD with a new drive. `FIXUP_EDGE`/`FIXUP_ATOM` usually happen if cdparanoia is somewhat struggling, but both are recoverable and lossless. `FIXUP_DROPPED`/`FIXUP_DUPED` indicate more severe errors, but are still recoverable and lossless, though a hard read error will often follow. `READERR` indicates that cdparanoia gave up after all retries and outputted zeroes for all samples it couldn't recover. Naming scheme ------------- cyanrip supports highly flexible naming schemes via a custom syntax. You can extensively customize how all files and directories are named. The default naming scheme for albums is `Album name [Format]` if the `releasecomment` tag is empty or `Album name (Release comment) [Format]` if it isn't. For tracks, its `Track number - Track title.Extension` if there's a single disc in the album or `Disc Number.Track number - Track title.Extension` if there are multiple. The log file will be named `Album name.log` in each of the output folders unless there are multiple CDs in the album, in which case it'll be `Album name CD1.log` for the first CD and so on. If you would like something different, read on. If for a one-off you'd like to specify a different directory name, you can just use `-D Directory` and not read further. The syntax is as follows: everything not inside `{` or `}` tags gets copied as-is unconditionally. Everything inside `{` or `}` tags is interpreted: if what's inside the tags matches a metadata key, the tag along with its outer brackets is replaced as-is. Otherwise only the tag brackets are removed and what's inside is copied literally. Conditions are possible and must follow this syntax: `{if #token1# != #token2#album name is |album|}`. Condition tokens must be wrapped in `#` tags, and both must exist. In between the 2 tokens must be either `>` or `<` or `==` or `!=`. If any of the tokens inside the `#` tags matches a metadata key, it is replaced with a value, otherwise its taken literally (so if the `album` tag exists, `#album#` resolves to the album name, otherwise to just `album`). If the condition is a direct comparison (`==` or `!=`), then the 2 tokens are compared as strings. If arithmetic comparison is used and the 2 tokens are integers, they're compared arithmetically. If an arithmetic comparison is used when both tokens are strings, the result of `strcmp` is used. If only one is a string, its pointer (always above 0, __unless__ the token did not match a metadata key, in which case 0) is used. If the condition is true, everything after the last token's `#` is copied, with any metadata tags there wrapped with `|`. Otherwise, nothing is copied. Examples are easier to understand, by default the folder value of `{album}{if #releasecomment# > #0# (|releasecomment|)} [{format}]` is used. This resolves to `Album [FLAC]` if there's nothing in the `releasecomment` key, or `Album (Release comment) [FLAC]` if there is. The default track file name syntax is: `{if #totaldiscs# > #1#|disc|.}{track} - {title}`. So this resolves to `01 - Title.flac` if there's a single CD, or `1.01 - Title.flac` if there are more than 1 CDs and you're ripping the first one. A useful example is to have separate directories for each disc: `-D "{album}{if #totaldiscs# > #1# CD|disc|} [{format}]" -F "{track} - {title}"`. The ripping log name and location can be modified via the `-L` argument. By default its set to `{album}{if #totaldiscs# > #1# CD|disc|}`, which resolves to `Album name.log` for 1 CD and `Album name CD1.log` if there are multiple CDs and you're ripping the first. Filename sanitation ------------------- If invalid symbols are found in a file or a folder, such as `:` or `/`, the symbol is by default substituted with a unicode lookalike, such as `∶` or `∕` respectively. If this is undesirable, this can be overridden via the `-T simple` argument. This will replace all invalid symbols with `_`. In case you're on an operating system with more liberal allowance on filenames, you can use the `-T os_unicode` option to allow symbols like `:` not supported on Windows to be passed through. Note that this will make files like these not accessible on Windows, unless renamed, so use this only if you're sure. CUE sheet --------- cyanrip will generate a CUE sheet from which a byte-exact duplicate of the disc can be made. By default, pregaps are kept in the CUE sheet as being appended to the previous track (except for the first track, where the pregap is dropped and signalled as silence). This is reffered to as "noncompliant" by [hydrogenaudio](https://wiki.hydrogenaud.io/index.php?title=Cue_sheet#Multiple_files_with_gaps_.28Noncompliant.29). Custom changes in the way pregaps are handled will be reflected in the CUE file. For example, dropping a pregap will signal [silence](https://wiki.hydrogenaud.io/index.php?title=Cue_sheet#Multiple_files_with_gaps_left_out) in the CUE sheet. Appending a pregap to the track will accordingly mark the track as [having two audio indices](https://wiki.hydrogenaud.io/index.php?title=Cue_sheet#Multiple_files_with_corrected_gaps). Links ===== You can talk about the project and get in touch with developers on: - IRC: **`#cyanrip`** on the [Libera.Chat](ircs://irc.libera.chat:6697) network - [Libera.Chat’s guide on how to connect](https://libera.chat/guides/connect) - Or use the [Kiwi web client](https://kiwiirc.com/nextclient/irc.libera.chat/?#cyanrip) cyanrip-0.9.2/meson.build000066400000000000000000000015171452633423300153420ustar00rootroot00000000000000project('cyanrip', 'c', license: 'LGPL2.1+', default_options: [ 'buildtype=debugoptimized', 'c_std=c99', 'warning_level=1' ], version: '0.9.0', meson_version: '>=0.53.0', ) conf = configuration_data() conf.set_quoted('PROJECT_NAME', meson.project_name()) conf.set_quoted('PROJECT_VERSION_STRING', meson.project_version()) version_split = meson.project_version().split('.') ver_major = version_split[0] ver_minor = version_split[1] ver_micro = version_split[2] conf.set('PROJECT_VERSION_MAJOR', ver_major) conf.set('PROJECT_VERSION_MINOR', ver_minor) conf.set('PROJECT_VERSION_MICRO', ver_micro) if host_machine.endian() == 'big' conf.set('CONFIG_BIG_ENDIAN', 1) else conf.set('CONFIG_BIG_ENDIAN', 0) endif cc = meson.get_compiler('c') subdir('src') configure_file( output: 'config.h', configuration: conf, ) cyanrip-0.9.2/src/000077500000000000000000000000001452633423300137635ustar00rootroot00000000000000cyanrip-0.9.2/src/accurip.c000066400000000000000000000200271452633423300155560ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "accurip.h" #include "cyanrip_log.h" #include "bytestream.h" #define ACCURIP_DB_BASE_URL "http://www.accuraterip.com/accuraterip" static int get_accurip_ids(cyanrip_ctx *ctx, uint32_t *id_type_1, uint32_t *id_type_2) { int audio_tracks = 0; uint64_t idt1 = 0x0; uint64_t idt2 = 0x0; for (int i = 0; i < ctx->nb_cd_tracks; i++) { cyanrip_track *t = &ctx->tracks[i]; if (t->track_is_data) continue; idt1 += t->start_lsn; idt2 += (t->start_lsn ? t->start_lsn : 1) * t->number; audio_tracks++; } lsn_t last = ctx->tracks[audio_tracks - 1].end_lsn + 1; idt1 += last; idt2 += last * (audio_tracks + 1); idt1 &= 0xffffffff; idt2 &= 0xffffffff; *id_type_1 = idt1; *id_type_2 = idt2; return audio_tracks; } typedef struct RecvCtx { cyanrip_ctx *ctx; uint8_t *data; size_t size; } RecvCtx; static size_t receive_data(void *buffer, size_t size, size_t nb, void *opaque) { RecvCtx *rctx = opaque; rctx->data = av_realloc(rctx->data, rctx->size + (size * nb)); memcpy(rctx->data + rctx->size, buffer, size * nb); rctx->size += size * nb; return size * nb; } static int cmp_conf(const void *a, const void *b) { return ((CRIPAccuDBEntry *)a)->confidence - ((CRIPAccuDBEntry *)b)->confidence; } int crip_fill_accurip(cyanrip_ctx *ctx) { int ret = 0; char errbuf[CURL_ERROR_SIZE]; RecvCtx rctx = { .ctx = ctx }; if (ctx->settings.disable_accurip) return 0; CURL *curl_ctx = curl_easy_init(); /* Get both accurip disc IDs */ uint32_t id_type_1, id_type_2; int audio_tracks = get_accurip_ids(ctx, &id_type_1, &id_type_2); /* Get CDDB ID */ const char *cddb_id_str = dict_get(ctx->meta, "cddb"); if (!cddb_id_str) { cyanrip_log(ctx, 0, "Unable to get AccuRIP DB data: missing CDDB ID!\n"); goto end; } /* Format all the data in the needed way */ uint32_t cddb_id = strtol(cddb_id_str, NULL, 16); char id_type_1_s[9] = { 0 }; snprintf(id_type_1_s, sizeof(id_type_1_s), "%08x", id_type_1); /* Finally compose the URL */ char request_url[512] = { 0 }; snprintf(request_url, sizeof(request_url), "%s/%c/%c/%c/dBAR-%.3d-%s-%08x-%08x.bin", ACCURIP_DB_BASE_URL, id_type_1_s[7], id_type_1_s[6], id_type_1_s[5], audio_tracks, id_type_1_s, id_type_2, cddb_id); curl_easy_setopt(curl_ctx, CURLOPT_URL, request_url); char user_agent[256] = { 0 }; sprintf(user_agent, "cyanrip/%s ( https://github.com/cyanreg/cyanrip )", PROJECT_VERSION_STRING); curl_easy_setopt(curl_ctx, CURLOPT_USERAGENT, user_agent); curl_easy_setopt(curl_ctx, CURLOPT_WRITEFUNCTION, receive_data); curl_easy_setopt(curl_ctx, CURLOPT_WRITEDATA, &rctx); curl_easy_setopt(curl_ctx, CURLOPT_ERRORBUFFER, errbuf); curl_easy_setopt(curl_ctx, CURLOPT_FAILONERROR, 1L); /* Explode on errors */ CURLcode res = curl_easy_perform(curl_ctx); if (res != CURLE_OK) { /* Filter out the majority of 404 errors here */ if (res == CURLE_HTTP_RETURNED_ERROR) { cyanrip_log(ctx, 0, "Unable to get AccuRIP DB data: missing entry!\n"); ctx->ar_db_status = CYANRIP_ACCUDB_NOT_FOUND; goto end; } /* Different error */ size_t len = strlen(errbuf); if (len) cyanrip_log(ctx, 0, "Unable to get AccuRIP DB data: %s%s", errbuf, ((errbuf[len - 1] != '\n') ? "\n" : "")); else cyanrip_log(ctx, 0, "Unable to get AccuRIP DB data: %s\n!\n", curl_easy_strerror(res)); ctx->ar_db_status = CYANRIP_ACCUDB_ERROR; goto end; } /* Get content type, if not possible we probably have an error somewhere */ char *content_type = NULL; res = curl_easy_getinfo(curl_ctx, CURLINFO_CONTENT_TYPE, &content_type); if (res != CURLE_OK) { cyanrip_log(ctx, 0, "Unable to get AccuRIP DB data: %s\n!\n", curl_easy_strerror(res)); goto end; } /* If we have a binary we're pretty sure we've found a match */ if (strcmp(content_type, "application/octet-stream")) { /* Atrocious heuristics to determine whether we have an error or binary data, don't look */ char *html_loc = strstr((const char *)rctx.data, "html"); if (html_loc && (html_loc - (char *)rctx.data) < 64) { /* If we have "html" in the first 64 bytes its likely an error. * This is painful to write. */ cyanrip_log(ctx, 0, "Unable to get AccuRIP DB data: missing entry!\n"); ctx->ar_db_status = CYANRIP_ACCUDB_NOT_FOUND; goto end; } } GetByteContext gbc = { 0 }; bytestream2_init(&gbc, rctx.data, rctx.size); ctx->ar_db_status = CYANRIP_ACCUDB_FOUND; int entry_size = 1 + 12 + audio_tracks * (1 + 8); if (rctx.size % entry_size) { cyanrip_log(ctx, 0, "AccuRIP DB data error, got unexpected number of bytes!\n"); ctx->ar_db_status = CYANRIP_ACCUDB_ERROR; goto end; } int nb_entries = rctx.size / entry_size; for (int i = 0; i < nb_entries; i++) { if (bytestream2_get_byte(&gbc) != audio_tracks || bytestream2_get_le32(&gbc) != id_type_1 || bytestream2_get_le32(&gbc) != id_type_2 || bytestream2_get_le32(&gbc) != cddb_id) { if (ctx->ar_db_status != CYANRIP_ACCUDB_FOUND) ctx->ar_db_status = CYANRIP_ACCUDB_MISMATCH; bytestream2_skip(&gbc, entry_size - (bytestream2_tell(&gbc) % entry_size)); continue; } ctx->ar_db_status = CYANRIP_ACCUDB_FOUND; for (int j = 0; j < audio_tracks; j++) { cyanrip_track *t = &ctx->tracks[j]; int confidence = bytestream2_get_byte(&gbc); uint32_t checksum = bytestream2_get_le32(&gbc); uint32_t checksum_450 = bytestream2_get_le32(&gbc); if (t->track_is_data) continue; t->ar_db_entries = av_realloc(t->ar_db_entries, sizeof(CRIPAccuDBEntry) * (t->ar_db_nb_entries + 1)); t->ar_db_status = CYANRIP_ACCUDB_FOUND; t->ar_db_entries[t->ar_db_nb_entries].confidence = confidence; t->ar_db_entries[t->ar_db_nb_entries].checksum = checksum; t->ar_db_entries[t->ar_db_nb_entries].checksum_450 = checksum_450; t->ar_db_max_confidence = FFMAX(confidence, t->ar_db_max_confidence); t->ar_db_nb_entries++; } } for (int i = 0; i < audio_tracks; i++) { cyanrip_track *t = &ctx->tracks[i]; if (t->ar_db_nb_entries) qsort(t->ar_db_entries, t->ar_db_nb_entries, sizeof(CRIPAccuDBEntry), cmp_conf); } end: curl_easy_cleanup(curl_ctx); av_free(rctx.data); return ret; } int crip_find_ar(cyanrip_track *t, uint32_t checksum, int is_450) { if (t->ar_db_status != CYANRIP_ACCUDB_FOUND) return 0; for (int i = 0; i < t->ar_db_nb_entries; i++) { CRIPAccuDBEntry *e = &t->ar_db_entries[i]; if (is_450 && e->checksum_450 == checksum) return e->confidence; else if (e->checksum == checksum) return e->confidence; } return -1; } cyanrip-0.9.2/src/accurip.h000066400000000000000000000016251452633423300155660ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include int crip_fill_accurip(cyanrip_ctx *ctx); int crip_find_ar(cyanrip_track *t, uint32_t checksum, int is_450); cyanrip-0.9.2/src/bytestream.h000066400000000000000000000173111452633423300163160ustar00rootroot00000000000000/* * Bytestream functions * copyright (c) 2006 Baptiste Coudurier * Copyright (c) 2012 Aneesh Dogra (lionaneesh) * * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include #include #include #include #include #include "../config.h" typedef struct GetByteContext { const uint8_t *buffer, *buffer_end, *buffer_start; } GetByteContext; #define DEF(type, name, bytes, read, write) \ static av_always_inline type bytestream_get_ ## name(const uint8_t **b) \ { \ (*b) += bytes; \ return read(*b - bytes); \ } \ static av_always_inline type bytestream2_get_ ## name ## u(GetByteContext *g) \ { \ return bytestream_get_ ## name(&g->buffer); \ } \ static av_always_inline type bytestream2_get_ ## name(GetByteContext *g) \ { \ if (g->buffer_end - g->buffer < bytes) { \ g->buffer = g->buffer_end; \ return 0; \ } \ return bytestream2_get_ ## name ## u(g); \ } \ static av_always_inline type bytestream2_peek_ ## name(GetByteContext *g) \ { \ if (g->buffer_end - g->buffer < bytes) \ return 0; \ return read(g->buffer); \ } DEF(uint64_t, le64, 8, AV_RL64, AV_WL64) DEF(unsigned int, le32, 4, AV_RL32, AV_WL32) DEF(unsigned int, le24, 3, AV_RL24, AV_WL24) DEF(unsigned int, le16, 2, AV_RL16, AV_WL16) DEF(uint64_t, be64, 8, AV_RB64, AV_WB64) DEF(unsigned int, be32, 4, AV_RB32, AV_WB32) DEF(unsigned int, be24, 3, AV_RB24, AV_WB24) DEF(unsigned int, be16, 2, AV_RB16, AV_WB16) DEF(unsigned int, byte, 1, AV_RB8 , AV_WB8) #if CONFIG_BIG_ENDIAN # define bytestream2_get_ne16 bytestream2_get_be16 # define bytestream2_get_ne24 bytestream2_get_be24 # define bytestream2_get_ne32 bytestream2_get_be32 # define bytestream2_get_ne64 bytestream2_get_be64 # define bytestream2_get_ne16u bytestream2_get_be16u # define bytestream2_get_ne24u bytestream2_get_be24u # define bytestream2_get_ne32u bytestream2_get_be32u # define bytestream2_get_ne64u bytestream2_get_be64u # define bytestream2_peek_ne16 bytestream2_peek_be16 # define bytestream2_peek_ne24 bytestream2_peek_be24 # define bytestream2_peek_ne32 bytestream2_peek_be32 # define bytestream2_peek_ne64 bytestream2_peek_be64 #else # define bytestream2_get_ne16 bytestream2_get_le16 # define bytestream2_get_ne24 bytestream2_get_le24 # define bytestream2_get_ne32 bytestream2_get_le32 # define bytestream2_get_ne64 bytestream2_get_le64 # define bytestream2_get_ne16u bytestream2_get_le16u # define bytestream2_get_ne24u bytestream2_get_le24u # define bytestream2_get_ne32u bytestream2_get_le32u # define bytestream2_get_ne64u bytestream2_get_le64u # define bytestream2_peek_ne16 bytestream2_peek_le16 # define bytestream2_peek_ne24 bytestream2_peek_le24 # define bytestream2_peek_ne32 bytestream2_peek_le32 # define bytestream2_peek_ne64 bytestream2_peek_le64 #endif static av_always_inline void bytestream2_init(GetByteContext *g, const uint8_t *buf, int buf_size) { av_assert0(buf_size >= 0); g->buffer = buf; g->buffer_start = buf; g->buffer_end = buf + buf_size; } static av_always_inline int bytestream2_get_bytes_left(GetByteContext *g) { return g->buffer_end - g->buffer; } static av_always_inline void bytestream2_skip(GetByteContext *g, unsigned int size) { g->buffer += FFMIN(g->buffer_end - g->buffer, size); } static av_always_inline void bytestream2_skipu(GetByteContext *g, unsigned int size) { g->buffer += size; } static av_always_inline int bytestream2_tell(GetByteContext *g) { return (int)(g->buffer - g->buffer_start); } static av_always_inline int bytestream2_size(GetByteContext *g) { return (int)(g->buffer_end - g->buffer_start); } static av_always_inline int bytestream2_seek(GetByteContext *g, int offset, int whence) { switch (whence) { case SEEK_CUR: offset = av_clip(offset, -(g->buffer - g->buffer_start), g->buffer_end - g->buffer); g->buffer += offset; break; case SEEK_END: offset = av_clip(offset, -(g->buffer_end - g->buffer_start), 0); g->buffer = g->buffer_end + offset; break; case SEEK_SET: offset = av_clip(offset, 0, g->buffer_end - g->buffer_start); g->buffer = g->buffer_start + offset; break; default: return AVERROR(EINVAL); } return bytestream2_tell(g); } static av_always_inline unsigned int bytestream2_get_buffer(GetByteContext *g, uint8_t *dst, unsigned int size) { int size2 = FFMIN(g->buffer_end - g->buffer, size); memcpy(dst, g->buffer, size2); g->buffer += size2; return size2; } static av_always_inline unsigned int bytestream2_get_bufferu(GetByteContext *g, uint8_t *dst, unsigned int size) { memcpy(dst, g->buffer, size); g->buffer += size; return size; } static av_always_inline unsigned int bytestream_get_buffer(const uint8_t **b, uint8_t *dst, unsigned int size) { memcpy(dst, *b, size); (*b) += size; return size; } static av_always_inline void bytestream_put_buffer(uint8_t **b, const uint8_t *src, unsigned int size) { memcpy(*b, src, size); (*b) += size; } cyanrip-0.9.2/src/checksums.h000066400000000000000000000057021452633423300161250ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include #include #include "cyanrip_main.h" typedef struct cyanrip_checksum_ctx { const AVCRC *eac_ctx; uint32_t eac_crc; uint32_t acu_start; uint32_t acu_end; uint32_t acu_mult; uint32_t acu_sum_1; uint32_t acu_sum_1_450; uint32_t acu_sum_2; } cyanrip_checksum_ctx; static inline void crip_init_checksum_ctx(cyanrip_ctx *ctx, cyanrip_checksum_ctx *s, cyanrip_track *t) { s->eac_ctx = av_crc_get_table(AV_CRC_32_IEEE_LE); s->eac_crc = UINT32_MAX; s->acu_start = 0; s->acu_end = t->nb_samples; s->acu_mult = 1; s->acu_sum_1 = 0x0; s->acu_sum_1_450 = 0x0; s->acu_sum_2 = 0x0; t->computed_crcs = 0; if (t->acurip_track_is_first) s->acu_start += (CDIO_CD_FRAMESIZE_RAW * 5) >> 2; if (t->acurip_track_is_last) s->acu_end -= (CDIO_CD_FRAMESIZE_RAW * 5) >> 2; } static inline void crip_process_checksums(cyanrip_checksum_ctx *s, const uint8_t *data, int bytes) { if (!bytes) return; s->eac_crc = av_crc(s->eac_ctx, s->eac_crc, data, bytes); /* Loop over samples */ for (int j = 0; j < (bytes >> 2); j++) { if (s->acu_mult >= s->acu_start && s->acu_mult <= s->acu_end) { uint32_t val = AV_RL32(&data[j*4]); uint64_t tmp = (uint64_t)val * (uint64_t)s->acu_mult; uint32_t lo = (uint32_t)(tmp & (uint64_t)UINT32_MAX); uint32_t hi = (uint32_t)(tmp / (uint64_t)0x100000000); s->acu_sum_1 += s->acu_mult * val; s->acu_sum_2 += hi; s->acu_sum_2 += lo; } if (((s->acu_mult - 1) >= (450 * (CDIO_CD_FRAMESIZE_RAW >> 2))) && ((s->acu_mult - 1) < (451 * (CDIO_CD_FRAMESIZE_RAW >> 2)))) { uint32_t mult = s->acu_mult - (450 * (CDIO_CD_FRAMESIZE_RAW >> 2)); s->acu_sum_1_450 += AV_RL32(&data[j*4]) * mult; } s->acu_mult++; } } static inline void crip_finalize_checksums(cyanrip_checksum_ctx *s, cyanrip_track *t) { t->computed_crcs = 1; t->eac_crc = s->eac_crc ^ UINT32_MAX; t->acurip_checksum_v1 = s->acu_sum_1; t->acurip_checksum_v1_450 = s->acu_sum_1_450; t->acurip_checksum_v2 = s->acu_sum_2; } cyanrip-0.9.2/src/coverart.c000066400000000000000000000325401452633423300157600ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "coverart.h" #include "cyanrip_log.h" #define COVERART_DB_URL_BASE "http://coverartarchive.org/release" int crip_save_art(cyanrip_ctx *ctx, CRIPArt *art, const cyanrip_out_fmt *fmt) { int ret = 0; if (!art->pkt) { cyanrip_log(ctx, 0, "Cover art has no packet!\n"); return 0; } char *filepath = crip_get_path(ctx, CRIP_PATH_COVERART, 1, fmt, art); if (!filepath) return 0; AVFormatContext *avf = NULL; ret = avformat_alloc_output_context2(&avf, NULL, NULL, filepath); if (ret < 0) { cyanrip_log(ctx, 0, "Unable to init lavf context: %s!\n", av_err2str(ret)); goto fail; } AVStream *st = avformat_new_stream(avf, NULL); if (!st) { cyanrip_log(ctx, 0, "Unable to alloc stream!\n"); ret = AVERROR(ENOMEM); goto fail; } memcpy(st->codecpar, art->params, sizeof(AVCodecParameters)); st->time_base = (AVRational){ 1, 25 }; av_dict_copy(&st->metadata, art->meta, 0); av_dict_copy(&avf->metadata, art->meta, 0); /* Open for writing */ ret = avio_open(&avf->pb, filepath, AVIO_FLAG_WRITE); if (ret < 0) { cyanrip_log(ctx, 0, "Couldn't open %s for writing: %s!\n", filepath, av_err2str(ret)); goto fail; } av_freep(&filepath); /* Silence libavformat warning */ AVDictionary *opts = NULL; av_dict_set(&opts, "update", "1", 0); /* Write header */ ret = avformat_write_header(avf, &opts); av_dict_free(&opts); if (ret < 0) { cyanrip_log(ctx, 0, "Couldn't write header: %s!\n", av_err2str(ret)); goto fail; } AVPacket *pkt = av_packet_clone(art->pkt); pkt->stream_index = st->index; if ((ret = av_interleaved_write_frame(avf, pkt)) < 0) { cyanrip_log(ctx, 0, "Error writing picture packet: %s!\n", av_err2str(ret)); goto fail; } av_packet_free(&pkt); if ((ret = av_write_trailer(avf)) < 0) { cyanrip_log(ctx, 0, "Error writing trailer: %s!\n", av_err2str(ret)); goto fail; } fail: if (avf) avio_closep(&avf->pb); avformat_free_context(avf); return ret; } void crip_free_art(CRIPArt *art) { av_packet_free(&art->pkt); av_freep(&art->params); av_freep(&art->data); art->size = 0; av_freep(&art->source_url); av_freep(&art->extension); av_dict_free(&art->meta); } static size_t receive_image(void *buffer, size_t size, size_t nb, void *opaque) { CRIPArt *art = opaque; uint8_t *new_data = av_realloc(art->data, art->size + (size * nb)); if (!new_data) return 0; memcpy(new_data + art->size, buffer, size * nb); art->data = new_data; art->size += size * nb; return size * nb; } static int fetch_image(cyanrip_ctx *ctx, CURL *curl_ctx, CRIPArt *art, const char *release_id, const char *type, int info_only, const char *own_url) { int ret; char errbuf[CURL_ERROR_SIZE]; char temp[4096]; if (!own_url) { snprintf(temp, sizeof(temp), "%s/%s/%s", COVERART_DB_URL_BASE, release_id, type); curl_easy_setopt(curl_ctx, CURLOPT_URL, temp); } else { curl_easy_setopt(curl_ctx, CURLOPT_URL, own_url); } char user_agent[256] = { 0 }; sprintf(user_agent, "cyanrip/%s ( https://github.com/cyanreg/cyanrip )", PROJECT_VERSION_STRING); curl_easy_setopt(curl_ctx, CURLOPT_USERAGENT, user_agent); curl_easy_setopt(curl_ctx, CURLOPT_WRITEFUNCTION, receive_image); curl_easy_setopt(curl_ctx, CURLOPT_WRITEDATA, art); curl_easy_setopt(curl_ctx, CURLOPT_ERRORBUFFER, errbuf); curl_easy_setopt(curl_ctx, CURLOPT_FAILONERROR, 1L); /* Explode on errors */ if (!info_only) { cyanrip_log(ctx, 0, "Downloading %s cover art...\n", type); curl_easy_setopt(curl_ctx, CURLOPT_FOLLOWLOCATION, 1L); } CURLcode res = curl_easy_perform(curl_ctx); if (res != CURLE_OK) { /* Filter out the majority of 404 errors here */ if (res == CURLE_HTTP_RETURNED_ERROR) { cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": not found!\n", type); crip_free_art(art); ret = own_url ? AVERROR(EINVAL) : 0; goto end; } /* Different error */ size_t len = strlen(errbuf); if (len) cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": %s%s!\n", type, errbuf, ((errbuf[len - 1] != '\n') ? "\n" : "")); else cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": %s\n!\n", type, curl_easy_strerror(res)); ret = AVERROR(EINVAL); goto end; } /* Get content type, if not possible we probably have an error somewhere */ char *content_type = NULL; res = curl_easy_getinfo(curl_ctx, CURLINFO_CONTENT_TYPE, &content_type); if (res != CURLE_OK) { cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": %s\n!\n", type, curl_easy_strerror(res)); ret = AVERROR(EINVAL); goto end; } /* Response code */ long response_code = 0; res = curl_easy_getinfo(curl_ctx, CURLINFO_RESPONSE_CODE, &response_code); if (res != CURLE_OK) { cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": %s\n!\n", type, curl_easy_strerror(res)); ret = AVERROR(EINVAL); goto end; } /* Get the URL */ char *final_url = NULL; res = curl_easy_getinfo(curl_ctx, CURLINFO_EFFECTIVE_URL, &final_url); if (res != CURLE_OK) { cyanrip_log(ctx, 0, "Unable to get cover art \"%s\": %s\n!\n", type, curl_easy_strerror(res)); ret = AVERROR(EINVAL); goto end; } ret = art->size; if (info_only) { av_freep(&art->data); art->size = 0; } else { char header[99]; snprintf(header, sizeof(header), "data:%s;base64,", content_type); size_t data_len = AV_BASE64_SIZE(art->size); char *data = av_mallocz(strlen(header) + data_len); memcpy(data, header, strlen(header)); av_base64_encode(data + strlen(header), data_len, art->data, art->size); av_free(art->data); art->data = data; art->size = data_len; } art->source_url = av_strdup(final_url); end: if (res < 0) crip_free_art(art); return ret; } static int demux_image(cyanrip_ctx *ctx, CRIPArt *art, int info_only) { int ret = 0; AVFormatContext *avf = NULL; const char *in_url = art->data ? (const char *)art->data : art->source_url; ret = avformat_open_input(&avf, in_url, NULL, NULL); if (ret < 0) { cyanrip_log(ctx, 0, "Unable to open \"%s\": %s!\n", art->data ? "(data)" : in_url, av_err2str(ret)); goto end; } ret = avformat_find_stream_info(avf, NULL); if (ret < 0) { cyanrip_log(ctx, 0, "Unable to get cover image info: %s!\n", av_err2str(ret)); goto end; } art->params = av_calloc(1, sizeof(AVCodecParameters)); if (!art->params) goto end; memcpy(art->params, avf->streams[0]->codecpar, sizeof(AVCodecParameters)); /* Output extension guesswork */ av_freep(&art->extension); if (art->params->codec_id == AV_CODEC_ID_MJPEG) { art->extension = av_strdup("jpg"); } else if (art->params->codec_id == AV_CODEC_ID_PNG) { art->extension = av_strdup("png"); } else if (art->params->codec_id == AV_CODEC_ID_BMP) { art->extension = av_strdup("bmp"); } else if (art->params->codec_id == AV_CODEC_ID_TIFF) { art->extension = av_strdup("tiff"); } else if (art->params->codec_id == AV_CODEC_ID_AV1) { art->extension = av_strdup("avif"); } else if (art->params->codec_id == AV_CODEC_ID_HEVC) { art->extension = av_strdup("heif"); } else if (art->params->codec_id == AV_CODEC_ID_WEBP) { art->extension = av_strdup("webp"); } else if (art->params->codec_id != AV_CODEC_ID_NONE) { art->extension = av_strdup(avcodec_get_name(art->params->codec_id)); } else { ret = AVERROR(EINVAL); cyanrip_log(ctx, 0, "Error demuxing cover image: %s!\n", av_err2str(ret)); goto end; } if (info_only) goto end; art->pkt = av_packet_alloc(); if (!art->pkt) { ret = AVERROR(ENOMEM); goto end; } ret = av_read_frame(avf, art->pkt); if (ret < 0) { cyanrip_log(ctx, 0, "Error demuxing cover image: %s!\n", av_err2str(ret)); goto end; } avformat_close_input(&avf); av_freep(&art->data); art->size = 0; return 0; end: avformat_close_input(&avf); return ret; } static int string_is_url(const char *src) { return !strncmp(src, "http://", 4) || !strncmp(src, "https://", 4) || !strncmp(src, "ftp://", 4) || !strncmp(src, "ftps://", 4) || !strncmp(src, "sftp://", 4) || !strncmp(src, "tftp://", 4) || !strncmp(src, "gopher://", 4) || !strncmp(src, "telnet://", 4); } int crip_fill_coverart(cyanrip_ctx *ctx, int info_only) { int err = 0; int have_front = 0; int have_back = 0; for (int i = 0; i < ctx->nb_cover_arts; i++) { const char *title = dict_get(ctx->cover_arts[i].meta, "title"); have_front |= !strcmp(title, "Front"); have_back |= !strcmp(title, "Back"); } CURL *curl_ctx = curl_easy_init(); if (!have_front || !have_back) { const char *release_id = dict_get(ctx->meta, "release_id"); if (!release_id && !ctx->settings.disable_coverart_db) { cyanrip_log(ctx, 0, "Release ID unavailable, cannot search Cover Art DB!\n"); } else if (!ctx->settings.disable_coverart_db) { int has_err = 0; if (!have_front) { has_err = fetch_image(ctx, curl_ctx, &ctx->cover_arts[ctx->nb_cover_arts], release_id, "front", info_only, NULL); if (has_err > 0) { av_dict_set(&ctx->cover_arts[ctx->nb_cover_arts].meta, "title", "Front", 0); av_dict_set(&ctx->cover_arts[ctx->nb_cover_arts].meta, "source", "Cover Art DB", 0); ctx->cover_arts[ctx->nb_cover_arts].extension = av_strdup("jpg"); ctx->nb_cover_arts++; } } if (!have_back && (has_err > 0)) { has_err = fetch_image(ctx, curl_ctx, &ctx->cover_arts[ctx->nb_cover_arts], release_id, "back", info_only, NULL); if (has_err > 0) { av_dict_set(&ctx->cover_arts[ctx->nb_cover_arts].meta, "title", "Back", 0); av_dict_set(&ctx->cover_arts[ctx->nb_cover_arts].meta, "source", "Cover Art DB", 0); ctx->cover_arts[ctx->nb_cover_arts].extension = av_strdup("jpg"); ctx->nb_cover_arts++; } } } } for (int i = 0; i < ctx->nb_cover_arts; i++) { char *source_url = ctx->cover_arts[i].source_url; const char *title = dict_get(ctx->cover_arts[i].meta, "title"); int is_url = string_is_url(source_url); /* If its a url, we haven't downloaded it from CADB and we're not info printing */ if (is_url && !ctx->cover_arts[i].data && !info_only) { err = fetch_image(ctx, curl_ctx, &ctx->cover_arts[i], NULL, title, 0, source_url); if (err < 0) goto end; } /* If we don't have to download it, or we have data */ if (!is_url || !info_only) { if ((err = demux_image(ctx, &ctx->cover_arts[i], info_only)) < 0) goto end; } } end: curl_easy_cleanup(curl_ctx); return err; } int crip_fill_track_coverart(cyanrip_ctx *ctx, int info_only) { int err = 0; CURL *curl_ctx = curl_easy_init(); for (int i = 0; i < ctx->nb_tracks; i++) { if (!ctx->tracks[i].art.source_url) continue; char *source_url = ctx->tracks[i].art.source_url; int is_url = string_is_url(source_url); /* If its a url, we haven't downloaded it from CADB and we're not info printing */ if (is_url && !ctx->tracks[i].art.data && !info_only) { err = fetch_image(ctx, curl_ctx, &ctx->tracks[i].art, NULL, "track", 0, source_url); if (err < 0) goto end; } /* If we don't have to download it, or we have data */ if (!is_url || !info_only) { if ((err = demux_image(ctx, &ctx->tracks[i].art, info_only)) < 0) goto end; } } end: curl_easy_cleanup(curl_ctx); return err; } cyanrip-0.9.2/src/coverart.h000066400000000000000000000020041452633423300157550ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "cyanrip_main.h" int crip_fill_coverart(cyanrip_ctx *ctx, int info_only); int crip_fill_track_coverart(cyanrip_ctx *ctx, int info_only); int crip_save_art(cyanrip_ctx *ctx, CRIPArt *art, const cyanrip_out_fmt *fmt); void crip_free_art(CRIPArt *art); cyanrip-0.9.2/src/cue_writer.c000066400000000000000000000140621452633423300163020ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "cyanrip_log.h" #include "cue_writer.h" int cyanrip_cue_init(cyanrip_ctx *ctx) { for (int i = 0; i < ctx->settings.outputs_num; i++) { char *cuefile = crip_get_path(ctx, CRIP_PATH_CUE, 1, &crip_fmt_info[ctx->settings.outputs[i]], NULL); ctx->cuefile[i] = av_fopen_utf8(cuefile, "w"); if (!ctx->cuefile[i]) { cyanrip_log(ctx, 0, "Couldn't open path \"%s\" for writing: %s!\n" "Invalid folder name? Try -D .\n", cuefile, av_err2str(AVERROR(errno))); av_freep(&cuefile); return 1; } av_freep(&cuefile); } return 0; } #define CLOG(FORMAT, DICT, TAG) \ do { \ if (dict_get(DICT, TAG)) { \ for (int Z = 0; Z < ctx->settings.outputs_num; Z++) \ fprintf(ctx->cuefile[Z], FORMAT, dict_get(DICT, TAG)); \ } \ } while (0) void cyanrip_cue_start(cyanrip_ctx *ctx) { CLOG("REM MUSICBRAINZ_ID \"%s\"\n", ctx->meta, "discid"); CLOG("REM DISCID \"%s\"\n", ctx->meta, "cddb"); const AVDictionaryEntry *d = NULL; while ((d = av_dict_get(ctx->meta, "", d, AV_DICT_IGNORE_SUFFIX))) { if (strcmp(d->key, "discid") && strcmp(d->key, "cddb") && strcmp(d->key, "disc_mcn") && strcmp(d->key, "album_artist") && strcmp(d->key, "album") && strcmp(d->key, "title")) { for (int Z = 0; Z < ctx->settings.outputs_num; Z++) { char *tmp = av_strdup(d->key); for (int i = 0; tmp[i]; i++) tmp[i] = av_toupper(tmp[i]); fprintf(ctx->cuefile[Z], "REM %s \"%s\"\n", tmp, d->value); av_free(tmp); } } } CLOG("MCN \"%s\"\n", ctx->meta, "disc_mcn"); CLOG("PERFORMER \"%s\"\n", ctx->meta, "album_artist"); CLOG("TITLE \"%s\"\n", ctx->meta, "album"); } void cyanrip_cue_track(cyanrip_ctx *ctx, cyanrip_track *t) { char time_00[16]; char time_01[16]; /* Finish over the pregap which has been appended to the last track */ if (t->pregap_lsn != CDIO_INVALID_LSN && t->pt && t->dropped_pregap_start == CDIO_INVALID_LSN && t->merged_pregap_end == CDIO_INVALID_LSN) { for (int Z = 0; Z < ctx->settings.outputs_num; Z++) fprintf(ctx->cuefile[Z], " TRACK %02d AUDIO\n", t->index); CLOG(" TITLE \"%s\"\n", t->meta, "title"); CLOG(" PERFORMER \"%s\"\n", t->meta, "artist"); cyanrip_frames_to_cue(t->pregap_lsn - t->pt->start_lsn, time_00); for (int Z = 0; Z < ctx->settings.outputs_num; Z++) fprintf(ctx->cuefile[Z], " INDEX 00 %s\n", time_00); } for (int Z = 0; Z < ctx->settings.outputs_num; Z++) { char *path = crip_get_path(ctx, t->track_is_data ? CRIP_PATH_DATA : CRIP_PATH_TRACK, 0, &crip_fmt_info[ctx->settings.outputs[Z]], t); char *name = path; for (int i = 0; name[i]; i++) { if (name[i] == '/') { name = &name[i + 1]; break; } } fprintf(ctx->cuefile[Z], "FILE \"%s\" %s\n", name, ctx->settings.outputs[Z] == CYANRIP_FORMAT_MP3 ? "MP3" : t->track_is_data ? "BINARY" : "WAVE"); fprintf(ctx->cuefile[Z], " TRACK %02d %s\n", t->number, t->track_is_data ? "MODE1/2352" : "AUDIO"); av_free(path); } if (!t->track_is_data) { CLOG(" TITLE \"%s\"\n", t->meta, "title"); CLOG(" PERFORMER \"%s\"\n", t->meta, "artist"); CLOG(" ISRC %s\n", t->meta, "isrc"); } if (t->dropped_pregap_start != CDIO_INVALID_LSN) { cyanrip_frames_to_cue(t->start_lsn - t->dropped_pregap_start, time_00); cyanrip_frames_to_cue(0, time_01); } else if (t->merged_pregap_end != CDIO_INVALID_LSN) { cyanrip_frames_to_cue(0, time_00); cyanrip_frames_to_cue(t->merged_pregap_end - t->start_lsn, time_01); } else { cyanrip_frames_to_cue(0, time_01); } for (int Z = 0; Z < ctx->settings.outputs_num; Z++) { if (t->preemphasis && !ctx->settings.deemphasis && !ctx->settings.force_deemphasis) fprintf(ctx->cuefile[Z], " FLAGS PRE\n"); if (t->dropped_pregap_start != CDIO_INVALID_LSN) { fprintf(ctx->cuefile[Z], " PREGAP %s\n", time_00); fprintf(ctx->cuefile[Z], " INDEX 01 %s\n", time_01); } else if (t->merged_pregap_end != CDIO_INVALID_LSN) { fprintf(ctx->cuefile[Z], " INDEX 00 %s\n", time_00); fprintf(ctx->cuefile[Z], " INDEX 01 %s\n", time_01); } else { fprintf(ctx->cuefile[Z], " INDEX 01 %s\n", time_01); } } } void cyanrip_cue_end(cyanrip_ctx *ctx) { for (int i = 0; i < ctx->settings.outputs_num; i++) { if (!ctx->cuefile[i]) continue; fclose(ctx->cuefile[i]); ctx->cuefile[i] = NULL; } } cyanrip-0.9.2/src/cue_writer.h000066400000000000000000000017371452633423300163140ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include "cyanrip_main.h" int cyanrip_cue_init(cyanrip_ctx *ctx); void cyanrip_cue_start(cyanrip_ctx *ctx); void cyanrip_cue_track(cyanrip_ctx *ctx, cyanrip_track *t); void cyanrip_cue_end(cyanrip_ctx *ctx); cyanrip-0.9.2/src/cyanrip_encode.c000066400000000000000000001047041452633423300171170ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include "fifo_frame.h" #include "fifo_packet.h" #include "cyanrip_encode.h" #include "cyanrip_log.h" #include "os_compat.h" #if CONFIG_BIG_ENDIAN #define AV_CODEC_ID_PCM_S16 AV_CODEC_ID_PCM_S16BE #define AV_CODEC_ID_PCM_S32 AV_CODEC_ID_PCM_S32BE #define AV_CODEC_ID_PCM_F64 AV_CODEC_ID_PCM_F64BE #else #define AV_CODEC_ID_PCM_S16 AV_CODEC_ID_PCM_S16LE #define AV_CODEC_ID_PCM_S32 AV_CODEC_ID_PCM_S32LE #define AV_CODEC_ID_PCM_F64 AV_CODEC_ID_PCM_F64LE #endif struct cyanrip_enc_ctx { cyanrip_ctx *ctx; AVBufferRef *fifo; pthread_t thread; AVFormatContext *avf; SwrContext *swr; AVCodecContext *out_avctx; atomic_int status; atomic_int quit; int audio_stream_index; cyanrip_track *t; int mutex_held; pthread_mutex_t lock; AVStream *st_aud; AVStream *st_img; const cyanrip_out_fmt *cfmt; int separate_writeout; AVBufferRef *packet_fifo; AVPacket *cover_art_pkt; }; typedef struct cyanrip_filt_ctx { /* Deemphasis, and HDCD decoding (not at once) */ AVFilterGraph *graph; AVFilterContext *buffersink_ctx; AVFilterContext *buffersrc_ctx; } cyanrip_filt_ctx; struct cyanrip_dec_ctx { cyanrip_filt_ctx filt; cyanrip_filt_ctx peak; }; void cyanrip_print_codecs(void) { for (int i = 0; i < CYANRIP_FORMATS_NB; i++) { const cyanrip_out_fmt *cfmt = &crip_fmt_info[i]; int is_supported = 0; if (cfmt->codec == AV_CODEC_ID_NONE) { is_supported = avcodec_find_encoder(AV_CODEC_ID_PCM_S16) && avcodec_find_encoder(AV_CODEC_ID_PCM_S32) && avcodec_find_encoder(AV_CODEC_ID_PCM_F64); } else { is_supported = !!avcodec_find_encoder(cfmt->codec); } if (is_supported) { const char *str = cfmt->coverart_supported ? "\t(supports cover art)" : ""; cyanrip_log(NULL, 0, "\t%s\tfolder: [%s]\textension: %s%s\n", cfmt->name, cfmt->folder_suffix, cfmt->ext, str); } } } int cyanrip_validate_fmt(const char *fmt) { for (int i = 0; i < CYANRIP_FORMATS_NB; i++) { const cyanrip_out_fmt *cfmt = &crip_fmt_info[i]; if ((!strncasecmp(fmt, cfmt->name, strlen(fmt))) && (strlen(fmt) == strlen(cfmt->name))) { if (cfmt->codec == AV_CODEC_ID_NONE) { if (avcodec_find_encoder(AV_CODEC_ID_PCM_S16) && avcodec_find_encoder(AV_CODEC_ID_PCM_S32) && avcodec_find_encoder(AV_CODEC_ID_PCM_F64)) return i; } else if (avcodec_find_encoder(cfmt->codec)) { return i; } else { cyanrip_log(NULL, 0, "Encoder for %s not compiled in ffmpeg!\n", cfmt->name); return -1; } } } return -1; } const char *cyanrip_fmt_desc(enum cyanrip_output_formats format) { return format < CYANRIP_FORMATS_NB ? crip_fmt_info[format].name : NULL; } const char *cyanrip_fmt_folder(enum cyanrip_output_formats format) { return format < CYANRIP_FORMATS_NB ? crip_fmt_info[format].folder_suffix : NULL; } static AVChannelLayout pick_codec_channel_layout(const AVCodec *codec) { int i = 0; int max_channels = 0; AVChannelLayout ilayout = AV_CHANNEL_LAYOUT_STEREO; int in_channels = ilayout.nb_channels; AVChannelLayout best_layout = { 0 }; /* Supports anything */ if (!codec->ch_layouts) return ilayout; /* Try to match */ while (1) { if (!codec->ch_layouts[i].nb_channels) break; if (!av_channel_layout_compare(&codec->ch_layouts[i], &ilayout)) return codec->ch_layouts[i]; i++; } i = 0; /* Try to match channel counts */ while (1) { if (!codec->ch_layouts[i].nb_channels) break; int num = codec->ch_layouts[i].nb_channels; if (num > max_channels) { max_channels = num; best_layout = codec->ch_layouts[i]; } if (num >= in_channels) return codec->ch_layouts[i]; i++; } /* Whatever */ return best_layout; } static enum AVSampleFormat pick_codec_sample_fmt(const AVCodec *codec, int hdcd) { int i = 0; int max_bps = 0; int ibps = hdcd ? 20 : 16; enum AVSampleFormat ifmt = hdcd ? AV_SAMPLE_FMT_S32 : AV_SAMPLE_FMT_S16; enum AVSampleFormat ifmt_p = hdcd ? AV_SAMPLE_FMT_S32P : AV_SAMPLE_FMT_S16P; enum AVSampleFormat max_bps_fmt = AV_SAMPLE_FMT_NONE; ibps = ibps >> 3; /* Accepts anything */ if (!codec->sample_fmts) return ifmt; /* Try to match the input sample format first */ while (1) { if (codec->sample_fmts[i] == AV_SAMPLE_FMT_NONE) break; if (codec->sample_fmts[i] == ifmt || codec->sample_fmts[i] == ifmt_p) return codec->sample_fmts[i]; i++; } i = 0; /* Try to match bits per sample */ while (1) { if (codec->sample_fmts[i] == -1) break; int bps = av_get_bytes_per_sample(codec->sample_fmts[i]); if (bps > max_bps) { max_bps = bps; max_bps_fmt = codec->sample_fmts[i]; } if (bps == ibps) return codec->sample_fmts[i]; i++; } /* Return the best one */ return max_bps_fmt; } static int pick_codec_sample_rate(const AVCodec *codec) { int i = 0, ret; int irate = 44100; if (!codec->supported_samplerates) return irate; /* Go to the array terminator (0) */ while (codec->supported_samplerates[++i] > 0); /* Alloc, copy and sort array upwards */ int *tmp = av_malloc(i*sizeof(int)); memcpy(tmp, codec->supported_samplerates, i*sizeof(int)); qsort(tmp, i, sizeof(int), cmp_numbers); /* Pick lowest one above the input rate, otherwise just use the highest one */ for (int j = 0; j < i; j++) { ret = tmp[j]; if (ret >= irate) break; } av_free(tmp); return ret; } static AVCodecContext *setup_out_avctx(cyanrip_ctx *ctx, AVFormatContext *avf, const AVCodec *codec, const cyanrip_out_fmt *cfmt, int decode_hdcd, int deemphasis) { AVCodecContext *avctx = avcodec_alloc_context3(codec); if (!avctx) return NULL; avctx->opaque = ctx; avctx->bit_rate = cfmt->lossless ? 0 : lrintf(ctx->settings.bitrate*1000.0f); avctx->sample_fmt = pick_codec_sample_fmt(codec, decode_hdcd); avctx->ch_layout = pick_codec_channel_layout(codec); avctx->compression_level = cfmt->compression_level; avctx->sample_rate = pick_codec_sample_rate(codec); avctx->time_base = (AVRational){ 1, avctx->sample_rate }; avctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; if (cfmt->lossless && decode_hdcd) avctx->bits_per_raw_sample = FFMIN(24, av_get_bytes_per_sample(avctx->sample_fmt)*8); else if (cfmt->lossless) avctx->bits_per_raw_sample = 16; if (avf->oformat->flags & AVFMT_GLOBALHEADER) avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; return avctx; } static void cyanrip_free_filt_ctx(cyanrip_ctx *ctx, cyanrip_filt_ctx *filt_ctx, int capture) { if (filt_ctx->graph) { if (capture) cyanrip_set_av_log_capture(ctx, 1, AV_LOG_INFO); avfilter_graph_free(&filt_ctx->graph); if (capture) cyanrip_set_av_log_capture(ctx, 0, 0); } } void cyanrip_free_dec_ctx(cyanrip_ctx *ctx, cyanrip_dec_ctx **s) { if (!s || !*s) return; cyanrip_dec_ctx *dec_ctx = *s; cyanrip_free_filt_ctx(ctx, &dec_ctx->filt, 0); cyanrip_free_filt_ctx(ctx, &dec_ctx->peak, 0); av_freep(s); } static int init_filtering(cyanrip_ctx *ctx, cyanrip_filt_ctx *s, int hdcd, int deemphasis, int peak) { int ret = 0; AVFilterInOut *inputs = NULL; AVFilterInOut *outputs = NULL; s->graph = avfilter_graph_alloc(); if (!s->graph) return AVERROR(ENOMEM); const AVFilter *abuffersrc = avfilter_get_by_name("abuffer"); char args[512]; uint64_t layout = AV_CH_LAYOUT_STEREO; snprintf(args, sizeof(args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, 1, 44100, 44100, av_get_sample_fmt_name(AV_SAMPLE_FMT_S16), layout); ret = avfilter_graph_create_filter(&s->buffersrc_ctx, abuffersrc, "in", args, NULL, s->graph); if (ret < 0) { cyanrip_log(ctx, 0, "Error creating filter source: %s!\n", av_err2str(ret)); goto fail; } if (!peak) { const AVFilter *abuffersink = avfilter_get_by_name("abuffersink"); ret = avfilter_graph_create_filter(&s->buffersink_ctx, abuffersink, "out", NULL, NULL, s->graph); if (ret < 0) { cyanrip_log(ctx, 0, "Error creating filter sink: %s!\n", av_err2str(ret)); goto fail; } static const enum AVSampleFormat out_sample_fmts_hdcd[] = { AV_SAMPLE_FMT_S32, -1 }; static const enum AVSampleFormat out_sample_fmts_deemph[] = { AV_SAMPLE_FMT_DBLP, -1 }; ret = av_opt_set_int_list(s->buffersink_ctx, "sample_fmts", hdcd ? out_sample_fmts_hdcd : deemphasis ? out_sample_fmts_deemph : NULL, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { cyanrip_log(ctx, 0, "Error setting filter sample format: %s!\n", av_err2str(ret)); goto fail; } static const int64_t out_channel_layouts[] = { AV_CH_LAYOUT_STEREO, -1 }; ret = av_opt_set_int_list(s->buffersink_ctx, "channel_layouts", out_channel_layouts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { cyanrip_log(ctx, 0, "Error setting filter channel layout: %s!\n", av_err2str(ret)); goto fail; } static const int out_sample_rates[] = { 44100, -1 }; ret = av_opt_set_int_list(s->buffersink_ctx, "sample_rates", out_sample_rates, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { cyanrip_log(ctx, 0, "Error setting filter sample rate: %s!\n", av_err2str(ret)); goto fail; } } outputs = avfilter_inout_alloc(); if (!outputs) { ret = AVERROR(ENOMEM); goto fail; } outputs->name = av_strdup("in"); outputs->filter_ctx = s->buffersrc_ctx; outputs->pad_idx = 0; outputs->next = NULL; if (!peak) { inputs = avfilter_inout_alloc(); if (!inputs) { ret = AVERROR(ENOMEM); goto fail; } inputs->name = av_strdup("out"); inputs->filter_ctx = s->buffersink_ctx; inputs->pad_idx = 0; inputs->next = NULL; } const char *filter_desc = hdcd ? "hdcd" : deemphasis ? "aemphasis=type=cd" : peak ? "ebur128=peak=true,anullsink" : NULL; ret = avfilter_graph_parse_ptr(s->graph, filter_desc, &inputs, &outputs, NULL); if (ret < 0) { cyanrip_log(ctx, 0, "Error parsing filter graph: %s!\n", av_err2str(ret)); goto fail; } ret = avfilter_graph_config(s->graph, NULL); if (ret < 0) { cyanrip_log(ctx, 0, "Error configuring filter graph: %s!\n", av_err2str(ret)); goto fail; } return 0; fail: avfilter_inout_free(&inputs); avfilter_inout_free(&outputs); return ret; } int cyanrip_create_dec_ctx(cyanrip_ctx *ctx, cyanrip_dec_ctx **s, cyanrip_track *t) { int ret = 0; cyanrip_dec_ctx *dec_ctx = av_mallocz(sizeof(*dec_ctx)); if (!dec_ctx) return AVERROR(ENOMEM); if ((ctx->settings.decode_hdcd) || (ctx->settings.deemphasis && t->preemphasis) || (ctx->settings.force_deemphasis)) { ret = init_filtering(ctx, &dec_ctx->filt, ctx->settings.decode_hdcd, (ctx->settings.deemphasis && t->preemphasis) || ctx->settings.force_deemphasis, 0); if (ret < 0) goto fail; } ret = init_filtering(ctx, &dec_ctx->peak, 0, 0, 1); if (ret < 0) goto fail; *s = dec_ctx; return 0; fail: cyanrip_free_dec_ctx(ctx, &dec_ctx); return ret; } static int push_frame_to_encs(cyanrip_ctx *ctx, cyanrip_enc_ctx **enc_ctx, int num_enc, AVFrame *frame) { int ret; for (int i = 0; i < num_enc; i++) { int status = atomic_load(&enc_ctx[i]->status); if (status < 0) return status; ret = cr_frame_fifo_push(enc_ctx[i]->fifo, frame); if (ret < 0) { cyanrip_log(ctx, 0, "Error pushing frame to FIFO: %s!\n", av_err2str(ret)); return ret; } } return 0; } static int filter_frame(cyanrip_ctx *ctx, cyanrip_enc_ctx **enc_ctx, int num_enc, cyanrip_dec_ctx *dec_ctx, AVFrame *frame, int calc_global_peak) { int ret = 0; AVFrame *dec_frame = NULL; ret = av_buffersrc_add_frame_flags(dec_ctx->peak.buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT | AV_BUFFERSRC_FLAG_KEEP_REF | AV_BUFFERSRC_FLAG_PUSH); if (ret < 0) { cyanrip_log(ctx, 0, "Error filtering frame: %s!\n", av_err2str(ret)); goto fail; } if (frame && calc_global_peak) { ret = av_buffersrc_add_frame_flags(ctx->peak_ctx->peak.buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT | AV_BUFFERSRC_FLAG_KEEP_REF | AV_BUFFERSRC_FLAG_PUSH); if (ret < 0) { cyanrip_log(ctx, 0, "Error filtering frame: %s!\n", av_err2str(ret)); goto fail; } } if (!dec_ctx->filt.buffersrc_ctx) return push_frame_to_encs(ctx, enc_ctx, num_enc, frame); ret = av_buffersrc_add_frame_flags(dec_ctx->filt.buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT | AV_BUFFERSRC_FLAG_KEEP_REF); if (ret < 0) { cyanrip_log(ctx, 0, "Error filtering frame: %s!\n", av_err2str(ret)); goto fail; } while (1) { dec_frame = av_frame_alloc(); if (!dec_frame) { ret = AVERROR(ENOMEM); goto fail; } ret = av_buffersink_get_frame_flags(dec_ctx->filt.buffersink_ctx, dec_frame, AV_BUFFERSINK_FLAG_NO_REQUEST); if (ret == AVERROR(EAGAIN)) { av_frame_free(&dec_frame); ret = 0; break; } else if (ret == AVERROR_EOF) { av_frame_free(&dec_frame); ret = 0; return push_frame_to_encs(ctx, enc_ctx, num_enc, NULL); } else if (ret < 0) { cyanrip_log(ctx, 0, "Error filtering frame: %s!\n", av_err2str(ret)); goto fail; } push_frame_to_encs(ctx, enc_ctx, num_enc, dec_frame); av_frame_free(&dec_frame); } fail: return ret; } int cyanrip_send_pcm_to_encoders(cyanrip_ctx *ctx, cyanrip_enc_ctx **enc_ctx, int num_enc, cyanrip_dec_ctx *dec_ctx, const uint8_t *data, int bytes, int calc_global_peak) { int ret = 0; AVFrame *frame = NULL; if (!data && !bytes) goto send; else if (!bytes) return 0; frame = av_frame_alloc(); if (!frame) { cyanrip_log(ctx, 0, "Error allocating frame!\n"); ret = AVERROR(ENOMEM); goto fail; } frame->sample_rate = 44100; frame->nb_samples = bytes >> 2; frame->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO; frame->format = AV_SAMPLE_FMT_S16; ret = av_frame_get_buffer(frame, 0); if (ret < 0) { cyanrip_log(ctx, 0, "Error allocating frame: %s!\n", av_err2str(ret)); goto fail; } memcpy(frame->data[0], data, bytes); send: ret = filter_frame(ctx, enc_ctx, num_enc, dec_ctx, frame, calc_global_peak); fail: av_frame_free(&frame); return ret; } int cyanrip_reset_encoding(cyanrip_ctx *ctx, cyanrip_track *t) { for (int i = 0; i < ctx->settings.outputs_num; i++) { cyanrip_enc_ctx *s = t->enc_ctx[i]; atomic_store(&s->quit, 1); pthread_mutex_unlock(&s->lock); s->mutex_held = 0; } for (int i = 0; i < ctx->settings.outputs_num; i++) cyanrip_end_track_encoding(&t->enc_ctx[i]); for (int i = 0; i < ctx->settings.outputs_num; i++) cyanrip_init_track_encoding(ctx, &t->enc_ctx[i], t, ctx->settings.outputs[i]); return 0; } int cyanrip_finalize_encoding(cyanrip_ctx *ctx, cyanrip_track *t) { AVFilterContext *filt_ctx = t->dec_ctx->peak.graph->filters[1]; av_opt_get_double(filt_ctx, "integrated", AV_OPT_SEARCH_CHILDREN, &t->ebu_integrated); av_opt_get_double(filt_ctx, "range", AV_OPT_SEARCH_CHILDREN, &t->ebu_range); av_opt_get_double(filt_ctx, "lra_low", AV_OPT_SEARCH_CHILDREN, &t->ebu_lra_low); av_opt_get_double(filt_ctx, "lra_high", AV_OPT_SEARCH_CHILDREN, &t->ebu_lra_high); av_opt_get_double(filt_ctx, "sample_peak", AV_OPT_SEARCH_CHILDREN, &t->ebu_sample_peak); av_opt_get_double(filt_ctx, "true_peak", AV_OPT_SEARCH_CHILDREN, &t->ebu_true_peak); cyanrip_free_filt_ctx(ctx, &t->dec_ctx->peak, 1); if (ctx->settings.decode_hdcd) cyanrip_log(ctx, 0, "\n "); else cyanrip_log(ctx, 0, "\n"); cyanrip_free_filt_ctx(ctx, &t->dec_ctx->filt, ctx->settings.decode_hdcd); return 0; } int cyanrip_initialize_ebur128(cyanrip_ctx *ctx) { int ret = 0; cyanrip_dec_ctx *dec_ctx = av_mallocz(sizeof(*dec_ctx)); if (!dec_ctx) return AVERROR(ENOMEM); ctx->peak_ctx = dec_ctx; ret = init_filtering(ctx, &dec_ctx->peak, 0, 0, 1); if (ret < 0) goto fail; return 0; fail: cyanrip_finalize_ebur128(ctx, 0); return ret; } int cyanrip_finalize_ebur128(cyanrip_ctx *ctx, int log) { cyanrip_dec_ctx *dec_ctx = ctx->peak_ctx; if (!log) goto end; AVFilterContext *filt_ctx = dec_ctx->peak.graph->filters[1]; int ret = av_buffersrc_add_frame_flags(dec_ctx->peak.buffersrc_ctx, NULL, AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT | AV_BUFFERSRC_FLAG_KEEP_REF | AV_BUFFERSRC_FLAG_PUSH); if (ret < 0) { cyanrip_log(ctx, 0, "Error filtering frame: %s!\n", av_err2str(ret)); cyanrip_free_filt_ctx(ctx, &dec_ctx->peak, 0); av_freep(ctx->peak_ctx); return ret; } av_opt_get_double(filt_ctx, "integrated", AV_OPT_SEARCH_CHILDREN, &ctx->ebu_integrated); av_opt_get_double(filt_ctx, "range", AV_OPT_SEARCH_CHILDREN, &ctx->ebu_range); av_opt_get_double(filt_ctx, "lra_low", AV_OPT_SEARCH_CHILDREN, &ctx->ebu_lra_low); av_opt_get_double(filt_ctx, "lra_high", AV_OPT_SEARCH_CHILDREN, &ctx->ebu_lra_high); av_opt_get_double(filt_ctx, "sample_peak", AV_OPT_SEARCH_CHILDREN, &ctx->ebu_sample_peak); av_opt_get_double(filt_ctx, "true_peak", AV_OPT_SEARCH_CHILDREN, &ctx->ebu_true_peak); cyanrip_log(ctx, 0, "Album Loudness "); end: if (ctx->peak_ctx) { cyanrip_free_filt_ctx(ctx, &ctx->peak_ctx->peak, log); if (log) cyanrip_log(ctx, 0, "\n"); } av_freep(&ctx->peak_ctx); return 0; } static SwrContext *setup_init_swr(cyanrip_ctx *ctx, AVCodecContext *out_avctx, int hdcd, int deemphasis) { SwrContext *swr = swr_alloc(); if (!swr) { cyanrip_log(ctx, 0, "Could not alloc swr context!\n"); return NULL; } AVChannelLayout ichl = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO; enum AVSampleFormat in_sample_fmt = hdcd ? AV_SAMPLE_FMT_S32 : (deemphasis ? AV_SAMPLE_FMT_DBLP : AV_SAMPLE_FMT_S16); av_opt_set_int (swr, "in_sample_rate", 44100, 0); av_opt_set_chlayout (swr, "in_chlayout", &ichl, 0); av_opt_set_sample_fmt(swr, "in_sample_fmt", in_sample_fmt, 0); av_opt_set_int (swr, "out_sample_rate", out_avctx->sample_rate, 0); av_opt_set_chlayout (swr, "out_chlayout", &out_avctx->ch_layout, 0); av_opt_set_sample_fmt(swr, "out_sample_fmt", out_avctx->sample_fmt, 0); if (swr_init(swr) < 0) { cyanrip_log(ctx, 0, "Could not init swr context!\n"); swr_free(&swr); return NULL; } return swr; } static int64_t get_next_audio_pts(cyanrip_enc_ctx *ctx, AVFrame *in) { const int64_t m = (int64_t)44100 * ctx->out_avctx->sample_rate; const int64_t b = (int64_t)ctx->out_avctx->time_base.num * m; const int64_t c = ctx->out_avctx->time_base.den; const int64_t in_pts = in ? in->pts : AV_NOPTS_VALUE; int64_t npts = in_pts == AV_NOPTS_VALUE ? AV_NOPTS_VALUE : av_rescale(in_pts, b, c); npts = swr_next_pts(ctx->swr, npts); int64_t out_pts = av_rescale(npts, c, b); return out_pts; } static int audio_process_frame(cyanrip_enc_ctx *ctx, AVFrame **input, int flush) { int ret; int frame_size = ctx->out_avctx->frame_size; int64_t resampled_frame_pts = get_next_audio_pts(ctx, *input); /* Resample the frame, can be NULL */ ret = swr_convert_frame(ctx->swr, NULL, *input); av_frame_free(input); if (ret < 0) { av_log(ctx, AV_LOG_ERROR, "Error pushing audio for resampling: %s!\n", av_err2str(ret)); return ret; } /* Not enough output samples to get a frame */ if (!flush) { int out_samples = swr_get_out_samples(ctx->swr, 0); if (!frame_size && out_samples) frame_size = out_samples; else if (!out_samples || ((frame_size) && (out_samples < frame_size))) return AVERROR(EAGAIN); } else if (!frame_size && ctx->swr) { frame_size = swr_get_out_samples(ctx->swr, 0); if (!frame_size) return 0; } if (flush && !ctx->swr) return 0; AVFrame *out_frame = NULL; if (!(out_frame = av_frame_alloc())) { av_log(ctx, AV_LOG_ERROR, "Error allocating frame!\n"); return AVERROR(ENOMEM); } out_frame->format = ctx->out_avctx->sample_fmt; out_frame->ch_layout = ctx->out_avctx->ch_layout; out_frame->sample_rate = ctx->out_avctx->sample_rate; out_frame->pts = resampled_frame_pts; /* SWR sets this field to whatever it can output if it can't this much */ out_frame->nb_samples = frame_size; /* Get frame buffer */ ret = av_frame_get_buffer(out_frame, 0); if (ret < 0) { av_log(ctx, AV_LOG_ERROR, "Error allocating frame: %s!\n", av_err2str(ret)); av_frame_free(&out_frame); return ret; } /* Resample */ ret = swr_convert_frame(ctx->swr, out_frame, NULL); if (ret < 0) { av_log(ctx, AV_LOG_ERROR, "Error pulling resampled audio: %s!\n", av_err2str(ret)); av_frame_free(&out_frame); return ret; } /* swr has been drained */ if (!out_frame->nb_samples) { av_frame_free(&out_frame); swr_free(&ctx->swr); } *input = out_frame; return 0; } int cyanrip_end_track_encoding(cyanrip_enc_ctx **s) { cyanrip_enc_ctx *ctx; if (!s || !*s) return 0; ctx = *s; atomic_store(&ctx->quit, 1); if (ctx->mutex_held) { pthread_mutex_unlock(&ctx->lock); ctx->mutex_held = 0; } cr_frame_fifo_push(ctx->fifo, NULL); pthread_join(ctx->thread, NULL); pthread_mutex_destroy(&ctx->lock); swr_free(&ctx->swr); avcodec_close(ctx->out_avctx); if (ctx->avf) avio_closep(&ctx->avf->pb); avformat_free_context(ctx->avf); av_buffer_unref(&ctx->fifo); av_buffer_unref(&ctx->packet_fifo); av_free(ctx->out_avctx); av_packet_free(&ctx->cover_art_pkt); int status = ctx->status; av_freep(s); return status; } static int open_output(cyanrip_ctx *ctx, cyanrip_enc_ctx *s) { int ret; /* Add metadata */ av_dict_copy(&s->avf->metadata, s->t->meta, 0); /* Write header */ ret = avformat_write_header(s->avf, NULL); if (ret < 0) { cyanrip_log(ctx, 0, "Couldn't write header: %s!\n", av_err2str(ret)); goto fail; } /* Mux cover image */ if (s->cover_art_pkt) { AVPacket *pkt = av_packet_clone(s->cover_art_pkt); pkt->stream_index = s->st_img->index; if ((ret = av_interleaved_write_frame(s->avf, pkt)) < 0) { cyanrip_log(ctx, 0, "Error writing picture packet: %s!\n", av_err2str(ret)); goto fail; } av_packet_free(&pkt); } fail: return ret; } static void *cyanrip_track_encoding(void *ctx) { cyanrip_enc_ctx *s = ctx; int ret = 0, flushing = 0; /* Allocate output packet */ AVPacket *out_pkt = av_packet_alloc(); if (!out_pkt) { ret = AVERROR(ENOMEM); cyanrip_log(s->ctx, 0, "Error while encoding: %s!\n", av_err2str(ret)); goto fail; } while (1) { AVFrame *out_frame = NULL; if (!flushing) { out_frame = cr_frame_fifo_pop(s->fifo); flushing = !out_frame; } if (atomic_load(&s->quit)) goto fail; ret = audio_process_frame(ctx, &out_frame, flushing); if (ret == AVERROR(EAGAIN)) continue; else if (ret) goto fail; /* Give frame */ ret = avcodec_send_frame(s->out_avctx, out_frame); av_frame_free(&out_frame); if (ret < 0) { cyanrip_log(s->ctx, 0, "Error encoding: %s!\n", av_err2str(ret)); goto fail; } /* Return loop */ while (1) { ret = avcodec_receive_packet(s->out_avctx, out_pkt); if (ret == AVERROR_EOF) { ret = 0; goto write_trailer; } else if (ret == AVERROR(EAGAIN)) { ret = 0; break; } else if (ret < 0) { cyanrip_log(s->ctx, 0, "Error while encoding: %s!\n", av_err2str(ret)); goto fail; } int sid = s->audio_stream_index; out_pkt->stream_index = sid; AVRational src_tb = s->out_avctx->time_base; AVRational dst_tb = s->avf->streams[sid]->time_base; /* Rescale timestamps to container */ av_packet_rescale_ts(out_pkt, src_tb, dst_tb); if (s->separate_writeout) { /* Put encoded frame in FIFO */ ret = cr_packet_fifo_push(s->packet_fifo, out_pkt); if (ret < 0) { cyanrip_log(ctx, 0, "Error pushing packet to FIFO: %s!\n", av_err2str(ret)); goto fail; } } else { /* Send frame to lavf */ ret = av_interleaved_write_frame(s->avf, out_pkt); if (ret < 0) { cyanrip_log(s->ctx, 0, "Error writing packet: %s!\n", av_err2str(ret)); goto fail; } } /* Reset the packet */ av_packet_unref(out_pkt); } } write_trailer: av_packet_free(&out_pkt); if (s->separate_writeout) { ret = cr_packet_fifo_push(s->packet_fifo, NULL); if (ret < 0) { cyanrip_log(ctx, 0, "Error pushing packet to FIFO: %s!\n", av_err2str(ret)); goto fail; } if (atomic_load(&s->quit)) goto fail; pthread_mutex_lock(&s->lock); pthread_mutex_unlock(&s->lock); if (atomic_load(&s->quit)) goto fail; if ((ret = open_output(s->ctx, s)) < 0) { cyanrip_log(s->ctx, 0, "Error writing to file: %s!\n", av_err2str(ret)); goto fail; } while ((out_pkt = cr_packet_fifo_pop(s->packet_fifo))) { /* Send frames to lavf */ ret = av_interleaved_write_frame(s->avf, out_pkt); av_packet_free(&out_pkt); if (ret < 0) { cyanrip_log(s->ctx, 0, "Error writing packet: %s!\n", av_err2str(ret)); goto fail; } } } if ((ret = av_write_trailer(s->avf)) < 0) { cyanrip_log(s->ctx, 0, "Error writing trailer: %s!\n", av_err2str(ret)); goto fail; } fail: av_packet_free(&out_pkt); atomic_store(&s->status, ret); return NULL; } int cyanrip_writeout_track(cyanrip_ctx *ctx, cyanrip_enc_ctx *s) { if (s->mutex_held) { pthread_mutex_unlock(&s->lock); s->mutex_held = 0; } return 0; } int cyanrip_init_track_encoding(cyanrip_ctx *ctx, cyanrip_enc_ctx **enc_ctx, cyanrip_track *t, enum cyanrip_output_formats format) { int ret = 0; const cyanrip_out_fmt *cfmt = &crip_fmt_info[format]; cyanrip_enc_ctx *s = av_mallocz(sizeof(*s)); int deemphasis = (ctx->settings.deemphasis && t->preemphasis) || ctx->settings.force_deemphasis; const AVCodec *out_codec = NULL; s->mutex_held = 1; s->t = t; s->ctx = ctx; s->cfmt = cfmt; s->separate_writeout = ctx->settings.enable_replaygain; atomic_init(&s->status, 0); atomic_init(&s->quit, 0); char *filename = crip_get_path(ctx, CRIP_PATH_TRACK, 1, cfmt, t); /* lavf init */ ret = avformat_alloc_output_context2(&s->avf, NULL, cfmt->lavf_name, filename); if (ret < 0) { cyanrip_log(ctx, 0, "Unable to init lavf context: %s!\n", av_err2str(ret)); goto fail; } s->st_aud = avformat_new_stream(s->avf, NULL); if (!s->st_aud) { cyanrip_log(ctx, 0, "Unable to alloc stream!\n"); ret = AVERROR(ENOMEM); goto fail; } /* Cover image init */ CRIPArt *art = &t->art; if (!art->pkt) { int i; for (i = 0; i < ctx->nb_cover_arts; i++) if (!strcmp(dict_get(ctx->cover_arts[i].meta, "title"), "Front")) break; art = &ctx->cover_arts[i == ctx->nb_cover_arts ? 0 : i]; } if (art->pkt && cfmt->coverart_supported && !ctx->settings.disable_coverart_embedding) { s->st_img = avformat_new_stream(s->avf, NULL); if (!s->st_img) { cyanrip_log(ctx, 0, "Unable to alloc stream!\n"); ret = AVERROR(ENOMEM); goto fail; } memcpy(s->st_img->codecpar, art->params, sizeof(AVCodecParameters)); s->st_img->disposition |= AV_DISPOSITION_ATTACHED_PIC; s->st_img->time_base = (AVRational){ 1, 25 }; av_dict_copy(&s->st_img->metadata, art->meta, 0); s->cover_art_pkt = av_packet_clone(art->pkt); } /* Find encoder */ if (cfmt->codec == AV_CODEC_ID_NONE) out_codec = avcodec_find_encoder(ctx->settings.decode_hdcd ? AV_CODEC_ID_PCM_S32 : AV_CODEC_ID_PCM_S16); else out_codec = avcodec_find_encoder(cfmt->codec); if (!out_codec) { cyanrip_log(ctx, 0, "Codec not found (not compiled in lavc?)!\n"); ret = AVERROR(ENOMEM); goto fail; } /* Output avctx */ s->out_avctx = setup_out_avctx(ctx, s->avf, out_codec, cfmt, ctx->settings.decode_hdcd, deemphasis); if (!s->out_avctx) { cyanrip_log(ctx, 0, "Unable to init output avctx!\n"); goto fail; } /* Set primary audio stream's parameters */ s->st_aud->time_base = (AVRational){ 1, s->out_avctx->sample_rate }; s->audio_stream_index = s->st_aud->index; /* Open encoder */ ret = avcodec_open2(s->out_avctx, out_codec, NULL); if (ret < 0) { cyanrip_log(ctx, 0, "Could not open output codec context!\n"); goto fail; } /* Set codecpar */ ret = avcodec_parameters_from_context(s->st_aud->codecpar, s->out_avctx); if (ret < 0) { cyanrip_log(ctx, 0, "Couldn't copy codec params!\n"); goto fail; } /* Open for writing */ ret = avio_open(&s->avf->pb, filename, AVIO_FLAG_WRITE); if (ret < 0) { cyanrip_log(ctx, 0, "Couldn't open %s: %s! Invalid folder name? Try -D .\n", filename, av_err2str(ret)); goto fail; } /* SWR */ s->swr = setup_init_swr(ctx, s->out_avctx, ctx->settings.decode_hdcd, deemphasis); if (!s->swr) goto fail; /* FIFO */ s->fifo = cr_frame_fifo_create(-1, FRAME_FIFO_BLOCK_NO_INPUT); if (!s->fifo) goto fail; /* File write lock */ ret = pthread_mutex_init(&s->lock, NULL); if (ret != 0) { ret = AVERROR(ret); goto fail; } pthread_mutex_lock(&s->lock); s->mutex_held = 1; /* Packet fifo */ if (s->separate_writeout) { s->packet_fifo = cr_packet_fifo_create(-1, 0); if (!s->packet_fifo) goto fail; } else { ret = open_output(ctx, s); if (ret < 0) goto fail; } av_freep(&filename); pthread_create(&s->thread, NULL, cyanrip_track_encoding, s); *enc_ctx = s; return 0; fail: av_free(filename); cyanrip_end_track_encoding(&s); return ret; } cyanrip-0.9.2/src/cyanrip_encode.h000066400000000000000000000040611452633423300171170ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include "cyanrip_main.h" typedef struct cyanrip_dec_ctx cyanrip_dec_ctx; typedef struct cyanrip_enc_ctx cyanrip_enc_ctx; void cyanrip_print_codecs(void); int cyanrip_validate_fmt(const char *fmt); const char *cyanrip_fmt_desc(enum cyanrip_output_formats format); const char *cyanrip_fmt_folder(enum cyanrip_output_formats format); int cyanrip_create_dec_ctx(cyanrip_ctx *ctx, cyanrip_dec_ctx **s, cyanrip_track *t); int cyanrip_init_track_encoding(cyanrip_ctx *ctx, cyanrip_enc_ctx **enc_ctx, cyanrip_track *t, enum cyanrip_output_formats format); int cyanrip_send_pcm_to_encoders(cyanrip_ctx *ctx, cyanrip_enc_ctx **enc_ctx, int num_enc, cyanrip_dec_ctx *dec_ctx, const uint8_t *data, int bytes, int calc_global_peak); int cyanrip_reset_encoding(cyanrip_ctx *ctx, cyanrip_track *t); int cyanrip_finalize_encoding(cyanrip_ctx *ctx, cyanrip_track *t); int cyanrip_initialize_ebur128(cyanrip_ctx *ctx); int cyanrip_finalize_ebur128(cyanrip_ctx *ctx, int log); int cyanrip_writeout_track(cyanrip_ctx *ctx, cyanrip_enc_ctx *enc_ctx); int cyanrip_end_track_encoding(cyanrip_enc_ctx **s); void cyanrip_free_dec_ctx(cyanrip_ctx *ctx, cyanrip_dec_ctx **s); cyanrip-0.9.2/src/cyanrip_log.c000066400000000000000000000472021452633423300164420ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include "cyanrip_encode.h" #include "cyanrip_log.h" #include "accurip.h" #define CLOG(FORMAT, DICT, TAG) \ if (dict_get(DICT, TAG)) \ cyanrip_log(ctx, 0, FORMAT, dict_get(DICT, TAG)); \ static void print_offsets(cyanrip_ctx *ctx, cyanrip_track *t) { if (t->pregap_lsn != CDIO_INVALID_LSN) { char pregap_duration[16]; cyanrip_frames_to_duration(t->start_lsn - t->pregap_lsn, pregap_duration); cyanrip_log(ctx, 0, " Pregap LSN: %i (duration: %s)\n", t->pregap_lsn, pregap_duration); } else { cyanrip_log(ctx, 0, " Pregap LSN: none\n"); } if (t->frames_before_disc_start) cyanrip_log(ctx, 0, " Prepended: %i frames of silence\n", t->frames_before_disc_start); cyanrip_log(ctx, 0, " Start LSN: %i", t->start_lsn_sig); if (t->start_lsn != t->start_lsn_sig) cyanrip_log(ctx, 0, " (with offset: %i)\n", t->start_lsn); else cyanrip_log(ctx, 0, "\n"); cyanrip_log(ctx, 0, " End LSN: %i", t->end_lsn_sig); if (t->end_lsn != t->end_lsn_sig) cyanrip_log(ctx, 0, " (with offset: %i)\n", t->end_lsn); else cyanrip_log(ctx, 0, "\n"); if (t->frames_after_disc_end) cyanrip_log(ctx, 0, " Appended: %i frames of silence\n", t->frames_after_disc_end); } void cyanrip_log_track_end(cyanrip_ctx *ctx, cyanrip_track *t) { char length[16]; cyanrip_samples_to_duration(t->nb_samples, length); cyanrip_log(ctx, 0, " Preemphasis: "); if (!t->preemphasis) { cyanrip_log(ctx, 0, "none detected"); if (ctx->settings.force_deemphasis) cyanrip_log(ctx, 0, " (deemphasis forced)\n"); else cyanrip_log(ctx, 0, "\n"); } else { if (t->preemphasis_in_subcode) cyanrip_log(ctx, 0, "present (subcode)"); else cyanrip_log(ctx, 0, "present (TOC)"); if (ctx->settings.deemphasis || ctx->settings.force_deemphasis) cyanrip_log(ctx, 0, " (deemphasis applied)\n"); else cyanrip_log(ctx, 0, "\n"); } cyanrip_log(ctx, 0, "\n Properties:\n"); if (t->track_is_data) { cyanrip_log(ctx, 0, " Data bytes: %i (%.2f Mib)\n", t->frames*CDIO_CD_FRAMESIZE_RAW, t->frames*CDIO_CD_FRAMESIZE_RAW / (1024.0 * 1024.0)); cyanrip_log(ctx, 0, " Frames: %u\n", t->end_lsn_sig - t->start_lsn_sig + 1); print_offsets(ctx, t); cyanrip_log(ctx, 0, "\n"); return; } cyanrip_log(ctx, 0, " Duration: %s\n", length); cyanrip_log(ctx, 0, " Samples: %u\n", t->nb_samples); cyanrip_log(ctx, 0, " Frames: %u\n", t->end_lsn_sig - t->start_lsn_sig + 1); print_offsets(ctx, t); int has_ar = t->ar_db_status == CYANRIP_ACCUDB_FOUND; if (t->computed_crcs) { cyanrip_log(ctx, 0, "\n EAC CRC32: %08X", t->eac_crc); if (t->total_repeats) cyanrip_log(ctx, 0, " (after %i rips)\n", t->total_repeats); else cyanrip_log(ctx, 0, "\n"); } cyanrip_log(ctx, 0, " Accurip: %s", ctx->settings.disable_accurip ? "disabled" : has_ar ? "disc found in database" : "not found"); if (has_ar) cyanrip_log(ctx, 0, " (max confidence: %i)\n", t->ar_db_max_confidence); else cyanrip_log(ctx, 0, "\n"); if (t->computed_crcs) { int match_v1 = has_ar ? crip_find_ar(t, t->acurip_checksum_v1, 0) : 0; int match_v2 = has_ar ? crip_find_ar(t, t->acurip_checksum_v2, 0) : 0; cyanrip_log(ctx, 0, " Accurip v1: %08X", t->acurip_checksum_v1); if (has_ar && match_v1 > 0) cyanrip_log(ctx, 0, " (accurately ripped, confidence %i)\n", match_v1); else if (has_ar && (match_v2 < 1)) cyanrip_log(ctx, 0, " (not found, either a new pressing, or bad rip)\n"); else cyanrip_log(ctx, 0, "\n"); cyanrip_log(ctx, 0, " Accurip v2: %08X", t->acurip_checksum_v2); if (has_ar && (match_v2 > 0)) cyanrip_log(ctx, 0, " (accurately ripped, confidence %i)\n", match_v2); else if (has_ar && (match_v1 < 0)) cyanrip_log(ctx, 0, " (not found, either a new pressing, or bad rip)\n"); else cyanrip_log(ctx, 0, "\n"); if (!has_ar || ((match_v1 < 0) && (match_v2 < 0))) { int match_450 = has_ar ? crip_find_ar(t, t->acurip_checksum_v1_450, 1) : 0; cyanrip_log(ctx, 0, " Accurip 450: %08X", t->acurip_checksum_v1_450); if (has_ar && (match_450 > (3*(t->ar_db_max_confidence+1)/4)) && (t->acurip_checksum_v1_450 == 0x0)) { cyanrip_log(ctx, 0, " (match found, confidence %i, but a checksum of 0 is meaningless)\n", match_450, t->ar_db_max_confidence); } else if (has_ar && (match_450 > (3*(t->ar_db_max_confidence+1)/4))) { cyanrip_log(ctx, 0, " (matches Accurip DB, confidence %i, track is partially accurately ripped)\n", match_450, t->ar_db_max_confidence); } else if (has_ar) { cyanrip_log(ctx, 0, " (not found)\n"); } else { cyanrip_log(ctx, 0, "\n"); } } } cyanrip_log(ctx, 0, "\n Metadata:\n", length); int max_key_len = 0; const AVDictionaryEntry *d = NULL; while ((d = av_dict_get(t->meta, "", d, AV_DICT_IGNORE_SUFFIX))) max_key_len = FFMAX(strlen(d->key), max_key_len); d = NULL; while ((d = av_dict_get(t->meta, "", d, AV_DICT_IGNORE_SUFFIX))) { int key_len = strlen(d->key); cyanrip_log(ctx, 0, " %s: ", d->key); for (int i = 0; i < (max_key_len - key_len); i++) cyanrip_log(ctx, 0, " "); cyanrip_log(ctx, 0, "%s\n", d->value); } if (!ctx->settings.disable_coverart_embedding && (t->art.source_url || ctx->nb_cover_arts)) { const char *codec_name = NULL; CRIPArt *art = &t->art; if (!art->source_url) { int i; for (i = 0; i < ctx->nb_cover_arts; i++) if (!strcmp(dict_get(ctx->cover_arts[i].meta, "title"), "Front")) break; art = &ctx->cover_arts[i == ctx->nb_cover_arts ? 0 : i]; } if (art->pkt && art->params) { const AVCodecDescriptor *cd = avcodec_descriptor_get(art->params->codec_id); if (cd) codec_name = cd->long_name; else codec_name = avcodec_get_name(art->params->codec_id); } if (ctx->settings.print_info_only) cyanrip_log(ctx, 0, "\n Embedded cover art:\n %s: %s\n", dict_get(art->meta, "title"), art->source_url); else cyanrip_log(ctx, 0, "\n Embedded cover art:\n %s: %ix%i %s\n", dict_get(art->meta, "title"), art->params->width, art->params->height, codec_name); } cyanrip_log(ctx, 0, "\n File(s):\n"); for (int f = 0; f < ctx->settings.outputs_num; f++) { char *path = crip_get_path(ctx, CRIP_PATH_TRACK, 0, &crip_fmt_info[ctx->settings.outputs[f]], t); cyanrip_log(ctx, 0, " %s\n", path); av_free(path); } cyanrip_log(ctx, 0, "\n"); } void cyanrip_log_start_report(cyanrip_ctx *ctx) { cyanrip_log(ctx, 0, "cyanrip %s (%s)\n", PROJECT_VERSION_STRING, vcstag); cyanrip_log(ctx, 0, "System device: %s\n", ctx->settings.dev_path); if (ctx->drive->drive_model) cyanrip_log(ctx, 0, "Device model: %s\n", ctx->drive->drive_model); cyanrip_log(ctx, 0, "Offset: %c%i %s\n", ctx->settings.offset >= 0 ? '+' : '-', abs(ctx->settings.offset), abs(ctx->settings.offset) == 1 ? "sample" : "samples"); cyanrip_log(ctx, 0, "%s%c%i %s\n", ctx->settings.over_under_read_frames < 0 ? "Underread: " : "Overread: ", ctx->settings.over_under_read_frames >= 0 ? '+' : '-', abs(ctx->settings.over_under_read_frames), abs(ctx->settings.over_under_read_frames) == 1 ? "frame" : "frames"); cyanrip_log(ctx, 0, "%s%s\n", ctx->settings.over_under_read_frames < 0 ? "Underread mode: " : "Overread mode: ", ctx->settings.overread_leadinout ? "read in lead-in/lead-out" : "fill with silence in lead-in/lead-out"); if (ctx->settings.speed && (ctx->mcap & CDIO_DRIVE_CAP_MISC_SELECT_SPEED)) cyanrip_log(ctx, 0, "Speed: %ix\n", ctx->settings.speed); else cyanrip_log(ctx, 0, "Speed: default (%s)\n", (ctx->mcap & CDIO_DRIVE_CAP_MISC_SELECT_SPEED) ? "changeable" : "unchangeable"); cyanrip_log(ctx, 0, "C2 errors: %s by drive\n", (ctx->rcap & CDIO_DRIVE_CAP_READ_C2_ERRS) ? "supported" : "unsupported"); if (ctx->settings.paranoia_level == crip_max_paranoia_level) cyanrip_log(ctx, 0, "Paranoia level: %s\n", "max"); else if (ctx->settings.paranoia_level == 0) cyanrip_log(ctx, 0, "Paranoia level: %s\n", "none"); else cyanrip_log(ctx, 0, "Paranoia level: %i\n", ctx->settings.paranoia_level); cyanrip_log(ctx, 0, "Frame retries: %i\n", ctx->settings.max_retries); cyanrip_log(ctx, 0, "HDCD decoding: %s\n", ctx->settings.decode_hdcd ? "enabled" : "disabled"); cyanrip_log(ctx, 0, "Album Art: %s", ctx->nb_cover_arts == 0 ? "none" : ""); for (int i = 0; i < ctx->nb_cover_arts; i++) { const char *title = dict_get(ctx->cover_arts[i].meta, "title"); const char *source = dict_get(ctx->cover_arts[i].meta, "source"); cyanrip_log(ctx, 0, "%s%s%s%s%s", title, source ? " (From: " : "", source ? source : "", source ? ")" : "", i != (ctx->nb_cover_arts - 1) ? ", " : ""); } cyanrip_log(ctx, 0, "\n"); cyanrip_log(ctx, 0, "Outputs: "); for (int i = 0; i < ctx->settings.outputs_num; i++) cyanrip_log(ctx, 0, "%s%s", cyanrip_fmt_desc(ctx->settings.outputs[i]), i != (ctx->settings.outputs_num - 1) ? ", " : ""); cyanrip_log(ctx, 0, "\n"); CLOG("Disc number: %s\n", ctx->meta, "disc"); CLOG("Total discs: %s\n", ctx->meta, "totaldiscs"); cyanrip_log(ctx, 0, "Disc tracks: %i\n", ctx->nb_cd_tracks); cyanrip_log(ctx, 0, "Tracks to rip: %s", (ctx->settings.rip_indices_count == -1) ? "all" : !ctx->settings.rip_indices_count ? "none" : ""); if (ctx->settings.rip_indices_count != -1) { for (int i = 0; i < ctx->settings.rip_indices_count; i++) cyanrip_log(ctx, 0, "%i%s", ctx->settings.rip_indices[i], i != (ctx->settings.rip_indices_count - 1) ? ", " : ""); } cyanrip_log(ctx, 0, "\n"); char duration[16]; cyanrip_frames_to_duration(ctx->duration_frames, duration); CLOG("DiscID: %s\n", ctx->meta, "discid") CLOG("Release ID: %s\n", ctx->meta, "release_id") CLOG("CDDB ID: %s\n", ctx->meta, "cddb") CLOG("Disc MCN: %s\n", ctx->meta, "disc_mcn") CLOG("Album: %s\n", ctx->meta, "album") CLOG("Album artist: %s\n", ctx->meta, "album_artist") cyanrip_log(ctx, 0, "AccurateRip: %s\n", ctx->ar_db_status == CYANRIP_ACCUDB_ERROR ? "error" : ctx->ar_db_status == CYANRIP_ACCUDB_NOT_FOUND ? "not found" : ctx->ar_db_status == CYANRIP_ACCUDB_FOUND ? "found" : ctx->ar_db_status == CYANRIP_ACCUDB_MISMATCH ? "mismatch" : "disabled"); cyanrip_log(ctx, 0, "Total time: %s\n", duration); cyanrip_log(ctx, 0, "\n"); } void cyanrip_log_finish_report(cyanrip_ctx *ctx) { char t_s[64]; time_t t_c = time(NULL); struct tm *t_l = localtime(&t_c); strftime(t_s, sizeof(t_s), "%Y-%m-%dT%H:%M:%S", t_l); if (ctx->ar_db_status == CYANRIP_ACCUDB_FOUND) { int accurip_verified = 0; int accurip_partial = 0; for (int i = 0; i < ctx->nb_tracks; i++) { cyanrip_track *t = &ctx->tracks[i]; if (t->ar_db_status == CYANRIP_ACCUDB_FOUND) { if ((crip_find_ar(t, t->acurip_checksum_v1, 0) > 0) || (crip_find_ar(t, t->acurip_checksum_v2, 0) > 0)) accurip_verified++; else if (crip_find_ar(t, t->acurip_checksum_v1_450, 1) > (3*(t->ar_db_max_confidence+1)/4) && t->acurip_checksum_v1_450) accurip_partial++; } } cyanrip_log(ctx, 0, "Tracks ripped accurately: %i/%i\n", accurip_verified, ctx->nb_tracks); if (accurip_partial) cyanrip_log(ctx, 0, "Tracks ripped partially accurately: %i/%i\n", accurip_partial, ctx->nb_tracks - accurip_verified); cyanrip_log(ctx, 0, "\n"); } int has_status = 0; cyanrip_log(ctx, 0, "Paranoia status counts:\n"); #define PCHECK(PROP) \ if (paranoia_status[PARANOIA_CB_ ## PROP]) { \ const char *pstr = " " #PROP ": "; \ cyanrip_log(ctx, 0, "%s", pstr); \ int padding = strlen(" FIXUP_DROPPED: ") - strlen(pstr); \ for (int i = 0; i < padding; i++) \ cyanrip_log(ctx, 0, " "); \ cyanrip_log(ctx, 0, "%lu\n", paranoia_status[PARANOIA_CB_ ## PROP]); \ has_status |= !!paranoia_status[PARANOIA_CB_ ## PROP]; \ } PCHECK(READ) PCHECK(VERIFY) PCHECK(FIXUP_EDGE) PCHECK(FIXUP_ATOM) PCHECK(SCRATCH) PCHECK(REPAIR) PCHECK(SKIP) PCHECK(DRIFT) PCHECK(BACKOFF) PCHECK(OVERLAP) PCHECK(FIXUP_DROPPED) PCHECK(FIXUP_DUPED) PCHECK(READERR) PCHECK(CACHEERR) PCHECK(WROTE) PCHECK(FINISHED) cyanrip_log(ctx, 0, "%s\n", has_status ? "" : " none\n"); #undef PCHECK cyanrip_log(ctx, 0, "Ripping errors: %i\n", ctx->total_error_count); cyanrip_log(ctx, 0, "Ripping finished at %s\n", t_s); } int cyanrip_log_init(cyanrip_ctx *ctx) { for (int i = 0; i < ctx->settings.outputs_num; i++) { char *logfile = crip_get_path(ctx, CRIP_PATH_LOG, 1, &crip_fmt_info[ctx->settings.outputs[i]], NULL); ctx->logfile[i] = av_fopen_utf8(logfile, "w+"); if (!ctx->logfile[i]) { cyanrip_log(ctx, 0, "Couldn't open path \"%s\" for writing: %s!\n" "Invalid folder name? Try -D .\n", logfile, av_err2str(AVERROR(errno))); av_freep(&logfile); return 1; } av_freep(&logfile); } return 0; } void cyanrip_log_end(cyanrip_ctx *ctx) { uint8_t digest[64]; char digest_str[AV_BASE64_SIZE(64)]; uint8_t *str_data = NULL; struct AVSHA512 *shactx = av_sha512_alloc(); for (int i = 0; i < ctx->settings.outputs_num; i++) { if (!ctx->logfile[i]) continue; if (!shactx) goto fail; av_sha512_init(shactx, 512); long int pos = ftell(ctx->logfile[i]); uint8_t *str_data_new = av_realloc(str_data, pos); if (!str_data_new) goto fail; str_data = str_data_new; rewind(ctx->logfile[i]); long int read_bytes = fread(str_data, 1, pos, ctx->logfile[i]); fseek(ctx->logfile[i], 0, SEEK_END); av_sha512_update(shactx, str_data, read_bytes); av_sha512_final(shactx, digest); /* Proprietary top-secret FUN512 encrayptalignalaiton algorithm */ for (int j = 0; j < 64; j++) /* To wash a velociraptor... */ digest[j] ^= 0x81 + i; /* Stand behind it */ for (int j = 0; j < 64; j++) /* Proudly yell "I AM A TRAFFIC LIGHT SPECIALIST" */ for (int k = 0; k < 64; k++) /* A USB will descend, and quickly freeze the raptor */ if (j != k) /* Carefully blast it with a jet engine to thaw it */ digest[j] ^= digest[k]; /* Enjoy your hot velociraptor meat by adding fresh miraculin */ av_base64_encode(digest_str, AV_BASE64_SIZE(64), digest, 64); /* Pretend it's not base64 */ for (int j = (AV_BASE64_SIZE(64) - 1); (digest_str[j] == '\0' || digest_str[j] == '='); j--) digest_str[j] = '\0'; for (int j = 0; j < strlen(digest_str); j++) { if (digest_str[j] == '/') digest_str[j] = '_'; if (digest_str[j] == '+') digest_str[j] = '.'; } fprintf(ctx->logfile[i], "Log FUN512: %s\n", digest_str); fail: fclose(ctx->logfile[i]); ctx->logfile[i] = NULL; } av_free(str_data); av_free(shactx); } static cyanrip_ctx *av_global_ctx = NULL; static int av_max_log_level = AV_LOG_QUIET; static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER; static void av_log_capture(void *ptr, int lvl, const char *format, va_list args) { pthread_mutex_lock(&log_lock); if (lvl > av_max_log_level) goto end; if (av_global_ctx) { for (int i = 0; i < av_global_ctx->settings.outputs_num; i++) { if (!av_global_ctx->logfile[i]) continue; va_list args2; va_copy(args2, args); vfprintf(av_global_ctx->logfile[i], format, args2); va_end(args2); } } vprintf(format, args); end: pthread_mutex_unlock(&log_lock); } void cyanrip_set_av_log_capture(cyanrip_ctx *ctx, int enable, int max_av_lvl) { pthread_mutex_lock(&log_lock); if (enable) { av_global_ctx = ctx; av_max_log_level = max_av_lvl; av_log_set_callback(av_log_capture); } else { av_log_set_callback(av_log_default_callback); av_global_ctx = NULL; av_max_log_level = AV_LOG_QUIET; } pthread_mutex_unlock(&log_lock); } void cyanrip_log(cyanrip_ctx *ctx, int verbose, const char *format, ...) { pthread_mutex_lock(&log_lock); va_list args; va_start(args, format); if (ctx) { for (int i = 0; i < ctx->settings.outputs_num; i++) { if (!ctx->logfile[i]) continue; va_list args2; va_copy(args2, args); vfprintf(ctx->logfile[i], format, args2); va_end(args2); } } vprintf(format, args); va_end(args); pthread_mutex_unlock(&log_lock); } cyanrip-0.9.2/src/cyanrip_log.h000066400000000000000000000023301452633423300164400ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include "cyanrip_main.h" int cyanrip_log_init(cyanrip_ctx *ctx); void cyanrip_log_end(cyanrip_ctx *ctx); void cyanrip_log_start_report(cyanrip_ctx *ctx); void cyanrip_log_finish_report(cyanrip_ctx *ctx); void cyanrip_log_track_end(cyanrip_ctx *ctx, cyanrip_track *t); void cyanrip_set_av_log_capture(cyanrip_ctx *ctx, int enable, int max_av_lvl); void cyanrip_log(cyanrip_ctx *ctx, int verbose, const char *format, ...); cyanrip-0.9.2/src/cyanrip_main.c000066400000000000000000002410641452633423300166070ustar00rootroot00000000000000/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include "cyanrip_main.h" #include "cyanrip_log.h" #include "cue_writer.h" #include "checksums.h" #include "discid.h" #include "musicbrainz.h" #include "coverart.h" #include "accurip.h" #include "os_compat.h" #include "cyanrip_encode.h" int quit_now = 0; const cyanrip_out_fmt crip_fmt_info[] = { [CYANRIP_FORMAT_FLAC] = { "flac", "FLAC", "flac", "flac", 1, 11, 1, AV_CODEC_ID_FLAC, }, [CYANRIP_FORMAT_MP3] = { "mp3", "MP3", "mp3", "mp3", 1, 0, 0, AV_CODEC_ID_MP3, }, [CYANRIP_FORMAT_TTA] = { "tta", "TTA", "tta", "tta", 0, 0, 1, AV_CODEC_ID_TTA, }, [CYANRIP_FORMAT_OPUS] = { "opus", "OPUS", "opus", "ogg", 0, 10, 0, AV_CODEC_ID_OPUS, }, [CYANRIP_FORMAT_AAC] = { "aac", "AAC", "m4a", "adts", 0, 0, 0, AV_CODEC_ID_AAC, }, [CYANRIP_FORMAT_AAC_MP4] = { "aac_mp4", "AAC", "mp4", "mp4", 1, 0, 0, AV_CODEC_ID_AAC, }, [CYANRIP_FORMAT_WAVPACK] = { "wavpack", "WV", "wv", "wv", 0, 3, 1, AV_CODEC_ID_WAVPACK, }, [CYANRIP_FORMAT_VORBIS] = { "vorbis", "OGG", "ogg", "ogg", 0, 0, 0, AV_CODEC_ID_VORBIS, }, [CYANRIP_FORMAT_ALAC] = { "alac", "ALAC", "m4a", "ipod", 0, 2, 1, AV_CODEC_ID_ALAC, }, [CYANRIP_FORMAT_WAV] = { "wav", "WAV", "wav", "wav", 0, 0, 1, AV_CODEC_ID_NONE, }, [CYANRIP_FORMAT_OPUS_MP4] = { "opus_mp4", "OPUS", "mp4", "mp4", 1, 10, 0, AV_CODEC_ID_OPUS, }, [CYANRIP_FORMAT_PCM] = { "pcm", "PCM", "pcm", "s16le", 0, 0, 1, AV_CODEC_ID_NONE, }, }; static void free_track(cyanrip_ctx *ctx, cyanrip_track *t) { for (int i = 0; i < ctx->settings.outputs_num; i++) cyanrip_end_track_encoding(&t->enc_ctx[i]); cyanrip_free_dec_ctx(ctx, &t->dec_ctx); crip_free_art(&t->art); av_dict_free(&t->meta); av_free(t->ar_db_entries); } static void cyanrip_ctx_end(cyanrip_ctx **s) { cyanrip_ctx *ctx; if (!s || !*s) return; ctx = *s; for (int i = 0; i < ctx->nb_tracks; i++) free_track(ctx, &ctx->tracks[i]); for (int i = 0; i < ctx->nb_cover_arts; i++) crip_free_art(&ctx->cover_arts[i]); cyanrip_finalize_ebur128(ctx, 0); av_free(ctx->mb_submission_url); if (ctx->paranoia) cdio_paranoia_free(ctx->paranoia); if (ctx->drive) cdio_cddap_close_no_free_cdio(ctx->drive); if (ctx->settings.eject_on_success_rip && !ctx->total_error_count && (ctx->mcap & CDIO_DRIVE_CAP_MISC_EJECT) && ctx->cdio && !quit_now) cdio_eject_media(&ctx->cdio); else if (ctx->cdio) cdio_destroy(ctx->cdio); free(ctx->settings.dev_path); av_dict_free(&ctx->meta); av_freep(&ctx->base_dst_folder); av_freep(&ctx); *s = NULL; } static const paranoia_mode_t paranoia_level_map[] = { [0] = PARANOIA_MODE_DISABLE, /* Disable everything */ [1] = PARANOIA_MODE_OVERLAP, /* Perform overlapped reads */ [2] = PARANOIA_MODE_OVERLAP | PARANOIA_MODE_VERIFY, /* Perform and verify overlapped reads */ [3] = PARANOIA_MODE_FULL ^ PARANOIA_MODE_NEVERSKIP, /* Maximum, but do allow skipping sectors */ }; const int crip_max_paranoia_level = (sizeof(paranoia_level_map) / sizeof(paranoia_level_map[0])) - 1; static int cyanrip_ctx_init(cyanrip_ctx **s, cyanrip_settings *settings) { cyanrip_ctx *ctx = av_mallocz(sizeof(cyanrip_ctx)); memcpy(&ctx->settings, settings, sizeof(cyanrip_settings)); if (ctx->settings.print_info_only) ctx->settings.eject_on_success_rip = 0; cdio_init(); if (!ctx->settings.dev_path) ctx->settings.dev_path = cdio_get_default_device(NULL); if (!(ctx->cdio = cdio_open(ctx->settings.dev_path, DRIVER_UNKNOWN))) { cyanrip_log(ctx, 0, "Unable to init cdio context\n"); return AVERROR(EINVAL); } cdio_get_drive_cap(ctx->cdio, &ctx->rcap, &ctx->wcap, &ctx->mcap); char *msg = NULL; if (!(ctx->drive = cdio_cddap_identify_cdio(ctx->cdio, CDDA_MESSAGE_LOGIT, &msg))) { cyanrip_log(ctx, 0, "Unable to init cddap context!\n"); if (msg) { cyanrip_log(ctx, 0, "cdio: \"%s\"\n", msg); cdio_cddap_free_messages(msg); } return AVERROR(EINVAL); } if (msg) { cyanrip_log(ctx, 0, "%s\n", msg); cdio_cddap_free_messages(msg); } cyanrip_log(ctx, 0, "Opening drive...\n"); int ret = cdio_cddap_open(ctx->drive); if (ret < 0) { cyanrip_log(ctx, 0, "Unable to open device!\n"); cyanrip_ctx_end(&ctx); return AVERROR(EINVAL); } cdio_cddap_verbose_set(ctx->drive, CDDA_MESSAGE_LOGIT, CDDA_MESSAGE_FORGETIT); if (settings->speed) { if (!(ctx->mcap & CDIO_DRIVE_CAP_MISC_SELECT_SPEED)) { cyanrip_log(ctx, 0, "Device does not support changing speeds!\n"); cyanrip_ctx_end(&ctx); return AVERROR(EINVAL); } ret = cdio_cddap_speed_set(ctx->drive, settings->speed); msg = cdio_cddap_errors(ctx->drive); if (msg) { cyanrip_log(ctx, 0, "cdio error: %s\n", msg); cdio_cddap_free_messages(msg); } if (ret) return AVERROR(EINVAL); } ctx->paranoia = cdio_paranoia_init(ctx->drive); if (!ctx->paranoia) { cyanrip_log(ctx, 0, "Unable to init paranoia!\n"); cyanrip_ctx_end(&ctx); return AVERROR(EINVAL); } cdio_paranoia_modeset(ctx->paranoia, paranoia_level_map[settings->paranoia_level]); ctx->start_lsn = 0; ctx->end_lsn = cdio_get_track_lsn(ctx->cdio, CDIO_CDROM_LEADOUT_TRACK) - 1; ctx->duration_frames = ctx->end_lsn - ctx->start_lsn + 1; ctx->nb_tracks = ctx->nb_cd_tracks = cdio_cddap_tracks(ctx->drive); if ((ctx->nb_tracks < 1) || (ctx->nb_tracks > CDIO_CD_MAX_TRACKS)) { cyanrip_log(ctx, 0, "Invalid number of tracks: %i!\n", ctx->nb_tracks); ctx->nb_tracks = 0; cyanrip_ctx_end(&ctx); return AVERROR(EINVAL); } int first_track_nb = cdio_get_first_track_num(ctx->cdio); for (int i = 0; i < ctx->nb_cd_tracks; i++) { cyanrip_track *t = &ctx->tracks[i]; t->index = i + 1; t->number = t->cd_track_number = i + first_track_nb; t->track_is_data = !cdio_cddap_track_audiop(ctx->drive, t->number); t->pregap_lsn = cdio_get_track_pregap_lsn(ctx->cdio, t->number); t->dropped_pregap_start = CDIO_INVALID_LSN; t->merged_pregap_end = CDIO_INVALID_LSN; t->start_lsn = cdio_get_track_lsn(ctx->cdio, t->number); t->end_lsn = cdio_get_track_last_lsn(ctx->cdio, t->number); t->start_lsn_sig = t->start_lsn; t->end_lsn_sig = t->end_lsn; if (t->track_is_data) { if (ctx->settings.pregap_action[t->number - 1] == CYANRIP_PREGAP_DEFAULT) ctx->settings.pregap_action[t->number - 1] = CYANRIP_PREGAP_DROP; if (ctx->settings.pregap_action[t->number - 0] == CYANRIP_PREGAP_DEFAULT) ctx->settings.pregap_action[t->number - 0] = CYANRIP_PREGAP_DROP; if (i == (ctx->nb_cd_tracks - 1)) { ctx->tracks[i - 1].end_lsn -= 11400; t->pregap_lsn = ctx->tracks[i - 1].end_lsn + 1; } } } for (int i = 0; i < ctx->nb_cd_tracks; i++) { cyanrip_track *t = &ctx->tracks[i]; if (t->track_is_data) continue; t->acurip_track_is_first = 1; break; } for (int i = ctx->nb_cd_tracks - 1; i >= 0; i--) { cyanrip_track *t = &ctx->tracks[i]; if (t->track_is_data) continue; t->acurip_track_is_last = 1; break; } /* For hot removal detection - init this so we can detect changes */ cdio_get_media_changed(ctx->cdio); *s = ctx; return 0; } #define REPLAYGAIN_REF_LOUDNESS (-18.0) static int crip_replaygain_meta_track(cyanrip_ctx *ctx, cyanrip_track *t) { char temp[32]; snprintf(temp, sizeof(temp), "%.2f dB", REPLAYGAIN_REF_LOUDNESS - t->ebu_integrated); av_dict_set(&t->meta, "REPLAYGAIN_TRACK_GAIN", temp, 0); av_dict_set_int(&t->meta, "R128_TRACK_GAIN", lrintf((REPLAYGAIN_REF_LOUDNESS - t->ebu_integrated + 5) * 256), 0); snprintf(temp, sizeof(temp), "%.2f dB", t->ebu_range); av_dict_set(&t->meta, "REPLAYGAIN_TRACK_RANGE", temp, 0); snprintf(temp, sizeof(temp), "%.6f", powf(10, t->ebu_true_peak / 20.0)); av_dict_set(&t->meta, "REPLAYGAIN_TRACK_PEAK", temp, 0); snprintf(temp, sizeof(temp), "%.2f LUFS", REPLAYGAIN_REF_LOUDNESS); av_dict_set(&t->meta, "REPLAYGAIN_REFERENCE_LOUDNESS", temp, 0); return 0; } static int crip_replaygain_meta_album(cyanrip_ctx *ctx) { char temp[32]; for (int i = 0; i < ctx->nb_cd_tracks; i++) { cyanrip_track *t = &ctx->tracks[i]; snprintf(temp, sizeof(temp), "%.2f dB", REPLAYGAIN_REF_LOUDNESS - ctx->ebu_integrated); av_dict_set(&t->meta, "REPLAYGAIN_ALBUM_GAIN", temp, 0); av_dict_set_int(&t->meta, "R128_ALBUM_GAIN", lrintf((REPLAYGAIN_REF_LOUDNESS - ctx->ebu_integrated + 5) * 256), 0); snprintf(temp, sizeof(temp), "%.2f dB", ctx->ebu_range); av_dict_set(&t->meta, "REPLAYGAIN_ALBUM_RANGE", temp, 0); snprintf(temp, sizeof(temp), "%.6f", powf(10, ctx->ebu_true_peak / 20.0)); av_dict_set(&t->meta, "REPLAYGAIN_ALBUM_PEAK", temp, 0); } return 0; } static void crip_fill_mcn(cyanrip_ctx *ctx) { /* Get disc MCN */ if (ctx->rcap & CDIO_DRIVE_CAP_READ_MCN) { const char *mcn = cdio_get_mcn(ctx->cdio); if (mcn) { if (strlen(mcn)) av_dict_set(&ctx->meta, "disc_mcn", mcn, 0); cdio_free((void *)mcn); } } } static void track_set_creation_time(cyanrip_ctx *ctx, cyanrip_track *t) { if (dict_get(t->meta, "creation_time")) return; char t_s[64]; time_t t_c = time(NULL); struct tm *t_l = localtime(&t_c); strftime(t_s, sizeof(t_s), "%Y-%m-%dT%H:%M:%S", t_l); av_dict_set(&t->meta, "creation_time", t_s, 0); } static void copy_album_to_track_meta(cyanrip_ctx *ctx) { for (int i = 0; i < ctx->nb_tracks; i++) { av_dict_set_int(&ctx->tracks[i].meta, "track", ctx->tracks[i].number, 0); av_dict_set_int(&ctx->tracks[i].meta, "tracktotal", ctx->nb_tracks, 0); av_dict_copy(&ctx->tracks[i].meta, ctx->meta, AV_DICT_DONT_OVERWRITE); } } static const uint8_t silent_frame[CDIO_CD_FRAMESIZE_RAW] = { 0 }; uint64_t paranoia_status[PARANOIA_CB_FINISHED + 1] = { 0 }; static void status_cb(long int n, paranoia_cb_mode_t status) { if (status >= PARANOIA_CB_READ && status <= PARANOIA_CB_FINISHED) paranoia_status[status]++; } static const uint8_t *cyanrip_read_frame(cyanrip_ctx *ctx) { int err = 0; char *msg = NULL; const uint8_t *data; data = (void *)cdio_paranoia_read_limited(ctx->paranoia, &status_cb, ctx->settings.max_retries); msg = cdio_cddap_errors(ctx->drive); if (msg) { cyanrip_log(ctx, 0, "\ncdio error: %s\n", msg); cdio_cddap_free_messages(msg); err = 1; } if (!data) { if (!msg) { cyanrip_log(ctx, 0, "\nFrame read failed!\n"); err = 1; } data = silent_frame; } ctx->total_error_count += err; return data; } static int search_for_offset(cyanrip_track *t, int *offset_found, const uint8_t *mem, int dir, int guess, int bytes) { if (guess) { const uint8_t *start_addr = mem + guess*4; uint32_t accurip_v1 = 0x0; for (int j = 0; j < (CDIO_CD_FRAMESIZE_RAW >> 2); j++) accurip_v1 += AV_RL32(&start_addr[j*4]) * (j + 1); if (crip_find_ar(t, accurip_v1, 1) == t->ar_db_max_confidence && accurip_v1) { *offset_found = guess; return 1; } } for (int byte_off = ((dir < 0) * 4); byte_off < bytes; byte_off += 4) { if (quit_now) return 0; int offset = dir * (byte_off >> 2); if (guess == offset) continue; const uint8_t *start_addr = mem + dir * byte_off; uint32_t accurip_v1 = 0x0; for (int j = 0; j < (CDIO_CD_FRAMESIZE_RAW >> 2); j++) accurip_v1 += AV_RL32(&start_addr[j*4]) * (j + 1); if (crip_find_ar(t, accurip_v1, 1) == t->ar_db_max_confidence && accurip_v1) { *offset_found = offset; return 1; } } return 0; } static void search_for_drive_offset(cyanrip_ctx *ctx, int range) { int had_ar = 0, did_check = 0; int offset_found = 0, offset_found_samples = 0; uint8_t *mem = av_malloc(2 * range * CDIO_CD_FRAMESIZE_RAW); if (ctx->ar_db_status != CYANRIP_ACCUDB_FOUND) goto end; for (int t_idx = 0; t_idx < ctx->nb_tracks; t_idx++) { cyanrip_track *t = &ctx->tracks[t_idx]; lsn_t start = cdio_get_track_lsn(ctx->cdio, t_idx + 1); lsn_t end = cdio_get_track_last_lsn(ctx->cdio, t_idx + 1); if (ctx->tracks[t_idx].ar_db_status != CYANRIP_ACCUDB_FOUND) continue; else had_ar |= 1; if ((end - start) < (450 + range)) continue; did_check |= 1; /* Set start/end ranges */ start += 450 - range; end = start + 2*range; size_t bytes = 0; cyanrip_log(ctx, 0, "Loading data for track %i...\n", t_idx + 1); cdio_paranoia_seek(ctx->paranoia, start, SEEK_SET); for (int i = 0; i < 2*range; i++) { const uint8_t *data = cyanrip_read_frame(ctx); memcpy(mem + bytes, data, CDIO_CD_FRAMESIZE_RAW); bytes += CDIO_CD_FRAMESIZE_RAW; if (quit_now) { cyanrip_log(ctx, 0, "Stopping, offset finding incomplete!\n"); goto end; } } int found, offset; int dir = (offset_found && (offset_found_samples < 0)) ? -1 : +1; cyanrip_log(ctx, 0, "Data loaded, searching for offsets...\n"); found = search_for_offset(t, &offset, mem + (bytes >> 1), dir, offset_found_samples, range * CDIO_CD_FRAMESIZE_RAW); if (!found) found = search_for_offset(t, &offset, mem + (bytes >> 1), -dir, 0, range * CDIO_CD_FRAMESIZE_RAW); if (!found) { cyanrip_log(ctx, 0, "Nothing found for track %i%s\n", t_idx + 1, t_idx != (ctx->nb_tracks - 1) ? ", trying another track" : ""); } else if (!offset_found) { offset_found_samples = offset; offset_found++; cyanrip_log(ctx, 0, "Offset of %c%i found in track %i%s\n", offset >= 0 ? '+' : '-', abs(offset), t_idx + 1, t_idx != (ctx->nb_tracks - 1) ? ", trying to confirm with another track" : ""); } else if (offset_found_samples == offset) { offset_found++; cyanrip_log(ctx, 0, "Offset of %c%i confirmed (confidence: %i) in track %i%s\n", offset >= 0 ? '+' : '-', abs(offset), offset_found, t_idx + 1, t_idx != (ctx->nb_tracks - 1) ? ", trying to confirm with another track" : ""); } else { cyanrip_log(ctx, 0, "New offset of %c%i found at track %i, scrapping old offset of %c%i%s\n", offset >= 0 ? '+' : '-', abs(offset), t_idx + 1, offset_found_samples >= 0 ? '+' : '-', abs(offset_found_samples), t_idx != (ctx->nb_tracks - 1) ? ", trying to confirm with another track" : ""); offset_found_samples = offset; offset_found = 1; } } end: av_free(mem); if (!offset_found) { if (!had_ar) { cyanrip_log(ctx, 0, "No track had AccuRip entry, cannot find offset!\n"); } else if (had_ar && !did_check) { cyanrip_log(ctx, 0, "No track was long enough, unable to find drive offset!\n"); } else { cyanrip_log(ctx, 0, "Was not able to find drive offset with a radius of %i frames" ", trying again with a larger radius...\n", range); search_for_drive_offset(ctx, 2*range); } return; } else { cyanrip_log(ctx, 0, "Drive offset of %c%i found (confidence: %i)!\n", offset_found_samples >= 0 ? '+' : '-', abs(offset_found_samples), offset_found); } } static void track_read_extra(cyanrip_ctx *ctx, cyanrip_track *t) { if (!t->track_is_data) { /* ISRC code */ if (!ctx->disregard_cd_isrc && (ctx->rcap & CDIO_DRIVE_CAP_READ_ISRC) && !dict_get(t->meta, "isrc")) { const char *isrc_str = cdio_get_track_isrc(ctx->cdio, t->cd_track_number); if (isrc_str) { if (strlen(isrc_str)) av_dict_set(&t->meta, "isrc", isrc_str, 0); else ctx->disregard_cd_isrc = 1; cdio_free((void *)isrc_str); } else { ctx->disregard_cd_isrc = 1; } } /* TOC preemphasis flag*/ t->preemphasis = cdio_cddap_track_preemp(ctx->drive, t->cd_track_number); /* Subcode preemphasis flag */ if (!t->preemphasis && (ctx->rcap & CDIO_DRIVE_CAP_READ_ISRC)) { cdio_subchannel_t subchannel_data = { 0 }; driver_return_code_t ret = cdio_audio_read_subchannel(ctx->cdio, &subchannel_data); if (ret != DRIVER_OP_SUCCESS) { cyanrip_log(ctx, 0, "Unable to read track %i subchannel info!\n", t->number); } else { t->preemphasis = t->preemphasis_in_subcode = subchannel_data.control & 0x01; } } } } static int cyanrip_rip_track(cyanrip_ctx *ctx, cyanrip_track *t) { int ret = 0; int line_len = 0; int max_line_len = 0; char line[4096]; if (t->track_is_data) { cyanrip_log(ctx, 0, "Track %i is data:\n", t->number); cyanrip_log_track_end(ctx, t); cyanrip_cue_track(ctx, t); return 0; } /* Hopefully reduce seeking by reading this here */ track_read_extra(ctx, t); /* Set creation time at the start of ripping */ track_set_creation_time(ctx, t); uint32_t start_frames_read; uint32_t *last_checksums = NULL; uint32_t nb_last_checksums = 0; uint32_t repeat_mode_encode = 0; uint32_t total_repeats = 0; int calc_global_peak_set = 0; int calc_global_peak = !ctx->settings.ripping_retries; repeat_ripping:; const int frames_before_disc_start = t->frames_before_disc_start; const int frames = t->frames; const int frames_after_disc_end = t->frames_after_disc_end; const ptrdiff_t offs = t->partial_frame_byte_offs; start_frames_read = ctx->frames_read; cdio_paranoia_seek(ctx->paranoia, t->start_lsn, SEEK_SET); int start_err = ctx->total_error_count; /* Checksum */ cyanrip_checksum_ctx checksum_ctx; crip_init_checksum_ctx(ctx, &checksum_ctx, t); /* Fill with silence to maintain track length */ for (int i = 0; i < frames_before_disc_start; i++) { int bytes = CDIO_CD_FRAMESIZE_RAW; const uint8_t *data = silent_frame; if (!i && offs) { data += CDIO_CD_FRAMESIZE_RAW + offs; bytes = -offs; } crip_process_checksums(&checksum_ctx, data, bytes); if (!ctx->settings.ripping_retries || repeat_mode_encode) { ret = cyanrip_send_pcm_to_encoders(ctx, t->enc_ctx, ctx->settings.outputs_num, t->dec_ctx, data, bytes, calc_global_peak); if (ret) { cyanrip_log(ctx, 0, "Error in decoding/sending frame: %s\n", av_err2str(ret)); goto fail; } } } int64_t frame_last_read = av_gettime_relative(); /* Read the actual CD data */ for (int i = 0; i < frames; i++) { /* Detect disc removals */ if (cdio_get_media_changed(ctx->cdio)) { cyanrip_log(ctx, 0, "\nDrive media changed, stopping!\n"); ret = AVERROR(EINVAL); goto fail; } /* Flush paranoia cache if overreading into lead-out - no idea why */ if ((t->start_lsn + i) > ctx->end_lsn) cdio_paranoia_seek(ctx->paranoia, t->start_lsn + i, SEEK_SET); int bytes = CDIO_CD_FRAMESIZE_RAW; const uint8_t *data = cyanrip_read_frame(ctx); /* Account for partial frames caused by the offset */ if (offs > 0) { if (!i) { data += offs; bytes -= offs; } else if ((i == (frames - 1)) && !frames_after_disc_end) { bytes = offs; } } else if (offs < 0) { if (!i && !frames_before_disc_start) { data += CDIO_CD_FRAMESIZE_RAW + offs; bytes = -offs; } else if (i == (frames - 1)) { bytes += offs; } } /* Stop now if requested */ if (quit_now) { cyanrip_log(ctx, 0, "\nStopping, ripping incomplete!\n"); break; } /* Update checksums */ crip_process_checksums(&checksum_ctx, data, bytes); /* Decode and encode */ if (!ctx->settings.ripping_retries || repeat_mode_encode) { ret = cyanrip_send_pcm_to_encoders(ctx, t->enc_ctx, ctx->settings.outputs_num, t->dec_ctx, data, bytes, calc_global_peak); if (ret < 0) { cyanrip_log(ctx, 0, "\nError in decoding/sending frame: %s\n", av_err2str(ret)); goto fail; } } if (line_len > 0) { cyanrip_log(NULL, 0, "\r", line); line_len = 0; } /* Report progress */ line_len += snprintf(line, sizeof(line), "Ripping%strack %i, progress - %0.2f%%", (!ctx->settings.ripping_retries || repeat_mode_encode) ? " and encoding " : " ", t->number, ((double)(i + 1)/frames)*100.0f); ctx->frames_read++; int64_t cur_time = av_gettime_relative(); int64_t frame_diff = cur_time - frame_last_read; frame_last_read = cur_time; int64_t diff = cr_sliding_win(&ctx->eta_ctx, frame_diff, cur_time, av_make_q(1, 1000000), 1000000LL * 1200LL, 1); int64_t seconds = (ctx->frames_to_read - ctx->frames_read) * diff; int hours = 0; while (seconds >= (3600LL * 1000000LL)) { seconds -= (3600LL * 1000000LL); hours++; } int minutes = 0; while (seconds >= (60LL * 1000000LL)) { seconds -= (60LL * 1000000LL); minutes++; } seconds = av_rescale(seconds, 1, 1000000); if (seconds == 60) { minutes++; seconds = 0; } if (minutes == 60) { hours++; minutes = 0; } if (hours) line_len += snprintf(line + line_len, sizeof(line) - line_len, ", ETA - %ih %im", hours, minutes); else if (minutes) line_len += snprintf(line + line_len, sizeof(line) - line_len, ", ETA - %im", minutes); else line_len += snprintf(line + line_len, sizeof(line) - line_len, ", ETA - %" PRId64 "s", seconds); if (ctx->total_error_count - start_err) line_len += snprintf(line + line_len, sizeof(line) - line_len, ", errors - %i", ctx->total_error_count - start_err); max_line_len = FFMAX(line_len, max_line_len); if (line_len < max_line_len) { for (int c = line_len; c < max_line_len; c++) line_len += snprintf(line + line_len, sizeof(line) - line_len, " "); } cyanrip_log(NULL, 0, "%s", line); } /* Fill with silence to maintain track length */ for (int i = 0; i < frames_after_disc_end; i++) { int bytes = CDIO_CD_FRAMESIZE_RAW; const uint8_t *data = silent_frame; if ((i == (frames_after_disc_end - 1)) && offs) bytes = offs; crip_process_checksums(&checksum_ctx, data, bytes); if (!ctx->settings.ripping_retries || repeat_mode_encode) { ret = cyanrip_send_pcm_to_encoders(ctx, t->enc_ctx, ctx->settings.outputs_num, t->dec_ctx, data, bytes, calc_global_peak); if (ret < 0) { cyanrip_log(ctx, 0, "Error in decoding/sending frame: %s\n", av_err2str(ret)); goto fail; } } } calc_global_peak = 0; crip_finalize_checksums(&checksum_ctx, t); if (ctx->settings.ripping_retries) { int matches = 0; for (int i = 0; i < nb_last_checksums; i++) matches += last_checksums[i] == checksum_ctx.eac_crc; total_repeats++; if (matches >= ctx->settings.ripping_retries) { cyanrip_log(ctx, 0, "\nDone; (%i out of %i matches for current checksum %08X)\n", matches, ctx->settings.ripping_retries, checksum_ctx.eac_crc); goto finalize_ripping; } if (total_repeats >= ctx->settings.max_retries) { cyanrip_log(ctx, 0, "\nDone; (no matches found, but hit repeat limit of %i)\n", ctx->settings.max_retries); goto finalize_ripping; } /* If the next match may be the last one, start encoding */ if ((matches + 1) >= ctx->settings.ripping_retries || (total_repeats + 1) >= ctx->settings.max_retries) { repeat_mode_encode = 1; if (!calc_global_peak_set) { calc_global_peak_set = 1; calc_global_peak = 1; } } cyanrip_log(ctx, 0, "\nRepeating ripping (%i out of %i matches for current checksum %08X)\n", matches, ctx->settings.ripping_retries, checksum_ctx.eac_crc); last_checksums = av_realloc(last_checksums, (nb_last_checksums + 1)*sizeof(*last_checksums)); if (!last_checksums) { ret = AVERROR(ENOMEM); goto end; } last_checksums[nb_last_checksums] = checksum_ctx.eac_crc; nb_last_checksums++; int err = cyanrip_reset_encoding(ctx, t); if (err < 0) { cyanrip_log(ctx, 0, "Error in encoding: %s\n", av_err2str(err)); ret = err; goto end; } ctx->frames_read = start_frames_read; goto repeat_ripping; } finalize_ripping: cyanrip_log(NULL, 0, "\nFlushing encoders...\n"); /* Flush encoders */ ret = cyanrip_send_pcm_to_encoders(ctx, t->enc_ctx, ctx->settings.outputs_num, t->dec_ctx, NULL, 0, 0); if (ret) { cyanrip_log(ctx, 0, "Error sending flush signal to encoders: %s\n", av_err2str(ret)); return ret; } fail: if (!ret && !quit_now) cyanrip_log(ctx, 0, "Track %i ripped and encoded successfully!\n", t->number); end: av_free(last_checksums); t->total_repeats = total_repeats; if (!ret) { cyanrip_finalize_encoding(ctx, t); if (ctx->settings.enable_replaygain) crip_replaygain_meta_track(ctx, t); cyanrip_log_track_end(ctx, t); cyanrip_cue_track(ctx, t); } else { ctx->total_error_count++; } return ret; } static void on_quit_signal(int signo) { if (quit_now) { cyanrip_log(NULL, 0, "Force quitting\n"); exit(1); } cyanrip_log(NULL, 0, "\r\nTrying to quit\n"); quit_now = 1; } static void setup_track_lsn(cyanrip_ctx *ctx, cyanrip_track *t) { lsn_t first_frame = t->start_lsn; lsn_t last_frame = t->end_lsn; /* Duration doesn't depend on adjustments we make to frames */ int frames = last_frame - first_frame + 1; t->nb_samples = frames*(CDIO_CD_FRAMESIZE_RAW >> 2); /* Move the seek position coarsely */ const int extra_frames = ctx->settings.over_under_read_frames; int sign = (extra_frames < 0) ? -1 : ((extra_frames > 0) ? +1 : 0); first_frame += sign*FFMAX(FFABS(extra_frames) - 1, 0); last_frame += sign*FFMAX(FFABS(extra_frames) - 1, 0); /* Bump the lower/higher frame in the offset direction */ first_frame -= sign < 0; last_frame += sign > 0; /* Don't read into the lead in/out */ if (!ctx->settings.overread_leadinout) { t->frames_before_disc_start = FFMAX(ctx->start_lsn - first_frame, 0); t->frames_after_disc_end = FFMAX(last_frame - ctx->end_lsn, 0); first_frame += t->frames_before_disc_start; last_frame -= t->frames_after_disc_end; } else { t->frames_before_disc_start = 0; t->frames_after_disc_end = 0; } /* Offset accounted start/end sectors */ t->start_lsn = first_frame; t->end_lsn = last_frame; t->frames = last_frame - first_frame + 1; /* Last/first frame partial offset */ ptrdiff_t offs = ctx->settings.offset*4; offs -= sign*FFMAX(FFABS(extra_frames) - 1, 0)*CDIO_CD_FRAMESIZE_RAW; t->partial_frame_byte_offs = offs; } static void setup_track_offsets_and_report(cyanrip_ctx *ctx) { int gaps = 0; cyanrip_log(ctx, 0, "Gaps:\n"); /* Before pregap */ if (ctx->tracks[0].pregap_lsn != CDIO_INVALID_LSN && ctx->tracks[0].pregap_lsn > ctx->start_lsn) { cyanrip_log(ctx, 0, " %i frame gap between lead-in and track 1 pregap, merging into pregap\n", ctx->tracks[0].pregap_lsn - ctx->start_lsn); gaps++; ctx->tracks[0].pregap_lsn = ctx->start_lsn; } else if (ctx->tracks[0].pregap_lsn == CDIO_INVALID_LSN && ctx->tracks[0].start_lsn > ctx->start_lsn) { cyanrip_log(ctx, 0, " %i frame unmarked gap between lead-in and track 1, marking as a pregap\n", ctx->tracks[0].start_lsn - ctx->start_lsn); gaps++; ctx->tracks[0].pregap_lsn = ctx->start_lsn; } /* Pregaps */ for (int i = 0; i < ctx->nb_tracks; i++) { cyanrip_track *ct = &ctx->tracks[i - 0]; cyanrip_track *lt = ct->number > 1 ? &ctx->tracks[i - 1] : NULL; if (ct->pregap_lsn == CDIO_INVALID_LSN) continue; cyanrip_log(ctx, 0, " %i frame pregap in track %i, ", ct->start_lsn - ct->pregap_lsn, ct->number); gaps++; switch (ctx->settings.pregap_action[ct->number - 1]) { case CYANRIP_PREGAP_DEFAULT: if (!lt) cyanrip_log(ctx, 0, "unmerged\n"); else cyanrip_log(ctx, 0, "merging into track %i\n", lt->number); if ((ct->number - 1) == 0) ct->dropped_pregap_start = ct->pregap_lsn; break; case CYANRIP_PREGAP_DROP: cyanrip_log(ctx, 0, "dropping\n"); ct->dropped_pregap_start = ct->pregap_lsn; if (lt) lt->end_lsn = ct->pregap_lsn - 1; break; case CYANRIP_PREGAP_MERGE: cyanrip_log(ctx, 0, "merging\n"); ct->merged_pregap_end = ct->start_lsn; ct->start_lsn = ct->pregap_lsn; if (lt) lt->end_lsn = ct->pregap_lsn - 1; break; case CYANRIP_PREGAP_TRACK: cyanrip_log(ctx, 0, "splitting off into a new track, number %i\n", ct->number > 1 ? ct->number : 0); if (lt) lt->end_lsn = ct->pregap_lsn - 1; if (ct->number > 1) /* Push all track numbers up if needed */ for (int j = i; j < ctx->nb_tracks; j++) ctx->tracks[j].number++; memmove(&ctx->tracks[i + 1], &ctx->tracks[i], sizeof(cyanrip_track)*(ctx->nb_tracks - i)); ct = &ctx->tracks[i + 1]; ctx->nb_tracks++; cyanrip_track *nt = &ctx->tracks[i]; memset(nt, 0, sizeof(*nt)); nt->number = ct->number - 1; nt->pregap_lsn = CDIO_INVALID_LSN; nt->dropped_pregap_start = CDIO_INVALID_LSN; nt->merged_pregap_end = CDIO_INVALID_LSN; nt->start_lsn = ct->pregap_lsn; nt->end_lsn = ct->start_lsn - 1; nt->cd_track_number = ct->cd_track_number; ct->pregap_lsn = CDIO_INVALID_LSN; } } /* Between tracks */ for (int i = 1; i < ctx->nb_tracks; i++) { cyanrip_track *ct = &ctx->tracks[i - 0]; cyanrip_track *lt = &ctx->tracks[i - 1]; if (ct->start_lsn == (lt->end_lsn + 1)) continue; int discont_frames = ct->start_lsn - lt->end_lsn; cyanrip_log(ctx, 0, " %i frame discontinuity between tracks %i and %i, ", discont_frames, ct->number, lt->number); gaps++; if (ctx->settings.pregap_action[ct->number - 1] != CYANRIP_PREGAP_DROP) { cyanrip_log(ctx, 0, "padding track %i\n", lt->number); lt->end_lsn = ct->start_lsn - 1; } else { cyanrip_log(ctx, 0, "ignoring\n"); } } /* After last track */ cyanrip_track *lt = &ctx->tracks[ctx->nb_tracks - 1]; if (ctx->end_lsn > lt->end_lsn && !lt->track_is_data) { int discont_frames = ctx->end_lsn - lt->end_lsn; cyanrip_log(ctx, 0, " %i frame gap between last track and lead-out, padding track\n", discont_frames); gaps++; lt->end_lsn = ctx->end_lsn; } /* Finally set up the internals with the set start_lsn/end_lsn */ for (int i = 0; i < ctx->nb_tracks; i++) { cyanrip_track *t = &ctx->tracks[i]; if (t->track_is_data) t->frames = t->end_lsn - t->start_lsn + 1; else setup_track_lsn(ctx, t); } /* Setup next/previous pointers and redo track indices */ for (int i = 0; i < ctx->nb_tracks; i++) { cyanrip_track *t = &ctx->tracks[i]; t->index = i + 1; t->pt = i ? &ctx->tracks[i - 1] : NULL; t->nt = i != (ctx->nb_tracks - 1) ? &ctx->tracks[i + 1] : NULL; } cyanrip_log(ctx, 0, "%s\n", gaps ? "" : " None signalled\n"); } /* Key 1 and 2 must be set, and src will be modified */ static char *append_missing_keys(char *src, const char *key1, const char *key2) { /* Copy string with enough space to append extra */ char *copy = av_mallocz(strlen(src) + strlen(key1) + strlen(key2) + 1); memcpy(copy, src, strlen(src)); int add_key1_offset = -1; int add_key2_offset = -1; /* Look for keyless entries */ int count = 0; char *p_save, *p = av_strtok(src, ":", &p_save); while (p) { if (!strstr(p, "=")) { if (count == 0) add_key1_offset = p - src; else if (count == 1) add_key2_offset = p - src; } p = av_strtok(NULL, ":", &p_save); if (++count >= 2) break; } /* Prepend key1 if missing */ if (add_key1_offset >= 0) { memmove(©[add_key1_offset + strlen(key1)], ©[add_key1_offset], strlen(copy) - add_key1_offset); memcpy(©[add_key1_offset], key1, strlen(key1)); if (add_key2_offset >= 0) add_key2_offset += strlen(key1); } /* Prepend key2 if missing */ if (add_key2_offset >= 0) { memmove(©[add_key2_offset + strlen(key2)], ©[add_key2_offset], strlen(copy) - add_key2_offset); memcpy(©[add_key2_offset], key2, strlen(key2)); } return copy; } static inline int crip_is_integer(const char *src) { for (int i = 0; i < strlen(src); i++) if (!av_isdigit(src[i])) return 0; return 1; } static int add_to_dir_list(char ***dir_list, int *dir_list_nb, const char *src) { int nb = *dir_list_nb; char **new_ptr = av_realloc(*dir_list, (nb + 1) * sizeof(*new_ptr)); if (!new_ptr) return AVERROR(ENOMEM); new_ptr[nb] = av_strdup(src); nb++; *dir_list = new_ptr; *dir_list_nb = nb; return 0; } struct CRIPCharReplacement { const char from; const char to; const char to_u[5]; int is_avail_locally; } crip_char_replacement[] = { { '<', '_', "‹", HAS_CH_LESS }, { '>', '_', "›", HAS_CH_MORE }, { ':', '_', "∶", HAS_CH_COLUMN }, { '|', '_', "│", HAS_CH_OR }, { '?', '_', "?", HAS_CH_Q }, { '*', '_', "∗", HAS_CH_ANY }, { '/', '_', "∕", HAS_CH_FWDSLASH }, { '\\', '_', "⧹", HAS_CH_BWDSLASH }, { '"', '\'', "“", HAS_CH_QUOTES }, { '"', '\'', "”", HAS_CH_QUOTES }, { 0 }, }; static int crip_bprint_sanitize(cyanrip_ctx *ctx, AVBPrint *buf, const char *str, char ***dir_list, int *dir_list_nb, int sanitize_fwdslash) { int32_t cp, ret, quote_match = 0; const char *pos = str, *end = str + strlen(str); int os_sanitize = (ctx->settings.sanitize_method == CRIP_SANITIZE_OS_SIMPLE) || (ctx->settings.sanitize_method == CRIP_SANITIZE_OS_UNICODE); while (str < end) { ret = av_utf8_decode(&cp, (const uint8_t **)&str, end, AV_UTF8_FLAG_ACCEPT_ALL); if (ret < 0) { cyanrip_log(ctx, 0, "Error parsing string: %s!\n", av_err2str(ret)); return ret; } struct CRIPCharReplacement *rep = NULL; for (int i = 0; crip_char_replacement[i].from; i++) { if (cp == crip_char_replacement[i].from) { int is_quote = crip_char_replacement[i].from == '"'; rep = &crip_char_replacement[i + (is_quote && quote_match)]; quote_match = (quote_match + 1) & 1; break; } } int skip = !rep; int skip_sanitation = rep && (os_sanitize && rep->is_avail_locally); int passthrough_slash = rep && !skip_sanitation && (rep->from == OS_DIR_CHAR && !sanitize_fwdslash); if (skip || skip_sanitation || passthrough_slash) { if (passthrough_slash) add_to_dir_list(dir_list, dir_list_nb, buf->str); av_bprint_append_data(buf, pos, str - pos); pos = str; continue; } else if (ctx->settings.sanitize_method == CRIP_SANITIZE_SIMPLE || ctx->settings.sanitize_method == CRIP_SANITIZE_OS_SIMPLE) { av_bprint_chars(buf, rep->to, 1); } else if (ctx->settings.sanitize_method == CRIP_SANITIZE_UNICODE || ctx->settings.sanitize_method == CRIP_SANITIZE_OS_UNICODE) { av_bprint_append_data(buf, rep->to_u, strlen(rep->to_u)); } pos = str; } return 0; } static char *get_dir_tag_val(cyanrip_ctx *ctx, AVDictionary *meta, const char *ofmt, const char *key) { char *val = NULL; if (!strcmp(key, "year")) { const char *date = dict_get(meta, "date"); if (date) { char *save_year, *date_dup = av_strdup(date); val = av_strdup(av_strtok(date_dup, ":-", &save_year)); av_free(date_dup); } } else if (!strcmp(key, "format")) { val = av_strdup(ofmt); } else if (!strcmp(key, "track")) { const char *track = dict_get(meta, "track"); if (crip_is_integer(track)) { int pad = 0, digits = strlen(track); if (((digits + pad) < 2) && ctx->nb_tracks > 9) pad++; if (((digits + pad) < 3) && ctx->nb_tracks > 99) pad++; val = av_mallocz(pad + digits + 1); for (int i = 0; i < pad; i++) val[i] = '0'; memcpy(&val[pad], track, digits); } else { val = av_strdup(track); } } else { val = av_strdup(dict_get(meta, key)); } return val; } static int process_cond(cyanrip_ctx *ctx, AVBPrint *buf, AVDictionary *meta, const char *ofmt, char ***dir_list, int *dir_list_nb, const char *scheme) { char *scheme_copy = av_strdup(scheme); char *save, *tok = av_strtok(scheme_copy, "{}", &save); while (tok) { if (!strncmp(tok, "if", strlen("if"))) { char *cond = av_strdup(tok); char *cond_save, *cond_tok = av_strtok(cond, "#", &cond_save); cond_tok = av_strtok(NULL, "#", &cond_save); if (!cond_tok) { cyanrip_log(ctx, 0, "Invalid scheme syntax, no \"#\"!\n"); av_free(cond); goto fail; } int val1_origin_is_tag = 1; char *val1 = get_dir_tag_val(ctx, meta, ofmt, cond_tok); if (!val1) { val1 = av_strdup(tok); val1_origin_is_tag = 0; } cond_tok = av_strtok(NULL, "#", &cond_save); if (!cond_tok) { cyanrip_log(ctx, 0, "Invalid scheme syntax, no terminating \"#\"!\n"); av_free(cond); av_free(val1); goto fail; } int cond_is_eq = 0, cond_is_not_eq = 0, cond_is_more = 0, cond_is_less = 0; if (strstr(cond_tok, "==")) { cond_is_eq = 1; } else if (strstr(cond_tok, "!=")) { cond_is_not_eq = 1; } else if (strstr(cond_tok, ">")) { cond_is_more = 1; } else if (strstr(cond_tok, "<")) { cond_is_less = 1; } else { cyanrip_log(ctx, 0, "Invalid condition syntax!\n"); av_free(cond); av_free(val1); goto fail; } cond_tok = av_strtok(NULL, "#", &cond_save); if (!cond_tok) { cyanrip_log(ctx, 0, "Invalid scheme syntax, no terminating \"#\"!\n"); goto fail; } int val2_origin_is_tag = 1; char *val2 = get_dir_tag_val(ctx, meta, ofmt, cond_tok); if (!val2) { val2 = av_strdup(cond_tok); val2_origin_is_tag = 0; } cond_tok = av_strtok(NULL, "#", &cond_save); if (!cond_tok) { cyanrip_log(ctx, 0, "Invalid scheme syntax, no terminating \"#\"!\n"); goto fail; } int cond_true = 0; cond_true |= cond_is_eq && !strcmp(val1, val2); cond_true |= cond_is_not_eq && strcmp(val1, val2); if (cond_is_less || cond_is_more) { int val1_is_int = crip_is_integer(val1), val2_is_int = crip_is_integer(val2); if (!val1_is_int && (val1_is_int == val2_is_int)) { /* None are int */ cond_true = cond_is_less ? (strcmp(val1, val2) < 0) : (cond_is_more ? strcmp(val1, val2) > 0 : 0); } else if (val1_is_int && (val1_is_int == val2_is_int)) { /* Both are int */ int64_t val1_dec = strtol(val1, NULL, 10); int64_t val2_dec = strtol(val2, NULL, 10); cond_true |= cond_is_less && val1_dec < val2_dec; cond_true |= cond_is_more && val1_dec > val2_dec; } else { ptrdiff_t val1_dec = val1_is_int ? strtol(val1, NULL, 10) : (!val1_origin_is_tag ? 0 : (ptrdiff_t)val1); ptrdiff_t val2_dec = val2_is_int ? strtol(val2, NULL, 10) : (!val2_origin_is_tag ? 0 : (ptrdiff_t)val2); cond_true |= cond_is_less && val1_dec < val2_dec; cond_true |= cond_is_more && val1_dec > val2_dec; } } if (cond_true) { char *true_save, *true_tok = av_strtok(cond_tok, "|", &true_save); while (true_tok) { int origin_is_tag = 1; char *true_val = get_dir_tag_val(ctx, meta, ofmt, true_tok); if (!true_val) { true_val = av_strdup(true_tok); origin_is_tag = 0; } crip_bprint_sanitize(ctx, buf, true_val, dir_list, dir_list_nb, origin_is_tag); av_free(true_val); true_tok = av_strtok(NULL, "|", &true_save); } } av_free(val2); av_free(val1); av_free(cond); tok = av_strtok(NULL, "{}", &save); continue; } int origin_is_tag = 1; char *val = get_dir_tag_val(ctx, meta, ofmt, tok); if (!val) { val = av_strdup(tok); origin_is_tag = 0; } crip_bprint_sanitize(ctx, buf, val, dir_list, dir_list_nb, origin_is_tag); av_free(val); tok = av_strtok(NULL, "{}", &save); } av_free(scheme_copy); return 0; fail: av_free(scheme_copy); return AVERROR(EINVAL); } char *crip_get_path(cyanrip_ctx *ctx, enum CRIPPathType type, int create_dirs, const cyanrip_out_fmt *fmt, void *arg) { char *ret = NULL; AVBPrint buf; char **dir_list = NULL; int dir_list_nb = 0; av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); if (process_cond(ctx, &buf, ctx->meta, fmt->folder_suffix, &dir_list, &dir_list_nb, ctx->settings.folder_name_scheme)) goto end; add_to_dir_list(&dir_list, &dir_list_nb, buf.str); av_bprint_chars(&buf, OS_DIR_CHAR, 1); char *ext = NULL; if (type == CRIP_PATH_COVERART) { CRIPArt *art = arg; crip_bprint_sanitize(ctx, &buf, dict_get(art->meta, "title"), &dir_list, &dir_list_nb, 0); ext = art->extension ? av_strdup(art->extension) : av_strdup(""); } else if (type == CRIP_PATH_LOG) { if (process_cond(ctx, &buf, ctx->meta, fmt->name, &dir_list, &dir_list_nb, ctx->settings.log_name_scheme)) goto end; ext = av_strdup("log"); } else if (type == CRIP_PATH_CUE) { if (process_cond(ctx, &buf, ctx->meta, fmt->name, &dir_list, &dir_list_nb, ctx->settings.cue_name_scheme)) goto end; ext = av_strdup("cue"); } else { cyanrip_track *t = arg; if (process_cond(ctx, &buf, t->meta, fmt->name, &dir_list, &dir_list_nb, ctx->settings.track_name_scheme)) goto end; ext = av_strdup(t->track_is_data ? "bin" : fmt->ext); } if (ext) av_bprintf(&buf, ".%s", ext); av_free(ext); end: for (int i = 0; i < dir_list_nb; i++) { if (create_dirs) { struct stat st_req = { 0 }; if (crip_stat(dir_list[i], &st_req) == -1) mkdir(dir_list[i], 0700); } av_free(dir_list[i]); } av_free(dir_list); av_bprint_finalize(&buf, &ret); return ret; } int main(int argc, char **argv) { cyanrip_ctx *ctx = NULL; cyanrip_settings settings = { 0 }; av_log_set_level(AV_LOG_QUIET); if (signal(SIGINT, on_quit_signal) == SIG_ERR) cyanrip_log(ctx, 0, "Can't init signal handler!\n"); /* Default settings */ settings.dev_path = NULL; settings.folder_name_scheme = "{album}{if #releasecomment# > #0# (|releasecomment|)} [{format}]"; settings.track_name_scheme = "{if #totaldiscs# > #1#|disc|.}{track} - {title}"; settings.log_name_scheme = "{album}{if #totaldiscs# > #1# CD|disc|}"; settings.cue_name_scheme = "{album}{if #totaldiscs# > #1# CD|disc|}"; settings.sanitize_method = CRIP_SANITIZE_UNICODE; settings.speed = 0; settings.max_retries = 10; settings.over_under_read_frames = 0; settings.offset = 0; settings.ripping_retries = 0; settings.print_info_only = 0; settings.disable_mb = 0; settings.disable_coverart_db = 0; settings.decode_hdcd = 0; settings.deemphasis = 1; settings.force_deemphasis = 0; settings.bitrate = 256.0f; settings.overread_leadinout = 0; settings.rip_indices_count = -1; settings.disable_accurip = 0; settings.eject_on_success_rip = 0; settings.outputs[0] = CYANRIP_FORMAT_FLAC; settings.outputs_num = 1; settings.disable_coverart_embedding = 0; settings.enable_replaygain = 1; settings.paranoia_level = FF_ARRAY_ELEMS(paranoia_level_map) - 1; memset(settings.pregap_action, CYANRIP_PREGAP_DEFAULT, sizeof(settings.pregap_action)); int c, idx; char *p_save, *p; int mb_release_idx = -1; char *mb_release_str = NULL; int discnumber = 0, totaldiscs = 0; char *album_metadata_ptr = NULL; char *track_metadata_ptr[198] = { NULL }; int track_metadata_ptr_cnt = 0; int find_drive_offset_range = 0; int offset_set = 0; CRIPArt cover_arts[32] = { 0 }; int nb_cover_arts = 0; CRIPArt track_cover_arts[198] = { 0 }; int track_cover_arts_map[198] = { 0 }; int nb_track_cover_arts = 0; while ((c = getopt(argc, argv, "hNAUfHIVQEGWKOl:a:t:b:c:r:d:o:s:S:D:p:C:R:P:F:L:T:M:Z:")) != -1) { switch (c) { case 'h': cyanrip_log(ctx, 0, "cyanrip %s (%s) help:\n", PROJECT_VERSION_STRING, vcstag); cyanrip_log(ctx, 0, "\n Ripping options:\n"); cyanrip_log(ctx, 0, " -d Set device path\n"); cyanrip_log(ctx, 0, " -s CD Drive offset in samples (default: 0)\n"); cyanrip_log(ctx, 0, " -r Maximum number of retries for frames and repeated rips (default: 10)\n"); cyanrip_log(ctx, 0, " -Z Rips tracks until their checksums match number of times. For very damaged CDs.\n"); cyanrip_log(ctx, 0, " -S Set drive speed (default: unset)\n"); cyanrip_log(ctx, 0, " -p = Track pregap handling (default: default)\n"); cyanrip_log(ctx, 0, " -P Paranoia level, %i to 0 inclusive, default: %i\n", crip_max_paranoia_level, settings.paranoia_level); cyanrip_log(ctx, 0, " -O Enable overreading into lead-in and lead-out (may freeze if unsupported by drive)\n"); cyanrip_log(ctx, 0, " -H Enable HDCD decoding. Do this if you're sure disc is HDCD\n"); cyanrip_log(ctx, 0, " -E Force CD deemphasis\n"); cyanrip_log(ctx, 0, " -W Disable automatic CD deemphasis\n"); cyanrip_log(ctx, 0, " -K Disable ReplayGain tagging\n"); cyanrip_log(ctx, 0, "\n Output options:\n"); cyanrip_log(ctx, 0, " -o Comma separated list of outputs\n"); cyanrip_log(ctx, 0, " -b Bitrate of lossy files in kbps\n"); cyanrip_log(ctx, 0, " -D Directory naming scheme, by default its \"%s\"\n", settings.folder_name_scheme); cyanrip_log(ctx, 0, " -F Track naming scheme, by default its \"%s\"\n", settings.track_name_scheme); cyanrip_log(ctx, 0, " -L Log file name scheme, by default its \"%s\"\n", settings.log_name_scheme); cyanrip_log(ctx, 0, " -M CUE file name scheme, by default its \"%s\"\n", settings.cue_name_scheme); cyanrip_log(ctx, 0, " -l Select which tracks to rip (default: all)\n"); cyanrip_log(ctx, 0, " -T Filename sanitation: simple, os_simple, unicode (default), os_unicode\n"); cyanrip_log(ctx, 0, "\n Metadata options:\n"); cyanrip_log(ctx, 0, " -I Only print CD and track info\n"); cyanrip_log(ctx, 0, " -a Album metadata, key=value:key=value\n"); cyanrip_log(ctx, 0, " -t = Track metadata, can be specified multiple times\n"); cyanrip_log(ctx, 0, " -R / Sets the MusicBrainz release to use, either as an index starting from 1 or an ID string\n"); cyanrip_log(ctx, 0, " -c / Tag multi-disc albums, syntax is disc/totaldiscs\n"); cyanrip_log(ctx, 0, " -C =<path> Set cover image path, type may be \"Name\" or a \"track_number\"\n"); cyanrip_log(ctx, 0, " -N Disables MusicBrainz lookup and ignores lack of manual metadata\n"); cyanrip_log(ctx, 0, " -A Disables AccurateRip database query and validation\n"); cyanrip_log(ctx, 0, " -U Disables Cover art DB database query and retrieval\n"); cyanrip_log(ctx, 0, " -G Disables embedding of cover art images\n"); cyanrip_log(ctx, 0, "\n Misc. options:\n"); cyanrip_log(ctx, 0, " -Q Eject tray once successfully done\n"); cyanrip_log(ctx, 0, " -V Print program version\n"); cyanrip_log(ctx, 0, " -h Print options help\n"); cyanrip_log(ctx, 0, " -f Find drive offset (requires a disc with an AccuRip DB entry)\n"); return 0; break; case 'S': settings.speed = (int)strtol(optarg, NULL, 10); if (settings.speed < 0) { cyanrip_log(ctx, 0, "Invalid drive speed!\n"); return 1; } break; case 'P': if (!strcmp(optarg, "none")) settings.paranoia_level = 0; else if (!strcmp(optarg, "max")) settings.paranoia_level = crip_max_paranoia_level; else settings.paranoia_level = (int)strtol(optarg, NULL, 10); if (settings.paranoia_level < 0 || settings.paranoia_level > crip_max_paranoia_level) { cyanrip_log(ctx, 0, "Invalid paranoia level %i must be between 0 and %i!\n", settings.paranoia_level, crip_max_paranoia_level); return 1; } break; case 'r': settings.max_retries = strtol(optarg, NULL, 10); if (settings.max_retries < 0) { cyanrip_log(ctx, 0, "Invalid retries amount!\n"); return 1; } break; case 'R': p = NULL; mb_release_idx = strtol(optarg, &p, 10); if (p != NULL && p[0] != ' ' && p[0] != '\0') { mb_release_str = optarg; mb_release_idx = -1; } else if (mb_release_idx <= 0) { cyanrip_log(ctx, 0, "Invalid release index %i!\n", mb_release_idx); return 1; } break; case 'K': settings.enable_replaygain = 0; break; case 's': settings.offset = strtol(optarg, NULL, 10); int sign = settings.offset < 0 ? -1 : +1; int frames = ceilf(abs(settings.offset)/(float)(CDIO_CD_FRAMESIZE_RAW >> 2)); settings.over_under_read_frames = sign*frames; offset_set = 1; break; case 'N': settings.disable_mb = 1; break; case 'A': settings.disable_accurip = 1; break; case 'U': settings.disable_coverart_db = 1; break; case 'b': settings.bitrate = strtof(optarg, NULL); break; case 'l': settings.rip_indices_count = 0; p = av_strtok(optarg, ",", &p_save); while (p) { idx = strtol(p, NULL, 10); for (int i = 0; i < settings.rip_indices_count; i++) { if (settings.rip_indices[i] == idx) { cyanrip_log(ctx, 0, "Duplicated rip idx %i\n", idx); return 1; } } settings.rip_indices[settings.rip_indices_count++] = idx; p = av_strtok(NULL, ",", &p_save); } qsort(settings.rip_indices, settings.rip_indices_count, sizeof(int), cmp_numbers); break; case 'o': settings.outputs_num = 0; if (!strncmp("help", optarg, strlen("help"))) { cyanrip_log(ctx, 0, "Supported output codecs:\n"); cyanrip_print_codecs(); return 0; } p = av_strtok(optarg, ",", &p_save); while (p) { int res = cyanrip_validate_fmt(p); for (int i = 0; i < settings.outputs_num; i++) { if (settings.outputs[i] == res) { cyanrip_log(ctx, 0, "Duplicated format \"%s\"\n", p); return 1; } } if (res != -1) { settings.outputs[settings.outputs_num++] = res; } else { cyanrip_log(ctx, 0, "Invalid format \"%s\"\n", p); return 1; } p = av_strtok(NULL, ",", &p_save); } break; case 'I': settings.print_info_only = 1; break; case 'G': settings.disable_coverart_embedding = 1; break; case 'H': settings.decode_hdcd = 1; break; case 'O': settings.overread_leadinout = 1; break; case 'Z': settings.ripping_retries = strtol(optarg, NULL, 10); if (settings.ripping_retries < 0) { cyanrip_log(ctx, 0, "Invalid retries amount!\n"); return 1; } break; case 'f': find_drive_offset_range = 6; break; case 'c': p = av_strtok(optarg, "/", &p_save); discnumber = strtol(p, NULL, 10); if (discnumber <= 0) { cyanrip_log(ctx, 0, "Invalid discnumber %i\n", discnumber); return 1; } p = av_strtok(NULL, "/", &p_save); if (!p) break; totaldiscs = strtol(p, NULL, 10); if (totaldiscs <= 0) { cyanrip_log(ctx, 0, "Invalid totaldiscs %i\n", totaldiscs); return 1; } if (discnumber > totaldiscs) { cyanrip_log(ctx, 0, "discnumber %i is larger than totaldiscs %i\n", discnumber, totaldiscs); return 1; } break; case 'p': p = av_strtok(optarg, "=", &p_save); idx = strtol(p, NULL, 10); if (idx < 1 || idx > 197) { cyanrip_log(ctx, 0, "Invalid track idx for pregap: %i\n", idx); return 1; } enum cyanrip_pregap_action act = CYANRIP_PREGAP_DEFAULT; p = av_strtok(NULL, "=", &p_save); if (!p) { cyanrip_log(ctx, 0, "Missing pregap action\n"); return 1; } if (!strncmp(p, "default", strlen("default"))) { act = CYANRIP_PREGAP_DEFAULT; } else if (!strncmp(p, "drop", strlen("drop"))) { act = CYANRIP_PREGAP_DROP; } else if (!strncmp(p, "merge", strlen("merge"))) { act = CYANRIP_PREGAP_MERGE; } else if (!strncmp(p, "track", strlen("track"))) { act = CYANRIP_PREGAP_TRACK; } else { cyanrip_log(ctx, 0, "Invalid pregap action %s\n", p); return 1; } settings.pregap_action[idx - 1] = act; break; case 'C': p = av_strtok(optarg, "=", &p_save); char *next = av_strtok(NULL, "=", &p_save); CRIPArt *dst = NULL; if (!next) { int have_front = 0; int have_back = 0; for (int i = 0; i < nb_cover_arts; i++) { if (!strcmp(cover_arts[i].title, "Front")) have_front = 1; if (!strcmp(cover_arts[i].title, "Back")) have_back = 1; } if (!have_front) { next = p; p = "Front"; } else if (!have_back) { next = p; p = "Back"; } else { cyanrip_log(ctx, 0, "No cover art location specified for \"%s\"\n", p); return 1; } } if (crip_is_integer(p)) { idx = strtol(p, NULL, 10); if (idx < 0 || idx > 198) { cyanrip_log(ctx, 0, "Invalid track idx for cover art: %i\n", idx); return 1; } for (int i = 0; i < nb_track_cover_arts; i++) { if (track_cover_arts_map[i] == idx) { cyanrip_log(ctx, 0, "Cover art already specified for track idx %i!\n", idx); return 1; } } track_cover_arts_map[nb_track_cover_arts] = idx; dst = &track_cover_arts[nb_track_cover_arts++]; p = "title"; } else { for (int i = 0; i < nb_cover_arts; i++) { if (!strcmp(cover_arts[i].title, p)) { cyanrip_log(ctx, 0, "Cover art \"%s\" already specified!\n", p); return 1; } } dst = &cover_arts[nb_cover_arts++]; if (nb_cover_arts > 31) { cyanrip_log(ctx, 0, "Too many cover arts specified!\n"); return 1; } } dst->source_url = next; dst->title = p; break; case 'E': settings.force_deemphasis = 1; break; case 'W': settings.deemphasis = 0; break; case 'Q': settings.eject_on_success_rip = 1; break; case 'D': settings.folder_name_scheme = optarg; break; case 'F': settings.track_name_scheme = optarg; break; case 'L': settings.log_name_scheme = optarg; break; case 'M': settings.cue_name_scheme = optarg; break; case 'T': if (!strncmp(optarg, "simple", strlen("simple"))) { settings.sanitize_method = CRIP_SANITIZE_SIMPLE; } else if (!strncmp(optarg, "os_simple", strlen("os_simple"))) { settings.sanitize_method = CRIP_SANITIZE_OS_SIMPLE; } else if (!strncmp(optarg, "unicode", strlen("unicode"))) { settings.sanitize_method = CRIP_SANITIZE_UNICODE; } else if (!strncmp(optarg, "os_unicode", strlen("os_unicode"))) { settings.sanitize_method = CRIP_SANITIZE_OS_UNICODE; } else { cyanrip_log(ctx, 0, "Invalid sanitation method %s\n", optarg); return 1; } break; case 'V': cyanrip_log(ctx, 0, "cyanrip %s (%s)\n", PROJECT_VERSION_STRING, vcstag); return 0; case 'd': settings.dev_path = strdup(optarg); break; case '?': return 1; break; case 'a': album_metadata_ptr = optarg; break; case 't': track_metadata_ptr[track_metadata_ptr_cnt++] = optarg; break; default: abort(); break; } } if (settings.outputs_num > 1 && !strstr(settings.folder_name_scheme, "{format}")) { cyanrip_log(ctx, 0, "Directory name scheme must contain {format} with multiple output formats!\n"); return 1; } if (find_drive_offset_range) { settings.disable_accurip = 0; settings.disable_mb = 1; settings.disable_coverart_db = 1; settings.offset = 0; settings.eject_on_success_rip = 0; cyanrip_log(ctx, 0, "Searching for drive offset, enabling AccuRip and disabling MusicBrainz and Cover art fetching...\n"); } if (cyanrip_ctx_init(&ctx, &settings)) return 1; if (!settings.offset && !offset_set && !find_drive_offset_range && (ctx->rcap & CDIO_DRIVE_CAP_READ_ISRC)) { cyanrip_log(ctx, 0, "Offset is unset! To continue with an offset of 0, run with -s 0!\n"); goto end; } /* Fill disc MCN */ crip_fill_mcn(ctx); /* Fill discid */ if (crip_fill_discid(ctx)) { ctx->total_error_count++; goto end; } /* Default album title */ av_dict_set(&ctx->meta, "album", "Unknown disc", 0); av_dict_set(&ctx->meta, "comment", "cyanrip "PROJECT_VERSION_STRING, 0); av_dict_set(&ctx->meta, "media_type", ctx->settings.decode_hdcd ? "HDCD" : "CD", 0); const char *barcode_id = dict_get(ctx->meta, "barcode"); const char *mcn_id = dict_get(ctx->meta, "disc_mcn"); const char *did_id = dict_get(ctx->meta, "discid"); if (barcode_id || mcn_id || did_id) { char fourcc_id[5] = { '0', '0', '0', '0', '\0' }; const char *id = NULL; /* Try the barcode first */ if (barcode_id) id = barcode_id; /* Try MCN */ if (!id && mcn_id) { /* Check MCN is not all zeroes (this happens often) */ for (int i = 0; i < strlen(mcn_id); i++) { if (mcn_id[i] != '0') { id = mcn_id; break; } } } /* Otherwise just grab the discid */ if (!id) id = did_id; strncpy(fourcc_id, id, 4); for (int i = 0; i < 4; i++) fourcc_id[i] = av_toupper(fourcc_id[i]); av_dict_set(&ctx->meta, "album", " (", AV_DICT_APPEND); av_dict_set(&ctx->meta, "album", fourcc_id, AV_DICT_APPEND); av_dict_set(&ctx->meta, "album", ")", AV_DICT_APPEND); } /* Set default track title */ for (int i = 0; i < ctx->nb_tracks; i++) av_dict_set(&ctx->meta, "title", "Unknown track", 0); /* Fill musicbrainz metadata */ if (crip_fill_metadata(ctx, !!album_metadata_ptr || track_metadata_ptr_cnt, mb_release_idx, mb_release_str, discnumber)) { ctx->total_error_count++; goto end; } /* Print this for easy access */ if (ctx->settings.print_info_only) cyanrip_log(ctx, 0, "MusicBrainz URL:\n%s\n", ctx->mb_submission_url); /* Copy album cover arts */ ctx->nb_cover_arts = nb_cover_arts; for (int i = 0; i < nb_cover_arts; i++) { ctx->cover_arts[i].source_url = av_strdup(cover_arts[i].source_url); av_dict_set(&ctx->cover_arts[i].meta, "title", cover_arts[i].title, 0); } /* Album cover art (down)loading, and DB quering */ if (crip_fill_coverart(ctx, ctx->settings.print_info_only) < 0) { ctx->total_error_count++; goto end; } /* Fill in accurip data */ if (crip_fill_accurip(ctx)) { ctx->total_error_count++; goto end; } if (find_drive_offset_range) { search_for_drive_offset(ctx, find_drive_offset_range); goto end; } if (mb_release_str && !dict_get(ctx->meta, "release_id")) av_dict_set(&ctx->meta, "release_id", mb_release_str, 0); if (discnumber) av_dict_set_int(&ctx->meta, "disc", discnumber, 0); if (totaldiscs) av_dict_set_int(&ctx->meta, "totaldiscs", totaldiscs, 0); /* Read user album metadata */ if (album_metadata_ptr) { /* Fixup */ char *copy = append_missing_keys(album_metadata_ptr, "album=", "album_artist="); /* Parse */ int err = av_dict_parse_string(&ctx->meta, copy, "=", ":", 0); av_free(copy); if (err) { cyanrip_log(ctx, 0, "Error reading album tags: %s\n", av_err2str(err)); ctx->total_error_count++; goto end; } /* Fixup title tag mistake */ const char *title = dict_get(ctx->meta, "title"); const char *album = dict_get(ctx->meta, "album"); if (title && !album) { av_dict_set(&ctx->meta, "album", title, 0); av_dict_set(&ctx->meta, "title", "", 0); } /* Populate artist tag if missing/unspecified */ const char *album_artist = dict_get(ctx->meta, "album_artist"); const char *artist = dict_get(ctx->meta, "artist"); if (album_artist && !artist) av_dict_set(&ctx->meta, "artist", album_artist, 0); else if (artist && !album_artist) av_dict_set(&ctx->meta, "album_artist", artist, 0); } /* Create log file */ if (!ctx->settings.print_info_only) { if (cyanrip_log_init(ctx) < 0) return 1; if (cyanrip_cue_init(ctx) < 0) return 1; } else { cyanrip_log(ctx, 0, "Log(s) will be written to:\n"); for (int f = 0; f < ctx->settings.outputs_num; f++) { char *logfile = crip_get_path(ctx, CRIP_PATH_LOG, 0, &crip_fmt_info[ctx->settings.outputs[f]], NULL); cyanrip_log(ctx, 0, " %s\n", logfile); av_free(logfile); } cyanrip_log(ctx, 0, "CUE files will be written to:\n"); for (int f = 0; f < ctx->settings.outputs_num; f++) { char *cuefile = crip_get_path(ctx, CRIP_PATH_CUE, 0, &crip_fmt_info[ctx->settings.outputs[f]], NULL); cyanrip_log(ctx, 0, " %s\n", cuefile); av_free(cuefile); } } cyanrip_log_start_report(ctx); if (!ctx->settings.print_info_only) cyanrip_cue_start(ctx); setup_track_offsets_and_report(ctx); copy_album_to_track_meta(ctx); /* Read user track metadata */ for (int i = 0; i < track_metadata_ptr_cnt; i++) { if (!track_metadata_ptr[i]) continue; char *end = NULL; int u_nb = strtol(track_metadata_ptr[i], &end, 10); /* Verify all indices */ int track_idx = 0; for (; track_idx < ctx->nb_tracks; track_idx++) { if (ctx->tracks[track_idx].number == u_nb) break; } if (track_idx >= ctx->nb_tracks) { cyanrip_log(ctx, 0, "Invalid track number %i, list has %i tracks!\n", u_nb, ctx->nb_tracks); ctx->total_error_count++; goto end; } end += 1; /* Move past equal sign */ /* Fixup */ char *copy = append_missing_keys(end, "title=", "artist="); /* Parse */ int err = av_dict_parse_string(&ctx->tracks[track_idx].meta, copy, "=", ":", 0); av_free(copy); if (err) { cyanrip_log(ctx, 0, "Error reading track tags: %s\n", av_err2str(err)); ctx->total_error_count++; goto end; } } /* Copy track cover arts */ for (int i = 0; i < nb_track_cover_arts; i++) { idx = track_cover_arts_map[i]; int track_idx = 0; for (; track_idx < ctx->nb_tracks; track_idx++) { if (ctx->tracks[track_idx].number == idx) break; } if (track_idx >= ctx->nb_tracks) { cyanrip_log(ctx, 0, "Invalid track number %i, list has %i tracks!\n", idx, ctx->nb_tracks); ctx->total_error_count++; goto end; } ctx->tracks[track_idx].art.source_url = av_strdup(track_cover_arts[i].source_url); av_dict_set(&ctx->tracks[track_idx].art.meta, "title", "Front", 0); } /* Track cover art (down)loading */ if (crip_fill_track_coverart(ctx, ctx->settings.print_info_only) < 0) { ctx->total_error_count++; goto end; } /* Write non-track cover arts */ if (ctx->nb_cover_arts) { cyanrip_log(ctx, 0, "Cover art destination(s):\n"); for (int f = 0; f < ctx->settings.outputs_num; f++) { for (int i = 0; i < ctx->nb_cover_arts; i++) { char *file = crip_get_path(ctx, CRIP_PATH_COVERART, 0, &crip_fmt_info[ctx->settings.outputs[f]], &ctx->cover_arts[i]); cyanrip_log(ctx, 0, " %s\n", file); av_free(file); if (!ctx->settings.print_info_only) { int err = crip_save_art(ctx, &ctx->cover_arts[i], &crip_fmt_info[ctx->settings.outputs[f]]); if (err) { ctx->total_error_count++; goto end; } } } } cyanrip_log(ctx, 0, "\n"); } cyanrip_log(ctx, 0, "Tracks:\n"); if (ctx->settings.rip_indices_count == -1) { ctx->frames_to_read = ctx->duration_frames; if (!ctx->settings.print_info_only) cyanrip_initialize_ebur128(ctx); for (int i = 0; i < ctx->nb_tracks; i++) { cyanrip_track *t = &ctx->tracks[i]; if (ctx->settings.print_info_only) { cyanrip_log(ctx, 0, "Track %i info:\n", t->number); track_read_extra(ctx, t); cyanrip_log_track_end(ctx, t); if (cdio_get_media_changed(ctx->cdio)) { cyanrip_log(ctx, 0, "Drive media changed, stopping!\n"); break; } } else { /* Initialize */ int ret = cyanrip_create_dec_ctx(ctx, &t->dec_ctx, t); if (ret < 0) { cyanrip_log(ctx, 0, "Error initializing decoder: %s\n", av_err2str(ret)); goto end; } for (int j = 0; j < ctx->settings.outputs_num; j++) { ret = cyanrip_init_track_encoding(ctx, &t->enc_ctx[j], t, ctx->settings.outputs[j]); if (ret < 0) { cyanrip_log(ctx, 0, "Error initializing encoder: %s\n", av_err2str(ret)); goto end; } } if (cyanrip_rip_track(ctx, t)) break; } if (quit_now) break; } if (!ctx->settings.print_info_only) cyanrip_finalize_ebur128(ctx, 1); if (ctx->settings.enable_replaygain && !ctx->settings.print_info_only) { crip_replaygain_meta_album(ctx); /** * Writeout tracks. */ for (int i = 0; i < ctx->nb_tracks; i++) { cyanrip_track *t = &ctx->tracks[i]; for (int j = 0; j < ctx->settings.outputs_num; j++) { int ret = cyanrip_writeout_track(ctx, t->enc_ctx[j]); if (ret < 0) { cyanrip_log(ctx, 0, "Error encoding: %s\n", av_err2str(ret)); goto end; } } if (quit_now) break; } } } else { for (int i = 0; i < ctx->settings.rip_indices_count; i++) { idx = ctx->settings.rip_indices[i]; /* Verify all indices */ int j = 0; for (; j < ctx->nb_tracks; j++) { if (ctx->tracks[j].number == idx) break; } if (j >= ctx->nb_tracks) { cyanrip_log(ctx, 0, "Invalid rip index %i, list has %i tracks!\n", idx, ctx->nb_tracks); ctx->total_error_count++; goto end; } ctx->frames_to_read += ctx->tracks[j].frames; } /** * Print-only mode, if requested. */ if (ctx->settings.print_info_only) { for (int i = 0; i < ctx->settings.rip_indices_count; i++) { idx = ctx->settings.rip_indices[i]; int j = 0; for (; j < ctx->nb_tracks; j++) { if (ctx->tracks[j].number == idx) break; } cyanrip_track *t = &ctx->tracks[j]; cyanrip_log(ctx, 0, "Track %i info:\n", t->number); track_read_extra(ctx, t); cyanrip_log_track_end(ctx, t); if (cdio_get_media_changed(ctx->cdio)) { cyanrip_log(ctx, 0, "Drive media changed, stopping!\n"); break; } } goto end; } cyanrip_initialize_ebur128(ctx); /** * Rip tracks. */ for (int i = 0; i < ctx->settings.rip_indices_count; i++) { idx = ctx->settings.rip_indices[i]; int j = 0; for (; j < ctx->nb_tracks; j++) { if (ctx->tracks[j].number == idx) break; } cyanrip_track *t = &ctx->tracks[j]; /* Initialize */ int ret = cyanrip_create_dec_ctx(ctx, &t->dec_ctx, t); if (ret < 0) { cyanrip_log(ctx, 0, "Error initializing decoder: %s\n", av_err2str(ret)); goto end; } for (j = 0; j < ctx->settings.outputs_num; j++) { ret = cyanrip_init_track_encoding(ctx, &t->enc_ctx[j], t, ctx->settings.outputs[j]); if (ret < 0) { cyanrip_log(ctx, 0, "Error initializing encoder: %s\n", av_err2str(ret)); goto end; } } /* Rip */ ret = cyanrip_rip_track(ctx, t); if (ret < 0) { cyanrip_log(ctx, 0, "Error ripping: %s\n", av_err2str(ret)); goto end; } if (quit_now) break; } cyanrip_finalize_ebur128(ctx, 1); if (ctx->settings.enable_replaygain) { crip_replaygain_meta_album(ctx); /** * Writeout tracks. */ for (int i = 0; i < ctx->settings.rip_indices_count; i++) { idx = ctx->settings.rip_indices[i]; int j = 0; for (; j < ctx->nb_tracks; j++) { if (ctx->tracks[j].number == idx) break; } cyanrip_track *t = &ctx->tracks[j]; for (j = 0; j < ctx->settings.outputs_num; j++) { int ret = cyanrip_writeout_track(ctx, t->enc_ctx[j]); if (ret < 0) { cyanrip_log(ctx, 0, "Error encoding: %s\n", av_err2str(ret)); goto end; } } if (quit_now) break; } } } if (!ctx->settings.print_info_only) cyanrip_log_finish_report(ctx); end: cyanrip_log_end(ctx); cyanrip_cue_end(ctx); int err_cnt = ctx->total_error_count; cyanrip_ctx_end(&ctx); return !!err_cnt; } #ifdef HAVE_WMAIN int wmain(int argc, wchar_t *argv[]) { char *argstr_flat, **win32_argv_utf8 = NULL; int i, ret, buffsize = 0, offset = 0; /* determine the UTF-8 buffer size (including NULL-termination symbols) */ for (i = 0; i < argc; i++) buffsize += WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL); win32_argv_utf8 = av_mallocz(sizeof(char *) * (argc + 1) + buffsize); argstr_flat = (char *)win32_argv_utf8 + sizeof(char *) * (argc + 1); for (i = 0; i < argc; i++) { win32_argv_utf8[i] = &argstr_flat[offset]; offset += WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, &argstr_flat[offset], buffsize - offset, NULL, NULL); } win32_argv_utf8[i] = NULL; ret = main(argc, win32_argv_utf8); av_free(win32_argv_utf8); return ret; } #endif ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/cyanrip_main.h��������������������������������������������������������������������0000664�0000000�0000000�00000016154�14526334233�0016614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <math.h> #include "../config.h" #include "version.h" #include "utils.h" #include <cdio/paranoia/paranoia.h> #include <cdio/audio.h> #include <libavutil/mem.h> #include <libavutil/dict.h> #include <libavutil/avstring.h> #include <libavutil/intreadwrite.h> #include <libavcodec/avcodec.h> enum cyanrip_output_formats { CYANRIP_FORMAT_FLAC = 0, CYANRIP_FORMAT_TTA, CYANRIP_FORMAT_OPUS, CYANRIP_FORMAT_AAC, CYANRIP_FORMAT_WAVPACK, CYANRIP_FORMAT_ALAC, CYANRIP_FORMAT_MP3, CYANRIP_FORMAT_VORBIS, CYANRIP_FORMAT_WAV, CYANRIP_FORMAT_AAC_MP4, CYANRIP_FORMAT_OPUS_MP4, CYANRIP_FORMAT_PCM, CYANRIP_FORMATS_NB, }; enum cyanrip_pregap_action { CYANRIP_PREGAP_DEFAULT = 0, CYANRIP_PREGAP_DROP, CYANRIP_PREGAP_MERGE, CYANRIP_PREGAP_TRACK, }; enum CRIPAccuDBStatus { CYANRIP_ACCUDB_DISABLED = 0, CYANRIP_ACCUDB_NOT_FOUND, CYANRIP_ACCUDB_ERROR, CYANRIP_ACCUDB_MISMATCH, CYANRIP_ACCUDB_FOUND, }; enum CRIPPathType { CRIP_PATH_COVERART, /* arg must be a CRIPArt * */ CRIP_PATH_TRACK, /* arg must be a cyanrip_track * */ CRIP_PATH_DATA, /* arg must be a cyanrip_track * */ CRIP_PATH_LOG, /* arg must be NULL */ CRIP_PATH_CUE, /* arg must be NULL */ }; enum CRIPSanitize { CRIP_SANITIZE_SIMPLE, /* Replace unacceptable symbols with _ */ CRIP_SANITIZE_OS_SIMPLE, /* Same as above, but only replaces symbols not allowed on current OS */ CRIP_SANITIZE_UNICODE, /* Replace unacceptable symbols with visually identical unicode equivalents */ CRIP_SANITIZE_OS_UNICODE, /* Same as above, but only replaces symbols not allowed on current OS */ }; typedef struct cyanrip_settings { char *dev_path; char *folder_name_scheme; char *track_name_scheme; char *log_name_scheme; char *cue_name_scheme; enum CRIPSanitize sanitize_method; int speed; int max_retries; int offset; int over_under_read_frames; int print_info_only; int disable_mb; float bitrate; int decode_hdcd; int disable_accurip; int disable_coverart_db; int overread_leadinout; int eject_on_success_rip; enum cyanrip_pregap_action pregap_action[99]; int rip_indices_count; int rip_indices[99]; int paranoia_level; int deemphasis; int force_deemphasis; int ripping_retries; int disable_coverart_embedding; int enable_replaygain; enum cyanrip_output_formats outputs[CYANRIP_FORMATS_NB]; int outputs_num; } cyanrip_settings; typedef struct CRIPAccuDBEntry { int confidence; uint32_t checksum; /* We don't know which version it is */ uint32_t checksum_450; } CRIPAccuDBEntry; typedef struct CRIPArt { AVDictionary *meta; char *source_url; char *title; /* Temporary, used during parsing only, copied to meta, do not free */ AVPacket *pkt; AVCodecParameters *params; uint8_t *data; size_t size; char *extension; } CRIPArt; typedef struct cyanrip_track { int number; /* Human readable track number, may be 0 */ int cd_track_number; /* Actual track on the CD, may be 0 */ AVDictionary *meta; /* Disc's AVDictionary gets copied here */ int total_repeats; /* How many times the track was re-ripped */ int index; /* Array position + 1 */ int track_is_data; int preemphasis; int preemphasis_in_subcode; size_t nb_samples; /* Track duration in samples */ int frames_before_disc_start; lsn_t frames; /* Actual number of frames to read, != samples */ int frames_after_disc_end; lsn_t pregap_lsn; lsn_t start_lsn; lsn_t start_lsn_sig; lsn_t end_lsn; lsn_t end_lsn_sig; /* CUE sheet generator only */ lsn_t dropped_pregap_start; lsn_t merged_pregap_end; ptrdiff_t partial_frame_byte_offs; CRIPArt art; /* One cover art, will not be saved */ int computed_crcs; uint32_t eac_crc; uint32_t acurip_checksum_v1; uint32_t acurip_checksum_v1_450; uint32_t acurip_checksum_v2; int acurip_track_is_first; int acurip_track_is_last; enum CRIPAccuDBStatus ar_db_status; CRIPAccuDBEntry *ar_db_entries; int ar_db_nb_entries; int ar_db_max_confidence; /* EBUR128 values */ double ebu_integrated; double ebu_range; double ebu_lra_low; double ebu_lra_high; double ebu_sample_peak; double ebu_true_peak; struct cyanrip_track *pt; struct cyanrip_track *nt; struct cyanrip_dec_ctx *dec_ctx; struct cyanrip_enc_ctx *enc_ctx[CYANRIP_FORMATS_NB]; } cyanrip_track; typedef struct cyanrip_ctx { cdrom_drive_t *drive; cdrom_paranoia_t *paranoia; CdIo_t *cdio; FILE *logfile[CYANRIP_FORMATS_NB]; FILE *cuefile[CYANRIP_FORMATS_NB]; cyanrip_settings settings; cyanrip_track tracks[198]; int nb_tracks; /* Total number of output tracks */ int nb_cd_tracks; /* Total tracks the CD signals */ int disregard_cd_isrc; /* If one track doesn't have ISRC, universally the rest won't */ char *mb_submission_url; /* Non-track bound cover art */ CRIPArt cover_arts[32]; int nb_cover_arts; /* Drive caps */ cdio_drive_read_cap_t rcap; cdio_drive_write_cap_t wcap; cdio_drive_misc_cap_t mcap; /* Metadata */ AVDictionary *meta; enum CRIPAccuDBStatus ar_db_status; /* Destination folder */ const char *base_dst_folder; int success; int total_error_count; lsn_t start_lsn; lsn_t end_lsn; lsn_t duration_frames; /* ETA */ CRSlidingWinCtx eta_ctx; lsn_t frames_read; lsn_t frames_to_read; /* Album EBUR128 values */ struct cyanrip_dec_ctx *peak_ctx; double ebu_integrated; double ebu_range; double ebu_lra_low; double ebu_lra_high; double ebu_sample_peak; double ebu_true_peak; } cyanrip_ctx; typedef struct cyanrip_out_fmt { const char *name; const char *folder_suffix; const char *ext; const char *lavf_name; int coverart_supported; int compression_level; int lossless; enum AVCodecID codec; } cyanrip_out_fmt; extern const cyanrip_out_fmt crip_fmt_info[]; char *crip_get_path(cyanrip_ctx *ctx, enum CRIPPathType type, int create_dirs, const cyanrip_out_fmt *fmt, void *arg); extern uint64_t paranoia_status[PARANOIA_CB_FINISHED + 1]; extern const int crip_max_paranoia_level; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/discid.c��������������������������������������������������������������������������0000664�0000000�0000000�00000007261�14526334233�0015374�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "discid.h" #include "cyanrip_log.h" #include <libavutil/sha.h> #include <libavutil/base64.h> #include <libavutil/bprint.h> int crip_fill_discid(cyanrip_ctx *ctx) { struct AVSHA *sha_ctx = av_sha_alloc(); int err = av_sha_init(sha_ctx, 160); if (err < 0) { cyanrip_log(ctx, 0, "Unable to init SHA for DiscID: %s!\n", av_err2str(err)); av_free(sha_ctx); return err; } uint8_t temp[33]; snprintf(temp, sizeof(temp), "%02X", ctx->tracks[0].number); av_sha_update(sha_ctx, temp, strlen(temp)); int last_audio_track_idx = ctx->nb_cd_tracks - 1; for (; last_audio_track_idx >= 0; last_audio_track_idx--) if (!ctx->tracks[last_audio_track_idx].track_is_data) break; snprintf(temp, sizeof(temp), "%02X", ctx->tracks[last_audio_track_idx].number); av_sha_update(sha_ctx, temp, strlen(temp)); lsn_t last = ctx->tracks[last_audio_track_idx].end_lsn + 151; snprintf(temp, sizeof(temp), "%08X", last); av_sha_update(sha_ctx, temp, strlen(temp)); for (int i = 0; i < 99; i++) { uint32_t offset = 0; if (i <= last_audio_track_idx) offset = ctx->tracks[i].start_lsn + (i <= last_audio_track_idx)*150; snprintf(temp, sizeof(temp), "%08X", offset); av_sha_update(sha_ctx, temp, strlen(temp)); } uint8_t digest[20]; av_sha_final(sha_ctx, digest); av_free(sha_ctx); int discid_len = AV_BASE64_SIZE(20); char *discid = av_mallocz(discid_len); av_base64_encode(discid, discid_len, digest, 20); for (int i = 0; i < strlen(discid); i++) { if (discid[i] == '/') discid[i] = '_'; if (discid[i] == '+') discid[i] = '.'; if (discid[i] == '=') discid[i] = '-'; } av_dict_set(&ctx->meta, "discid", discid, AV_DICT_DONT_STRDUP_VAL); /* FreeDB */ uint32_t cddb = 0; for (int i = 0; i <= last_audio_track_idx; i++) { uint32_t m = (ctx->tracks[i].start_lsn + 150) / 75; while (m > 0) { cddb += m % 10; m /= 10; } } cddb = (cddb % 0xff) << 24; cddb |= (last/75 - (ctx->tracks[0].start_lsn + 150)/75) << 8; cddb |= ctx->tracks[last_audio_track_idx].number; snprintf(temp, sizeof(temp), "%08X", cddb); av_dict_set(&ctx->meta, "cddb", temp, 0); /* TOC string */ AVBPrint buf; av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); av_bprintf(&buf, "https://musicbrainz.org/cdtoc/attach?toc="); av_bprintf(&buf, "%i%s%i%s%u", ctx->tracks[0].number, "+", ctx->tracks[last_audio_track_idx].number, "+", last); for (int i = 0; i <= last_audio_track_idx; i++) { uint32_t offset = ctx->tracks[i].start_lsn + (i <= last_audio_track_idx)*150; av_bprintf(&buf, "%s%u", "+", offset); } av_bprintf(&buf, "&tracks=%i", last_audio_track_idx + 1); av_bprintf(&buf, "&id=%s", discid); av_bprint_finalize(&buf, &ctx->mb_submission_url); return 0; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/discid.h��������������������������������������������������������������������������0000664�0000000�0000000�00000001521�14526334233�0015372�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include <cyanrip_main.h> int crip_fill_discid(cyanrip_ctx *ctx); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/fifo_frame.c����������������������������������������������������������������������0000664�0000000�0000000�00000000750�14526334233�0016226�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "fifo_frame.h" #define FRENAME(x) FRAME_FIFO_ ## x #define RENAME(x) cr_frame_ ##x #define PRIV_RENAME(x) frame_ ##x #define FNAME enum CRFrameFIFOFlags #define SNAME CRFrameFIFO #define FREE_FN av_frame_free #define CLONE_FN(x) ((x) ? av_frame_clone((x)) : NULL) #define TYPE AVFrame #include "fifo_template.c" #undef TYPE #undef CLONE_FN #undef FREE_FN #undef SNAME #undef FNAME #undef PRIV_RENAME #undef RENAME #undef FRENAME ������������������������cyanrip-0.9.2/src/fifo_frame.h����������������������������������������������������������������������0000664�0000000�0000000�00000000645�14526334233�0016236�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <pthread.h> #include <assert.h> #include <libavutil/frame.h> enum CRFrameFIFOFlags { FRAME_FIFO_BLOCK_MAX_OUTPUT = (1 << 0), FRAME_FIFO_BLOCK_NO_INPUT = (1 << 1), }; #define FRENAME(x) FRAME_FIFO_ ## x #define RENAME(x) cr_frame_ ##x #define FNAME enum CRFrameFIFOFlags #define TYPE AVFrame #include "fifo_template.h" #undef TYPE #undef FNAME #undef RENAME #undef FRENAME �������������������������������������������������������������������������������������������cyanrip-0.9.2/src/fifo_packet.c���������������������������������������������������������������������0000664�0000000�0000000�00000000761�14526334233�0016405�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "fifo_packet.h" #define FRENAME(x) PACKET_FIFO_ ## x #define RENAME(x) cr_packet_ ##x #define PRIV_RENAME(x) packet_ ##x #define FNAME enum CRPacketFIFOFlags #define SNAME CRPacketFIFO #define FREE_FN av_packet_free #define CLONE_FN(x) ((x) ? av_packet_clone((x)) : NULL) #define TYPE AVPacket #include "fifo_template.c" #undef TYPE #undef CLONE_FN #undef FREE_FN #undef SNAME #undef FNAME #undef PRIV_RENAME #undef RENAME #undef FRENAME ���������������cyanrip-0.9.2/src/fifo_packet.h���������������������������������������������������������������������0000664�0000000�0000000�00000000656�14526334233�0016415�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <pthread.h> #include <assert.h> #include <libavcodec/packet.h> enum CRPacketFIFOFlags { PACKET_FIFO_BLOCK_MAX_OUTPUT = (1 << 0), PACKET_FIFO_BLOCK_NO_INPUT = (1 << 1), }; #define FRENAME(x) PACKET_FIFO_ ## x #define RENAME(x) cr_packet_ ##x #define FNAME enum CRPacketFIFOFlags #define TYPE AVPacket #include "fifo_template.h" #undef TYPE #undef FNAME #undef RENAME #undef FRENAME ����������������������������������������������������������������������������������cyanrip-0.9.2/src/fifo_template.c�������������������������������������������������������������������0000664�0000000�0000000�00000011450�14526334233�0016746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <libavutil/frame.h> typedef struct SNAME { TYPE **queued; int num_queued; int max_queued; FNAME block_flags; unsigned int queued_alloc_size; pthread_mutex_t lock; pthread_cond_t cond_in; pthread_cond_t cond_out; } SNAME; static void PRIV_RENAME(fifo_destroy)(void *opaque, uint8_t *data) { SNAME *ctx = (SNAME *)data; pthread_mutex_lock(&ctx->lock); for (int i = 0; i < ctx->num_queued; i++) FREE_FN(&ctx->queued[i]); av_freep(&ctx->queued); pthread_mutex_unlock(&ctx->lock); pthread_cond_destroy(&ctx->cond_in); pthread_cond_destroy(&ctx->cond_out); pthread_mutex_destroy(&ctx->lock); av_free(ctx); } AVBufferRef *RENAME(fifo_create)(int max_queued, FNAME block_flags) { SNAME *ctx = av_mallocz(sizeof(*ctx)); if (!ctx) return NULL; AVBufferRef *ctx_ref = av_buffer_create((uint8_t *)ctx, sizeof(*ctx), PRIV_RENAME(fifo_destroy), NULL, 0); if (!ctx_ref) { av_free(ctx); return NULL; } pthread_mutex_init(&ctx->lock, NULL); pthread_cond_init(&ctx->cond_in, NULL); pthread_cond_init(&ctx->cond_out, NULL); ctx->block_flags = block_flags; ctx->max_queued = max_queued; return ctx_ref; } int RENAME(fifo_is_full)(AVBufferRef *src) { if (!src) return 0; SNAME *ctx = (SNAME *)src->data; pthread_mutex_lock(&ctx->lock); int ret = 0; /* max_queued == -1 -> unlimited */ if (!ctx->max_queued) ret = 1; /* max_queued = 0 -> always full */ else if (ctx->max_queued > 0) ret = ctx->num_queued > (ctx->max_queued + 1); pthread_mutex_unlock(&ctx->lock); return ret; } int RENAME(fifo_get_size)(AVBufferRef *src) { if (!src) return 0; SNAME *ctx = (SNAME *)src->data; pthread_mutex_lock(&ctx->lock); int ret = ctx->num_queued; pthread_mutex_unlock(&ctx->lock); return ret; } int RENAME(fifo_get_max_size)(AVBufferRef *src) { if (!src) return INT_MAX; SNAME *ctx = (SNAME *)src->data; pthread_mutex_lock(&ctx->lock); int ret = ctx->max_queued == -1 ? INT_MAX : ctx->max_queued; pthread_mutex_unlock(&ctx->lock); return ret; } void RENAME(fifo_set_max_queued)(AVBufferRef *dst, int max_queued) { SNAME *ctx = (SNAME *)dst->data; pthread_mutex_lock(&ctx->lock); ctx->max_queued = max_queued; pthread_mutex_unlock(&ctx->lock); } void RENAME(fifo_set_block_flags)(AVBufferRef *dst, FNAME block_flags) { SNAME *ctx = (SNAME *)dst->data; pthread_mutex_lock(&ctx->lock); ctx->block_flags = block_flags; pthread_mutex_unlock(&ctx->lock); } int RENAME(fifo_push)(AVBufferRef *dst, TYPE *in) { if (!dst) return 0; int err = 0; TYPE *in_clone = CLONE_FN(in); SNAME *ctx = (SNAME *)dst->data; pthread_mutex_lock(&ctx->lock); if (ctx->max_queued == 0) goto unlock; /* Block or error, but only for non-NULL pushes */ if (in && (ctx->max_queued != -1) && (ctx->num_queued > (ctx->max_queued + 1))) { if (!(ctx->block_flags & FRENAME(BLOCK_MAX_OUTPUT))) { err = AVERROR(ENOBUFS); FREE_FN(&in_clone); goto unlock; } pthread_cond_wait(&ctx->cond_out, &ctx->lock); } unsigned int oalloc = ctx->queued_alloc_size; TYPE **fq = av_fast_realloc(ctx->queued, &ctx->queued_alloc_size, sizeof(TYPE *)*(ctx->num_queued + 1)); if (!fq) { ctx->queued_alloc_size = oalloc; err = AVERROR(ENOMEM); FREE_FN(&in_clone); goto unlock; } ctx->queued = fq; ctx->queued[ctx->num_queued++] = in_clone; pthread_cond_signal(&ctx->cond_in); unlock: pthread_mutex_unlock(&ctx->lock); return err; } TYPE *RENAME(fifo_pop)(AVBufferRef *src) { if (!src) return NULL; TYPE *out = NULL; SNAME *ctx = (SNAME *)src->data; pthread_mutex_lock(&ctx->lock); if (!ctx->num_queued) { if (!(ctx->block_flags & FRENAME(BLOCK_NO_INPUT))) goto unlock; pthread_cond_wait(&ctx->cond_in, &ctx->lock); } out = ctx->queued[0]; ctx->num_queued--; assert(ctx->num_queued >= 0); memmove(&ctx->queued[0], &ctx->queued[1], ctx->num_queued*sizeof(TYPE *)); if (ctx->max_queued > 0) pthread_cond_signal(&ctx->cond_out); unlock: pthread_mutex_unlock(&ctx->lock); return out; } TYPE *RENAME(fifo_peek)(AVBufferRef *src) { if (!src) return NULL; TYPE *out = NULL; SNAME *ctx = (SNAME *)src->data; pthread_mutex_lock(&ctx->lock); if (!ctx->num_queued) { if (!(ctx->block_flags & FRENAME(BLOCK_NO_INPUT))) goto unlock; pthread_cond_wait(&ctx->cond_in, &ctx->lock); } out = CLONE_FN(ctx->queued[0]); unlock: pthread_mutex_unlock(&ctx->lock); return out; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/fifo_template.h�������������������������������������������������������������������0000664�0000000�0000000�00000001512�14526334233�0016751�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* Create */ AVBufferRef *RENAME(fifo_create)(int max_queued, FNAME block_flags); /* -1 = INF, 0 = none */ AVBufferRef *RENAME(fifo_ref)(AVBufferRef *src, int max_queued, FNAME block_flags); /* Query */ int RENAME(fifo_is_full)(AVBufferRef *src); int RENAME(fifo_get_size)(AVBufferRef *src); int RENAME(fifo_get_max_size)(AVBufferRef *src); /* Modify */ void RENAME(fifo_set_max_queued)(AVBufferRef *dst, int max_queued); void RENAME(fifo_set_block_flags)(AVBufferRef *dst, FNAME block_flags); /* Up/downstreaming */ int RENAME(fifo_mirror)(AVBufferRef *dst, AVBufferRef *src); int RENAME(fifo_unmirror)(AVBufferRef *dst, AVBufferRef *src); int RENAME(fifo_unmirror_all)(AVBufferRef *dst); /* I/O */ int RENAME(fifo_push)(AVBufferRef *dst, TYPE *in); TYPE *RENAME(fifo_pop)(AVBufferRef *src); TYPE *RENAME(fifo_peek)(AVBufferRef *src); ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/meson.build�����������������������������������������������������������������������0000664�0000000�0000000�00000003565�14526334233�0016136�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������build_opts = [ '-D_ISOC11_SOURCE', '-D_XOPEN_SOURCE=700', '-U__STRICT_ANSI__', # Warnings '-Wundef', '-Wmissing-prototypes', '-Wshadow', '-Wparentheses', '-Wpointer-arith', '-Wno-pointer-sign', # Warnings to treat as errors '-Werror=implicit-function-declaration', ] #fix for MinGW32 if host_machine.system() == 'windows' and cc.sizeof('void*') == 4 build_opts += '-D__MINGW_USE_VC2005_COMPAT' endif static_build = get_option('default_library') == 'static' # Required dependencies dependencies = [ # ffmpeg libs dependency('libavcodec', version: '>= 59.24.100'), dependency('libavformat', version: '>= 58.13.100'), dependency('libswresample', version: '>= 4.5.100'), dependency('libavfilter', version: '>= 7.16.100'), dependency('libavutil', version: '>= 57.25.100'), # other dependencies dependency('libcdio', version: '>= 2.0'), dependency('libcdio_paranoia', version: '>= 10.2'), dependency('libmusicbrainz5', version: '>= 5.1', static: static_build), dependency('libcurl', version: '>=7.66.0'), # misc dependency('threads'), cc.find_library('m', required : true), ] # Base files sources = [ 'cyanrip_encode.c', 'cyanrip_log.c', 'cyanrip_main.c', 'utils.c', 'fifo_frame.c', 'fifo_packet.c', 'discid.c', 'musicbrainz.c', 'coverart.c', 'accurip.c', 'cue_writer.c', # Version vcs_tag(command: ['git', 'rev-parse', '--short', 'HEAD'], input: 'version.c.in', output: 'version.c', fallback: 'release') ] # Check for wmain support (Windows/MinGW) if cc.links('int wmain() { return 0; }', args: '-municode') conf.set('HAVE_WMAIN', 1) build_opts += '-municode' endif add_global_arguments(build_opts, language: 'c') executable('cyanrip', install: true, sources: sources, dependencies: dependencies, ) �������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/musicbrainz.c���������������������������������������������������������������������0000664�0000000�0000000�00000037067�14526334233�0016472�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "musicbrainz.h" #include "cyanrip_log.h" #include <musicbrainz5/mb5_c.h> #include <libavutil/crc.h> #define READ_MB(FUNC, MBCTX, DICT, KEY, APPEND) \ do { \ int flags = AV_DICT_DONT_STRDUP_VAL | ((APPEND) ? AV_DICT_APPEND : 0x0); \ if (!MBCTX) \ break; \ int len = FUNC(MBCTX, NULL, 0) + 1; \ char *str = av_mallocz(4*len); \ FUNC(MBCTX, str, len); \ if (str[0] == '\0') \ av_free(str); \ else \ av_dict_set(&DICT, KEY, str, flags); \ } while (0) static void mb_credit(Mb5ArtistCredit credit, AVDictionary *dict, const char *key) { int append = 0; Mb5NameCreditList namecredit_list = mb5_artistcredit_get_namecreditlist(credit); for (int i = 0; i < mb5_namecredit_list_size(namecredit_list); i++) { Mb5NameCredit namecredit = mb5_namecredit_list_item(namecredit_list, i); if (mb5_namecredit_get_name(namecredit, NULL, 0)) { READ_MB(mb5_namecredit_get_name, namecredit, dict, key, append++); } else { Mb5Artist artist = mb5_namecredit_get_artist(namecredit); if (artist) READ_MB(mb5_artist_get_name, artist, dict, key, append++); } READ_MB(mb5_namecredit_get_joinphrase, namecredit, dict, key, append++); } } /* This is pretty awful but I blame musicbrainz entirely */ static uint32_t crc_medium(Mb5Medium medium) { uint32_t crc = UINT32_MAX; const AVCRC *crc_tab = av_crc_get_table(AV_CRC_32_IEEE_LE); Mb5TrackList track_list = mb5_medium_get_tracklist(medium); if (!track_list) return 0; for (int i = 0; i < mb5_track_list_size(track_list); i++) { AVDictionary *tmp_dict = NULL; Mb5Track track = mb5_track_list_item(track_list, i); Mb5Recording recording = mb5_track_get_recording(track); READ_MB(mb5_recording_get_id, recording, tmp_dict, "mbid", 0); Mb5ArtistCredit credit; if (recording) { READ_MB(mb5_recording_get_title, recording, tmp_dict, "title", 0); credit = mb5_recording_get_artistcredit(recording); } else { READ_MB(mb5_track_get_title, track, tmp_dict, "title", 0); credit = mb5_track_get_artistcredit(track); } if (credit) mb_credit(credit, tmp_dict, "artist"); if (dict_get(tmp_dict, "mbid")) crc = av_crc(crc_tab, crc, dict_get(tmp_dict, "mbid"), strlen(dict_get(tmp_dict, "mbid"))); if (dict_get(tmp_dict, "artist")) crc = av_crc(crc_tab, crc, dict_get(tmp_dict, "artist"), strlen(dict_get(tmp_dict, "artist"))); if (dict_get(tmp_dict, "title")) crc = av_crc(crc_tab, crc, dict_get(tmp_dict, "title"), strlen(dict_get(tmp_dict, "title"))); av_dict_free(&tmp_dict); crc ^= mb5_track_get_length(track); crc ^= i; } return crc; } static int mb_tracks(cyanrip_ctx *ctx, Mb5Release release, const char *discid, int discnumber) { /* Set totaldiscs if possible */ Mb5MediumList medium_list_extra = NULL; Mb5MediumList medium_full_list = mb5_release_get_mediumlist(release); int num_cds = mb5_medium_list_size(medium_full_list); av_dict_set_int(&ctx->meta, "totaldiscs", num_cds, 0); if (num_cds == 1 && !discnumber) av_dict_set_int(&ctx->meta, "disc", 1, 0); Mb5Medium medium = NULL; if (discnumber) { if (discnumber < 1 || discnumber > num_cds) { cyanrip_log(ctx, 0, "Invalid disc number %i, release only has %i CDs\n", discnumber, num_cds); return 1; } medium = mb5_medium_list_item(medium_full_list, discnumber - 1); if (!medium) { cyanrip_log(ctx, 0, "Got empty medium list.\n"); return 1; } } else { medium_list_extra = mb5_release_media_matching_discid(release, discid); if (!medium_list_extra) { cyanrip_log(ctx, 0, "No mediums match DiscID!\n"); return 0; } medium = mb5_medium_list_item(medium_list_extra, 0); if (!medium) { cyanrip_log(ctx, 0, "Got empty medium list.\n"); mb5_medium_list_delete(medium_list_extra); return 1; } if (num_cds > 1) { uint32_t medium_crc = crc_medium(medium); for (int i = 0; i < num_cds; i++) { Mb5Medium tmp_medium = mb5_medium_list_item(medium_full_list, i); if (medium_crc == crc_medium(tmp_medium)) { av_dict_set_int(&ctx->meta, "disc", i + 1, 0); break; } } } } READ_MB(mb5_medium_get_title, medium, ctx->meta, "discname", 0); READ_MB(mb5_medium_get_format, medium, ctx->meta, "format", 0); Mb5TrackList track_list = mb5_medium_get_tracklist(medium); if (!track_list) { cyanrip_log(ctx, 0, "Medium has no track list.\n"); if (medium_list_extra) mb5_medium_list_delete(medium_list_extra); return 0; } for (int i = 0; i < mb5_track_list_size(track_list); i++) { if (i >= ctx->nb_cd_tracks) break; Mb5Track track = mb5_track_list_item(track_list, i); Mb5Recording recording = mb5_track_get_recording(track); READ_MB(mb5_recording_get_id, recording, ctx->tracks[i].meta, "mbid", 0); Mb5ArtistCredit credit; if (recording) { READ_MB(mb5_recording_get_title, recording, ctx->tracks[i].meta, "title", 0); credit = mb5_recording_get_artistcredit(recording); } else { READ_MB(mb5_track_get_title, track, ctx->tracks[i].meta, "title", 0); credit = mb5_track_get_artistcredit(track); } if (credit) mb_credit(credit, ctx->tracks[i].meta, "artist"); } if (medium_list_extra) mb5_medium_list_delete(medium_list_extra); return 0; } static int mb_metadata(cyanrip_ctx *ctx, int manual_metadata_specified, int release_idx, char *release_str, int discnumber) { int ret = 0, notfound = 0, possible_stub = 0; const char *ua = "cyanrip/" PROJECT_VERSION_STRING " ( https://github.com/cyanreg/cyanrip )"; Mb5Query query = mb5_query_new(ua, NULL, 0); if (!query) { cyanrip_log(ctx, 0, "Could not connect to MusicBrainz.\n"); return 1; } char *names[] = { "inc" }; char *values[] = { "recordings artist-credits" }; const char *discid = dict_get(ctx->meta, "discid"); if (!discid) { cyanrip_log(ctx, 0, "Missing DiscID!\n"); return 0; } Mb5Metadata metadata = mb5_query_query(query, "discid", discid, 0, 1, names, values); if (!metadata) { tQueryResult res = mb5_query_get_lastresult(query); if (res != eQuery_ResourceNotFound) { int chars = mb5_query_get_lasterrormessage(query, NULL, 0) + 1; char *msg = av_mallocz(chars*sizeof(*msg)); mb5_query_get_lasterrormessage(query, msg, chars); cyanrip_log(ctx, 0, "MusicBrainz query failed: %s\n", msg); av_freep(&msg); } switch(res) { case eQuery_Timeout: case eQuery_ConnectionError: cyanrip_log(ctx, 0, "Connection failed, try again? Or disable via -N\n"); break; case eQuery_AuthenticationError: case eQuery_FetchError: case eQuery_RequestError: cyanrip_log(ctx, 0, "Error fetching/requesting/auth, this shouldn't happen.\n"); break; case eQuery_ResourceNotFound: notfound = 1; break; default: break; } ret = 1; goto end; } Mb5ReleaseList release_list = NULL; Mb5Disc disc = mb5_metadata_get_disc(metadata); if (!disc) { possible_stub = 1; notfound = 1; goto end_meta; } release_list = mb5_disc_get_releaselist(disc); if (!release_list) { cyanrip_log(ctx, 0, "MusicBrainz lookup failed: DiscID has no associated releases.\n"); notfound = 1; goto end_meta; } Mb5Release release = NULL; int num_releases = mb5_release_list_size(release_list); if (!num_releases) { cyanrip_log(ctx, 0, "MusicBrainz lookup failed: no releases found for DiscID.\n"); notfound = 1; goto end_meta; } else if (num_releases > 1 && ((release_idx < 0) && !release_str)) { cyanrip_log(ctx, 0, "Multiple releases found in database for DiscID %s:\n", discid); for (int i = 0; i < num_releases; i++) { release = mb5_release_list_item(release_list, i); AVDictionary *tmp_dict = NULL; READ_MB(mb5_release_get_date, release, tmp_dict, "date", 0); READ_MB(mb5_release_get_title, release, tmp_dict, "album", 0); READ_MB(mb5_release_get_id, release, tmp_dict, "id", 0); READ_MB(mb5_release_get_disambiguation, release, tmp_dict, "disambiguation", 0); READ_MB(mb5_release_get_country, release, tmp_dict, "country", 0); Mb5MediumList medium_list = mb5_release_get_mediumlist(release); int num_cds = mb5_medium_list_size(medium_list); if (num_cds > 1) av_dict_set_int(&tmp_dict, "num_cds", num_cds, 0); #define PROP(key, postamble) \ (!!dict_get(tmp_dict, key)) ? " (" : "", \ (!!dict_get(tmp_dict, key)) ? dict_get(tmp_dict, key) : "", \ (!!dict_get(tmp_dict, key)) ? postamble : "", \ (!!dict_get(tmp_dict, key)) ? ")" : "" cyanrip_log(ctx, 0, " %i (ID: %s): %s" "%s%s%s%s" "%s%s%s%s" "%s%s%s%s" "%s%s%s%s" "%s", i + 1, dict_get(tmp_dict, "id") ? dict_get(tmp_dict, "id") : "unknown id", dict_get(tmp_dict, "album") ? dict_get(tmp_dict, "album") : "unknown album", PROP("disambiguation", ""), PROP("country", ""), PROP("num_cds", " CDs"), PROP("date", ""), "\n"); #undef PROP av_dict_free(&tmp_dict); } cyanrip_log(ctx, 0, "\n"); cyanrip_log(ctx, 0, "Please specify which release to use by adding the -R argument with an index or ID.\n"); ret = 1; goto end_meta; } else if (release_idx >= 0) { /* Release index specified */ if ((release_idx < 1) || (release_idx > num_releases)) { cyanrip_log(ctx, 0, "Invalid release index %i specified, only have %i releases!\n", release_idx, num_releases); ret = 1; goto end_meta; } release = mb5_release_list_item(release_list, release_idx - 1); } else if (release_str) { /* Release ID specified */ int i = 0; for (; i < num_releases; i++) { release = mb5_release_list_item(release_list, i); AVDictionary *tmp_dict = NULL; READ_MB(mb5_release_get_id, release, tmp_dict, "id", 0); if (dict_get(tmp_dict, "id") && !strcmp(release_str, dict_get(tmp_dict, "id"))) { av_dict_free(&tmp_dict); break; } av_dict_free(&tmp_dict); } if (i == num_releases) { cyanrip_log(ctx, 0, "Release ID %s not found in release list for DiscID %s!\n", release_str, discid); ret = 1; goto end_meta; } } else { release = mb5_release_list_item(release_list, 0); } READ_MB(mb5_release_get_id, release, ctx->meta, "release_id", 0); READ_MB(mb5_release_get_disambiguation, release, ctx->meta, "releasecomment", 0); READ_MB(mb5_release_get_date, release, ctx->meta, "date", 0); READ_MB(mb5_release_get_title, release, ctx->meta, "album", 0); READ_MB(mb5_release_get_barcode, release, ctx->meta, "barcode", 0); READ_MB(mb5_release_get_packaging, release, ctx->meta, "packaging", 0); READ_MB(mb5_release_get_country, release, ctx->meta, "country", 0); READ_MB(mb5_release_get_status, release, ctx->meta, "status", 0); /* Label info */ Mb5LabelInfoList *labelinfolist = mb5_release_get_labelinfolist(release); if (mb5_labelinfo_list_size(labelinfolist) == 1) { Mb5LabelInfo *labelinfo = mb5_label_list_item(labelinfolist, 0); READ_MB(mb5_labelinfo_get_catalognumber, labelinfo, ctx->meta, "catalog", 0); Mb5Label *label = mb5_labelinfo_get_label(labelinfo); READ_MB(mb5_label_get_name, label, ctx->meta, "label", 0); } Mb5ArtistCredit artistcredit = mb5_release_get_artistcredit(release); if (artistcredit) mb_credit(artistcredit, ctx->meta, "album_artist"); cyanrip_log(ctx, 0, "Found MusicBrainz release: %s - %s\n", dict_get(ctx->meta, "album"), dict_get(ctx->meta, "album_artist")); /* Read track metadata */ mb_tracks(ctx, release, discid, discnumber); end_meta: mb5_metadata_delete(metadata); /* This frees _all_ metadata */ end: mb5_query_delete(query); if (notfound) { if (possible_stub) { cyanrip_log(ctx, 0, "MusicBrainz lookup failed, but DiscID has a matching stub, " "consider verifying the data and creating a release here:\n"); ret = 1; } else if (!manual_metadata_specified) { cyanrip_log(ctx, 0, "Unable to find release info for this CD, " "and metadata hasn't been manually added!\n"); ret = 1; } else { cyanrip_log(ctx, 0, "Unable to find metadata for this CD, but " "metadata has been manually specified, continuing.\n"); ret = 0; } if (!possible_stub) { cyanrip_log(ctx, 0, "Please help improve the MusicBrainz DB by " "submitting the disc info via the following URL:\n"); } cyanrip_log(ctx, 0, "%s\n", ctx->mb_submission_url); if (ret) cyanrip_log(ctx, 0, "To continue add metadata via -a or -t, or ignore via -N!\n"); } return ret; } int crip_fill_metadata(cyanrip_ctx *ctx, int manual_metadata_specified, int release_idx, char *release_str, int discnumber) { /* Get musicbrainz tags */ if (!ctx->settings.disable_mb) return mb_metadata(ctx, manual_metadata_specified, release_idx, release_str, discnumber); return 0; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/musicbrainz.h���������������������������������������������������������������������0000664�0000000�0000000�00000001752�14526334233�0016467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include <cyanrip_main.h> /* Returns 0 on success, !0 on hard error */ int crip_fill_metadata(cyanrip_ctx *ctx, int manual_metadata_specified, int release_idx, char *release_str, int discnumber); ����������������������cyanrip-0.9.2/src/os_compat.h�����������������������������������������������������������������������0000664�0000000�0000000�00000005551�14526334233�0016126�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * various OS-feature replacement utilities * copyright (c) 2000, 2001, 2002 Fabrice Bellard * * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #ifdef _WIN32 #include <direct.h> #include <sys/stat.h> #include <windows.h> #include <wchar.h> #include <libavutil/mem.h> static inline int utf8towchar(const char *filename_utf8, wchar_t **filename_w) { int num_chars = MultiByteToWideChar(CP_UTF8, 0, filename_utf8, -1, NULL, 0); if (num_chars <= 0) abort(); *filename_w = (wchar_t *)av_calloc(num_chars, sizeof(wchar_t)); MultiByteToWideChar(CP_UTF8, 0, filename_utf8, -1, *filename_w, num_chars); return 0; } static inline int win32_mkdir(const char *filename_utf8) { wchar_t *filename_w; int ret; if (utf8towchar(filename_utf8, &filename_w)) return -1; ret = _wmkdir(filename_w); av_free(filename_w); return ret; } static inline int crip_stat(const char *filename_utf8, struct stat* statbuf) { wchar_t *filename_w; int ret; if (utf8towchar(filename_utf8, &filename_w)) return -1; /* Assume struct stat is the same as struct _stat64, which should be true * because Meson sets _FILE_OFFSET_BITS=64 for mingw64 */ ret = _wstat64(filename_w, statbuf); av_free(filename_w); return ret; } #define mkdir(path, mode) win32_mkdir(path) #else #define crip_stat stat #endif #if defined(__MACH__) #define HAS_COLUMN 0 #endif #if defined(HAVE_WMAIN) #define HAS_CH_LESS 0 #define HAS_CH_MORE 0 #define HAS_CH_COLUMN 0 #define HAS_CH_OR 0 #define HAS_CH_Q 0 #define HAS_CH_ANY 0 #define HAS_CH_FWDSLASH 0 #define HAS_CH_BWDSLASH 0 #define HAS_CH_QUOTES 0 #endif #ifndef HAS_CH_LESS #define HAS_CH_LESS 1 #endif #ifndef HAS_CH_MORE #define HAS_CH_MORE 1 #endif #ifndef HAS_CH_COLUMN #define HAS_CH_COLUMN 1 #endif #ifndef HAS_CH_OR #define HAS_CH_OR 1 #endif #ifndef HAS_CH_Q #define HAS_CH_Q 1 #endif #ifndef HAS_CH_ANY #define HAS_CH_ANY 1 #endif #ifndef HAS_CH_FWDSLASH #define HAS_CH_FWDSLASH 0 // default should be 0 here #endif #ifndef HAS_CH_BWDSLASH #define HAS_CH_BWDSLASH 1 #endif #ifndef HAS_CH_QUOTES #define HAS_CH_QUOTES 1 #endif #ifndef OS_DIR_CHAR #define OS_DIR_CHAR '/' #endif �������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/utils.c���������������������������������������������������������������������������0000664�0000000�0000000�00000003403�14526334233�0015267�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "utils.h" int64_t cr_sliding_win(CRSlidingWinCtx *ctx, int64_t num, int64_t pts, AVRational tb, int64_t len, int do_avg) { struct CRSlidingWinEntry *top, *last; int64_t sum = 0; if (pts == INT64_MIN) goto calc; if (!ctx->num_entries) goto add; for (int i = 0; i < ctx->num_entries; i++) { last = &ctx->entries[i]; int64_t test = av_add_stable(last->tb, last->pts, tb, len); if (((ctx->num_entries + 1) > MAX_ROLLING_WIN_ENTRIES) || av_compare_ts(test, last->tb, pts, tb) < 0) { ctx->num_entries--; memmove(last, last + 1, sizeof(*last) * ctx->num_entries); } } add: top = &ctx->entries[ctx->num_entries++]; top->num = num; top->pts = pts; top->tb = tb; calc: /* Calculate the average */ for (int i = 0; i < ctx->num_entries; i++) sum += ctx->entries[i].num; if (do_avg && ctx->num_entries) sum /= ctx->num_entries; return sum; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/utils.h���������������������������������������������������������������������������0000664�0000000�0000000�00000005317�14526334233�0015302�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <stdio.h> #include <stddef.h> #include <string.h> #include <libavutil/rational.h> #include <libavutil/mathematics.h> #include <libavutil/dict.h> /* Sliding window */ #define MAX_ROLLING_WIN_ENTRIES 1024 * 16 typedef struct CRSlidingWinCtx { struct CRSlidingWinEntry { int64_t num; int64_t pts; AVRational tb; } entries[MAX_ROLLING_WIN_ENTRIES]; int num_entries; } CRSlidingWinCtx; int64_t cr_sliding_win(CRSlidingWinCtx *ctx, int64_t num, int64_t pts, AVRational tb, int64_t len, int do_avg); static inline const char *dict_get(AVDictionary *dict, const char *key) { AVDictionaryEntry *e = av_dict_get(dict, key, NULL, 0); return e ? e->value : NULL; } static inline void cyanrip_frames_to_cue(uint32_t frames, char *str) { if (!str) return; const uint32_t min = frames / (75 * 60); const uint32_t sec = (frames - (min * 75 * 60)) / 75; const uint32_t left = frames - (min * 75 * 60) - (sec * 75); snprintf(str, 16, "%02i:%02i:%02i", min, sec, left); } static inline void cyanrip_frames_to_duration(uint32_t frames, char *str) { if (!str) return; const double tot = frames/75.0; /* 75 frames per second */ const int hr = tot/3600.0; const int min = (tot/60.0) - (hr * 60.0); const int sec = tot - ((hr * 3600.0) + min * 60.0); const int msec = tot - sec; snprintf(str, 13, "%02i:%02i:%02i.%03i", hr, min, sec, msec); } static inline void cyanrip_samples_to_duration(uint32_t samples, char *str) { if (!str) return; const double tot = samples/44100.0; /* 44100 samples per second */ const int hr = tot/3600.0; const int min = (tot/60.0) - (hr * 60.0); const int sec = tot - ((hr * 3600.0) + min * 60.0); const int msec = tot - sec; snprintf(str, 13, "%02i:%02i:%02i.%03i", hr, min, sec, msec); } static inline int cmp_numbers(const void *a, const void *b) { return *((int *)a) > *((int *)b); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/version.c.in����������������������������������������������������������������������0000664�0000000�0000000�00000000042�14526334233�0016215�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������const char *vcstag = "@VCS_TAG@"; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyanrip-0.9.2/src/version.h�������������������������������������������������������������������������0000664�0000000�0000000�00000001433�14526334233�0015622�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * This file is part of cyanrip. * * cyanrip 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. * * cyanrip 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 cyanrip; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ extern const char *vcstag; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������