pax_global_header00006660000000000000000000000064146310753560014524gustar00rootroot0000000000000052 comment=1ed9031faaea5c75f88b2135d04b29ef24766788 atomicparsley-20240608.083822.1ed9031/000077500000000000000000000000001463107535600164015ustar00rootroot00000000000000atomicparsley-20240608.083822.1ed9031/.github/000077500000000000000000000000001463107535600177415ustar00rootroot00000000000000atomicparsley-20240608.083822.1ed9031/.github/workflows/000077500000000000000000000000001463107535600217765ustar00rootroot00000000000000atomicparsley-20240608.083822.1ed9031/.github/workflows/ci.yml000066400000000000000000000012761463107535600231220ustar00rootroot00000000000000name: CI on: push: branches: [ master ] pull_request: branches: [ master ] defaults: run: shell: bash jobs: build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v3 - name: configure run: cmake . - name: build run: cmake --build . --config Release asan: runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v3 - name: configure run: cmake -DASAN=on -DCMAKE_BUILD_TYPE=Debug . - name: build run: cmake --build . - name: test run: tests/test.sh atomicparsley-20240608.083822.1ed9031/.github/workflows/greetings.yml000066400000000000000000000015741463107535600245170ustar00rootroot00000000000000name: Greetings on: [pull_request, issues] jobs: greeting: runs-on: ubuntu-latest steps: - uses: actions/first-interaction@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} issue-message: 'Thanks for filing an issue! Please note that this project is only passively maintained, so your best bet for getting an issue resolved is through a pull request that is easy to verify! [Please read this for more information.](https://github.com/wez/atomicparsley#a-note-on-maintenance)' pr-message: 'Thanks for your contribution! As this project is only passively maintained, it is preferable for PRs to be as simple as possible to verify and review. If feasible and appropriate, consider including a tiny test file to exercise your proposed changes! [Please read this for more information.](https://github.com/wez/atomicparsley#a-note-on-maintenance)' atomicparsley-20240608.083822.1ed9031/.github/workflows/release.yml000066400000000000000000000032721463107535600241450ustar00rootroot00000000000000name: Release on: push: tags: - "20*" defaults: run: shell: bash jobs: build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macos-latest, windows-latest, ubuntu-latest] steps: - uses: actions/checkout@v3 - name: configure run: cmake . - name: build run: cmake --build . --config Release - name: build-musl if: runner.os == 'Linux' run: | rm CMakeCache.txt mv AtomicParsley AtomicParsleyLinux docker run --rm -v "$(pwd):/mnt" -w '/mnt' alpine sh -c "\ apk add cmake build-base linux-headers zlib-dev && \ cmake . && \ cmake --build . --config Release \ " - name: zip if: runner.os == 'macOS' run: zip AtomicParsleyMacOS.zip AtomicParsley - name: zip if: runner.os == 'Linux' run: | zip AtomicParsleyAlpine.zip AtomicParsley mv AtomicParsleyLinux AtomicParsley zip AtomicParsleyLinux.zip AtomicParsley - name: zip if: runner.os == 'Windows' run: | pushd Release 7z a -tzip ../AtomicParsleyWindows.zip AtomicParsley.exe popd - name: build-windows-x86 if: runner.os == 'Windows' run: | rm CMakeCache.txt cmake -S . -B build-windows-x86 -A Win32 cmake --build build-windows-x86 --config Release pushd build-windows-x86/Release 7z a -tzip ../../AtomicParsleyWindowsX86.zip AtomicParsley.exe popd - name: "Upload to Tagged Release" uses: softprops/action-gh-release@v1 with: files: "AtomicParsley*.zip" env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" atomicparsley-20240608.083822.1ed9031/.gitignore000066400000000000000000000005421463107535600203720ustar00rootroot00000000000000.*.sw* .DS_Store obj_dir /AtomicParsley* Makefile autom4te.cache config.log config.status configure src/Makefile src/config.h src/config.h.in* Makefile.in aclocal.m4 depcomp install-sh missing *.o *.obj .deps compile src/.deps .dirstamp config.guess config.sub src/stamp-h1 a.out.dSYM a.out CMakeCache.txt CMakeFiles/ cmake_install.cmake .ninja* *.ninja atomicparsley-20240608.083822.1ed9031/CMakeLists.txt000066400000000000000000000040521463107535600211420ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.5) project(AtomicParsley) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() option(ASAN "whether to enable ASAN" OFF) find_program(GIT git) if(GIT) execute_process( COMMAND "${GIT}" "show" "-s" "--format=%H;%cd" "--date=format:%Y%m%d.%H%M%S.0" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE git_result OUTPUT_VARIABLE git_data ERROR_VARIABLE git_err OUTPUT_STRIP_TRAILING_WHITESPACE ) if(git_result EQUAL 0) list(GET git_data 0 BUILD_INFO) list(GET git_data 1 PACKAGE_VERSION) endif() endif() include(CheckSymbolExists) check_symbol_exists(strsep "string.h" HAVE_STRSEP) if(HAVE_STRSEP) add_definitions(-DHAVE_STRSEP) endif() check_symbol_exists(fseeko "stdio.h" HAVE_FSEEKO) if(HAVE_FSEEKO) add_definitions(-DHAVE_FSEEKO) endif() add_definitions( -DPACKAGE_VERSION="${PACKAGE_VERSION}" -DBUILD_INFO="${BUILD_INFO}" -D_FILE_OFFSET_BITS=64 ) find_package(ZLIB) if(ZLIB_FOUND) include_directories(${ZLIB_INCLUDE_DIRS}) add_definitions(-DHAVE_ZLIB_H) endif() list(APPEND sources src/CDtoc.cpp src/arrays.cpp src/compress.cpp src/extracts.cpp src/iconv.cpp src/id3v2.cpp src/main.cpp src/metalist.cpp src/parsley.cpp src/sha1.cpp src/util.cpp src/uuid.cpp ) if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") list(APPEND sources src/nsfile.mm src/nsimage.mm ) endif() if(WIN32) list(APPEND sources src/extras/getopt.c src/extras/getopt1.c ) endif() add_executable( AtomicParsley ${sources} ) if(ZLIB_FOUND) target_link_libraries( AtomicParsley ${ZLIB_LIBRARIES} ) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") target_link_libraries( AtomicParsley "-framework Cocoa" "-framework Foundation" "-framework IOKit" ) endif() if (ASAN) set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") endif() install(TARGETS AtomicParsley RUNTIME DESTINATION bin) atomicparsley-20240608.083822.1ed9031/COPYING000066400000000000000000000354231463107535600174430ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS atomicparsley-20240608.083822.1ed9031/CREDITS000066400000000000000000000032111463107535600174160ustar00rootroot00000000000000AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C) 2005-2007, puck_lock Copyright (C) 2009-2012, Wez Furlong Copyright (C) 2010-2012, Oleg Oshmyan Copyright (C) 2009, 2011, Santino Fuentes Copyright (C) 2011, John D Pell Copyright (C) 2010, Edriss Mirzadeh Copyright (C) 2009, Josh Aune Copyright (C) 2014, Paul Foose Miscellaneous Contributions made at unknown (to the current maintainers) times, except that the changes are presumably circa 2005-2007: * Mellow_Flow * Mike Brancato * Brian Story * Lowell Stewart * SLarew Contains code derived from code that is: Copyright (C) 1998-2003 Daniel Veillard. All Rights Reserved. (See iconv.cpp for license) Original code for IsoLatin1 and UTF-16 by "Martin J. Duerst" Copyright (C) 2000, 2001, 2003, 2004, 2005 Free Software Foundation, Inc. Authors: Scott G. Miller, Robert Klep (See sha1.cpp) Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. Copyright (c) 1998 Microsoft. (See uuid.cpp) atomicparsley-20240608.083822.1ed9031/Changes.txt000066400000000000000000000332761463107535600205250ustar00rootroot00000000000000v0.1 10/05/2005 Parsing of atoms intial Tree printout extraction of all "covr.data" atoms out to files v0.2 11/10/2005 AtomicInfo.NextAtomNumber introduced to facilitate dynamic atom tree reorganization CreateSparseAtom added v0.5 11/22/2005 Writes artist properly of variable lengths properly into an iTMS m4p file properly (other files don't fare well due to the stsd atom non-standard nature) a number of code-uglifying workarounds were employed to get get that far; v0.6 11/25/2005 Added genre string/numerical support, support for genre's dual-atom ©gen/gnre nature, genre string->integer bug fixes to APar_LocateAtomInsertionPoint when an atom is missing APar_CreateSparseAtom for ordinary non-data atoms are now type -1 (which means they aren't of any interest to us besides length & name); implemnted the Integer data class char4short verified iTunes standard genres only go up to "Hard Rock" added jpg/png artwork embedding into "covr" atoms; slight bugfix for APar_FindAtom (created spurious trailing "covr" atoms). v0.6 GPL'ed at sourceforge.net v0.65 11/25/2005 bugfixes to newly introduced bugs in APar_FindAtom metaEnema to remove all metadata (safe even for m4p drm files) year implemented properly (tagtime moved onto non-standard 'tdtg' atom ala id3v2.4 - because I like that tag); added setting compilation "cpil" tag (an annoying 5byte tag) added advisory setting (maybe it'll give me a kick one cold winter day-do a "Get Info" in iTunes & in the main "Summary" tab view will be a new little icon next to artwork) v0.7 11/26/2005 added a writeBack flag to for a less beta-like future integrated NSImage resizing of artwork environmental preferences for artwork modifications build system mods for Mac-specific compiling v0.7.1 11/27/2005 modified parsing & writing to support Apple Lossless (alac) mp4 files. The lovely "alac.alac" non-standard atoms (parents & carry data) caused unplayable files to be written. Only QT ISMA files get screwed now (no idea about Nero) v0.7.2 11/29/2005 creates iTunes-required meta.hdlr all the tags now get spit back when reading them (--textdata) slight fix to how atoms are parsed all known m4a files now tag properly: iTunes (m4a, m4b, chapterized, alac), Quicktime (ISMA & mpeg4 - change filename ext to .m4a to see art all QT products require the meta.hdlr addition), faac, Helix Producer & Nero slight change to how PrintDataAtoms called FindParentAtom added tag time on "©ed1" (edit date-might only really belong directly under udta) added "©url" to hold url fixes to APar_RemoveAtom added cli ability to remove all artwork v0.7.3 12/02/2005 handles stsd (and child) atoms better modifies all stco offsets when needed (not just the first) new oddball iTMS video "drmi" atom handling new "stik" atom support (sets iTunes GetInfo->options:Movie,TV Show, Music Video) writes iTMS video drm TV shows well now diffs in a hex editor are moov atom length, and then into stco, so all is well v0.7.4 12/03/2005 "desc", "tvnn", "tvsh", "tven" & "tves" setting v0.7.5b 12/09/2005 forced 'mdat' into being childless (chapterized mpeg4 files have atoms scattered througout mdat, but they aren't children) fixed issues with ffmpeg created mpeg4 files (that have mdat as 2nd atom moov & chilren as last atoms); moved ffmpeg mdat atoms around to end better atom adding at the end subbed getopt_long_only to getopt_long for pre-10.4 users added progressbar v0.7.5c 12/10/2005 funnguy0's linux patches (thanks so much for that) v0.7.5d 12/11/2005 endian issues for x86 mostly resolved setting genre's segfaults stik doesn't get set in a multi-option command, but does as a single atom setting Debian port added to binaries (compiled under debian-31r0a-i386 with g++4.02-2, libc6_2.3.5-8 & libstdc++6_4.0.2-2) - under VirtualPC - with the nano editor! v0.7.5e 12/16/2005 ammends how atoms are added at the end of the hierarchy (notably this affects ffmpeg video files) writes "keyw", "catg", "pcst", "aART" atoms read-only "purl" & "egid" added v0.7.6 12/31/2005 ceased flawed null-termination (which was implemented more in my mind) of text 'data' atoms UTF-8 output on Mac OS X & Linux - comment in DUSE_ICONV_CONVERSION in the build file to test it other platforms (maybe my win98Se isn't utf8 aware?) cygwin build accommodations fix to the secondary "of" number for track/disk on non-PPC implemented user-defined completely sanctioned 'uuid' atoms to hold.... anything (text only for now) "--tagtime", "--url" & "--information" now get set onto uuid atoms allow creation of uuid atoms directly from the cli cygwin-win98SE port added to binary releases added '--freefree' to remove any&all 'free' atoms v0.8 01/14/2006 switched over to uint8_t for former ADC_CPIL_TMPO & former ADC_Integer added podcast stik setting & purl/egid bugfixes to APar_RemoveAtom bugfixes & optimizations to APar_FindAtom changes to text output & set values for stik atom increase in buffer size limit non-uuid strings to 255bytes fixed retreats in progress bar added purd atom support mdat.length=0 atom (length=1/64-bit isn't supported I'll somehow cope with a < 4GB file) switch from long to uint32_t better x86 bitshifting added swtich to prevent moving mdat atoms (possible PSP requires mdat before moov) universal binary for Mac OS X release no text limit on lyrics tag v0.8.4 02/25/2006 fixed an imaging bug from preferences fixed metaEnema screwing up the meta atom (APar_RemoveAtom bugfix to remove a direct_find atom) added --output, --overWrite added --metaDump to dump ONLY metadata tags to a file versioning for cvs builds limited support for 64-bit mdat atoms (limited to a little less than a 32-bit atom > 4GB) bugfixes to APar_RemoveAtom for removing uuid atoms or non-existing atoms & to delete all artwork, then add in 1 command ("--artwork REMOVE_ALL --artwork /path --artwork /path") support 64-bit co64 atom support MacOSX-style type/creator codes for tempfiles that end in ".mp4" (no need to change extn to ".m4v"/".m4a" anymore) moved purl/egid onto AtomicDataClass_UInteger (0x00 instead of 0x15) to mirror Apple's change on these tags start incorporating Brian's Win32 fixes (if you malloc, memset is sure to follow fopen) give the 'name' atom for '---' iTunes-internal tags for metadata printouts allow --freefree remove 'free's up to a certain level (preserves iTunes padding) squash some memory leaks change how CreateSparseAtom was matching atoms to accommodate EliminateAtom-ed atoms (facilitates the previous artwork amendments) exit on unsupported 'ftyp' file brands anonymous 3rd party native win32 contributions reworked APar_DetermineAtomLengths to accommodate proper tag setting with --mdatLock parsing atoms under 'stsd' is no longer internally used - only for tree printing reworked Mac OS X TYPE determination based on new stsd_codec structure member revisit co64 offset calculations start extracting track-level details (dates, language, encoder, channels) changed stco/co64 calculations to support non-muxed files anonymous "Everyday is NOT like Sunday" contribution changed unknown 0x15 flagged metadata atoms to hex printouts move mdat only when moov precedes mdat new flexible esds parsing v0.8.8 05/21/2006 prevent libmp4v2 artwork from a hexdump changed how short strings were set win32 change for uuid atoms to avoid sprintf skip parsing 'free' atoms work around foobar2000 0.9 non-compliant tagging scheme & added cli switch to give 'tags' the GoLytely - aka '--foobar2000Enema' ability to read/set completely separate 3gp tags subset (3GPP TS 26.444 version 6.4.0 Release 6 compliant & more like QuickTime-style tags) added libxml's utf8 & utf16 conversion functions new windows (windows2000 & later) unicode (utf16) console output (literal utf8 bytes in win98 & earlier memset standard means of initializing simplified setting of arbitrary info uniformly onto parsedAtoms.AtomicData win32 switch to CP_UTF8 codepage on redirected console output for better unicode output support eliminate need for libiconv - use xml's utf8<->latin1 functions to supplant libiconv properly display atoms like '©nam' under Windows for trees & atom printouts support setting unicode on Windows CP_UTF8 added 3GP keyword fixed bug removing last 3GP asset to reset the length of 'udta' added 'manualAtomRemove' for manually removing iTunes-style atoms improved tracking of filesize/percentage when large free atoms impinge on % of new filesize added 3GP location 'loci' (El Loco) atom - all known 3GP assets can now be set/viewed (except support for multiple same atoms of different languages) ->forced<- elimination of Nero tagging scheme (their foobar2000 inspired 'tags' atom) on 3GP files prevent iTunes-style tags on 3GP files or 3GP assets on MPEG-4 files fix offsets in fragmented files ("moof.traf.tfhd") up MAX_ATOMS to 1024 Windows support for full utf16 (unicode) for cli args & filenames v0.9.0 09/15/2006 new file scanning method based on an array of known atoms/KnownAtoms struct added to list the gamut of known atoms & their basic properties better atom versioning & flags support allow negatives in 3gp asset coordinates (switch to high-bit ascii for getopt_long for assets) fixed minor bug that crept in on non-Win systems in removing files switch from moving mdat(s) to moving moov to reorder atoms mellow_flow's genre fix SLarew's utf16 fix for printing 3gp assets on Win32 reorder moov's child atoms so that udta is last (as per ISO spec recommendations) in moov enable use of 'free' atom padding for rapid updating, pad with a (user-defined) default amount of padding with a complete file rewrite switch remaining AtomicInfo variables over to pointers add support for multiple same atoms with differing languages (like 3gp assets); more flexible 'stik' setting/retrieving & added Audiobook genre bugfix (again!!) added ability to list std genres & stik strings switch output for rtng's "Lyrics" to "Content" list file brands bugfix for removing some cli metadata prevent optimizing on PSP mpeg-4 files (but allow dynamic updating, and don't add padding to psp files) new APar_FindAtom routine eliminating some loops updated routine to find 'moov.udta.meta.hdlr' or iTunes-style tagging simplified APar_RemoveAtom 3gp assets differing in language are grouped now instead of being fifo simplified printing of non-string iTunes-style tags work around 3rd party bug affecting 'cprt' corruption switch to fseeko to support files between 2.5GB & 4GB (and ancillary routines off of filesize like progress bar) fix co64 reduction offsets prevent optimizing when just getting a tree or tags (screwed up track level details) bashfulbladder's booklet stik, only allow dynamic updating with --overWrite & new "AP -t +" routine to show padding & supplemental info changing win32 filename to '-utf8.exe' forces raw utf8 input/output win32 longhelp is converted to utf16 (for atom names) new shorthelp added as default help page bugfix removing non-existing atoms an actual change (removal/addition/change) of an atom is now required for any type of write action fix channel listing for 'esds' without sec5 info added ability to force image dimensions on MacOSX revamped track level details 255 byte limit for strings changed to 255 utf8 *character* limit --stik Audiobook now changes file extension to '.m4b' (for Mac OS X, finder Type code is changed to 'M4B ' too) fix --3gp-year "" in APar_RemoveAtom bugfix setting string lengths in 3gp keyword added ability to add ISO 'cprt' copyright at movie or track level implemented v5 sha1 namepsace/name uuids fixed crash on finding any atom with full uuids (like psp files) more extensive type/profiles/levels in track level details add support for embedding files on uuid atoms switch to reading artwork directly into memory (as opposed to copying from a->b) when setting artwork modified ExtractPixPrefs for leaks - defaults now to deleting temp pic files skip sprintf for uuid binary strings ('qlts' is why) & switch to (less flexible) memcpy accommodate iTunes 7.0 adding aprox. 2k of NULL bytes outside of any atom structure add 'pgap' atom defaults to duplicating the gapless padding at the end of file now (but can be optionally skipped) fixed clipping when setting unicode characters v0.9.X ??/??/2007 now checks/lists 3 letter language codes allow setting 3gp assets at track level fix double fclose & relative paths with --overWrite coalesce iso copyright notices into the new APar_UserData_atom_Init initial support for setting iTunes reverseDNS atoms fix validation test for 'trak' child atoms for atypical order add mjpeg2000 (mjp2) major brand support (for copyright notices & uuid atoms) restyled listings of all text metadata tags (-t 1) fix multiple BOM prints on printouts limit offset adjustments to local (non-external) data added support for adjusting item location offsets switch to a makefile/configure/config.h build system start of ID3v2 2.4 implementation to go into ID32 atoms limit chunk offset updates to local data extend atom creation to file level (FL meta gets created after 'moov') much of ID3v2 2.4 is completed: multiple text fields, counters, APIC/GEOB setting/extracting, group symbols & zlib compression add 3gp7 brands allow ID32 based on compatible ftyp branding refactoring & splitting of metadata listings allow multiple entries in reverseDNS atoms (excepting iTunes domain) initial (unfinished) revisit of file reorganizing/padding update mvhd/tkhd modification timestamps v0.9.6 02/22/2014 update for iTunes 11 compatibility if stik "Movie" is used, set value to 9, instead of 0 iTunes now views stick value 0 as "Home Video" left "Short Film" to also set stik 9, for backward compatibility with work-arounds using "Short Film" to set stik to 9 Swapped TV-Y and TV-Y7 to conform with iTunes usage. atomicparsley-20240608.083822.1ed9031/README.md000066400000000000000000000156541463107535600176730ustar00rootroot00000000000000# AtomicParsley ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wez/atomicparsley/ci.yml?branch=master) AtomicParsley is a lightweight command line program for reading, parsing and setting metadata into MPEG-4 files, in particular, iTunes-style metadata. ## Install ### macOS * Navigate to the [latest release](https://github.com/wez/atomicparsley/releases/latest) * Download the `AtomicParsleyMacOS.zip` file and extract `AtomicParsley` AtomicParsley is also available for brew users and can be installed by executing this command in a terminal: ``` $ brew install atomicparsley ```` Note that the version available in brew may lag behind the latest version of the code in this repo. ### Windows * Navigate to the [latest release](https://github.com/wez/atomicparsley/releases/latest) * Download the `AtomicParsleyWindows.zip` file and extract `AtomicParsley.exe` ### Linux (x86-64) * Navigate to the [latest release](https://github.com/wez/atomicparsley/releases/latest) * Download the `AtomicParsleyLinux.zip` file and extract `AtomicParsley` ### Alpine Linux (x86-64 musl libc) * Navigate to the [latest release](https://github.com/wez/atomicparsley/releases/latest) * Download the `AtomicParsleyAlpine.zip` file and extract `AtomicParsley` * And finally `apk add libstdc++` ## Usage ```txt AtomicParsley sets metadata into MPEG-4 files & derivatives supporting 3 tag schemes: iTunes-style, 3GPP assets & ISO defined copyright notifications. AtomicParsley quick help for setting iTunes-style metadata into MPEG-4 files. General usage examples: AtomicParsley /path/to.mp4 -T 1 AtomicParsley /path/to.mp4 -t + AtomicParsley /path/to.mp4 --artist "Me" --artwork /path/to/art.jpg Atomicparsley /path/to.mp4 --albumArtist "You" --podcastFlag true Atomicparsley /path/to.mp4 --stik "TV Show" --advisory explicit Getting information about the file & tags: -T --test Test file for mpeg4-ishness & print atom tree -t --textdata Prints tags embedded within the file -E --extractPix Extracts pix to the same folder as the mpeg-4 file Setting iTunes-style metadata tags --artist (string) Set the artist tag --title (string) Set the title tag --album (string) Set the album tag --genre (string) Genre tag (see --longhelp for more info) --tracknum (num)[/tot] Track number (or track number/total tracks) --disk (num)[/tot] Disk number (or disk number/total disks) --comment (string) Set the comment tag --year (num|UTC) Year tag (see --longhelp for "Release Date") --lyrics (string) Set lyrics (not subject to 256 byte limit) --lyricsFile (/path) Set lyrics to the content of a file --composer (string) Set the composer tag --copyright (string) Set the copyright tag --grouping (string) Set the grouping tag --artwork (/path) Set a piece of artwork (jpeg or png only) --bpm (number) Set the tempo/bpm --albumArtist (string) Set the album artist tag --compilation (boolean) Set the compilation flag (true or false) --hdvideo (number) Set the hdvideo flag to one of: false or 0 for standard definition true or 1 for 720p 2 for 1080p --advisory (string*) Content advisory (*values: 'clean', 'explicit') --stik (string*) Sets the iTunes "stik" atom (see --longhelp) --description (string) Set the description tag --longdesc (string) Set the long description tag --storedesc (string) Set the store description tag --TVNetwork (string) Set the TV Network name --TVShowName (string) Set the TV Show name --TVEpisode (string) Set the TV episode/production code --TVSeasonNum (number) Set the TV Season number --TVEpisodeNum (number) Set the TV Episode number --podcastFlag (boolean) Set the podcast flag (true or false) --category (string) Sets the podcast category --keyword (string) Sets the podcast keyword --podcastURL (URL) Set the podcast feed URL --podcastGUID (URL) Set the episode's URL tag --purchaseDate (UTC) Set time of purchase --encodingTool (string) Set the name of the encoder --encodedBy (string) Set the name of the Person/company who encoded the file --apID (string) Set the Account Name --cnID (number) Set the iTunes Catalog ID (see --longhelp) --geID (number) Set the iTunes Genre ID (see --longhelp) --xID (string) Set the vendor-supplied iTunes xID (see --longhelp) --gapless (boolean) Set the gapless playback flag --contentRating (string*) Set tv/mpaa rating (see -rDNS-help) Deleting tags Set the value to "": --artist "" --stik "" --bpm "" To delete (all) artwork: --artwork REMOVE_ALL manually removal: --manualAtomRemove "moov.udta.meta.ilst.ATOM" More detailed iTunes help is available with AtomicParsley --longhelp Setting reverse DNS forms for iTunes files: see --reverseDNS-help Setting 3gp assets into 3GPP & derivative files: see --3gp-help Setting copyright notices for all files: see --ISO-help For file-level options & padding info: see --file-help Setting custom private tag extensions: see --uuid-help Setting ID3 tags onto mpeg-4 files: see --ID3-help ---------------------------------------------------------------------- AtomicParsley version: 20221229.172126.0 d813aa6e0304ed3ab6d92f1ae96cd52b586181ec (utf8) Submit bug fixes to https://github.com/wez/atomicparsley ``` ## Build from Source If you are building from source you will need `cmake` and `make`. On Windows systems you'll need Visual Studio or MingW. ``` cmake . cmake --build . --config Release ``` will generate an `AtomicParsley` executable. ### Dependencies: zlib - used to compress ID3 frames & expand already compressed frames available from http://www.zlib.net ## A note on maintenance! > I made some fixes to the original project on sourceforge back in 2009 and > became the de-facto fork of AtomicParsley as a result. However, I haven't > used this tool myself in many years and have acted in a very loose guiding > role since then. > > In 2020 Bitbucket decided to cease hosting Mercurial based repositories > which meant that I had to move it in order to keep it alive, so you'll > see a flurry of recent activity. > > I'll consider merging pull requests if they are easy to review, but because > I don't use this tool myself I have no way to verify complex changes. > If you'd like to make such a change, please consider contributing some > kind of basic automated test with a corresponding small test file. > > This repo has GitHub Actions enabled for the three major platforms > so bootstrapping some test coverage is feasible. > > You are welcome to report issues using the issue tracker, but I (@wez) > am unlikely to act upon them. atomicparsley-20240608.083822.1ed9031/src/000077500000000000000000000000001463107535600171705ustar00rootroot00000000000000atomicparsley-20240608.083822.1ed9031/src/.clang-format000066400000000000000000000002151463107535600215410ustar00rootroot00000000000000--- BasedOnStyle: LLVM IndentWidth: 2 BinPackArguments: false BinPackParameters: false IncludeIsMainRegex: AtomicParse --- Language: Cpp --- atomicparsley-20240608.083822.1ed9031/src/AtomDefs.h000066400000000000000000000400531463107535600210450ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - AtomDefs.h AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #include "AtomicParsley.h" atomDefinition KnownAtoms[] = { // name parent atom(s) container // number // box_type {"<()>", {"_ANY_LEVEL"}, UNKNOWN_ATOM_TYPE, UKNOWN_REQUIREMENTS, UNKNOWN_ATOM}, // our unknown atom (self-defined) {"ftyp", {"FILE_LEVEL"}, CHILD_ATOM, REQUIRED_ONCE, SIMPLE_ATOM}, {"moov", {"FILE_LEVEL"}, PARENT_ATOM, REQUIRED_ONCE, SIMPLE_ATOM}, {"mdat", {"FILE_LEVEL"}, CHILD_ATOM, OPTIONAL_MANY, SIMPLE_ATOM}, {"pdin", {"FILE_LEVEL"}, CHILD_ATOM, OPTIONAL_ONCE, VERSIONED_ATOM}, {"moof", {"FILE_LEVEL"}, PARENT_ATOM, OPTIONAL_MANY, SIMPLE_ATOM}, {"mfhd", {"moof"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {"traf", {"moof"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"tfhd", {"traf"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {"trun", {"traf"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {"mfra", {"FILE_LEVEL"}, PARENT_ATOM, OPTIONAL_ONCE, SIMPLE_ATOM}, {"tfra", {"mfra"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"mfro", {"mfra"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {"free", {"_ANY_LEVEL"}, CHILD_ATOM, OPTIONAL_MANY, SIMPLE_ATOM}, {"skip", {"_ANY_LEVEL"}, CHILD_ATOM, OPTIONAL_MANY, SIMPLE_ATOM}, {"uuid", {"_ANY_LEVEL"}, CHILD_ATOM, REQUIRED_ONCE, EXTENDED_ATOM}, {"mvhd", {"moov"}, CHILD_ATOM, REQUIRED_ONCE, VERSIONED_ATOM}, {"iods", {"moov"}, CHILD_ATOM, OPTIONAL_ONCE, VERSIONED_ATOM}, {"drm ", {"moov"}, CHILD_ATOM, OPTIONAL_ONCE, VERSIONED_ATOM}, // 3gp/MobileMP4 {"trak", {"moov"}, PARENT_ATOM, OPTIONAL_MANY, SIMPLE_ATOM}, {"tkhd", {"trak"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM}, {"tref", {"trak"}, PARENT_ATOM, OPTIONAL_MANY, SIMPLE_ATOM}, {"mdia", {"trak"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"tapt", {"trak"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"clef", {"tapt"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"prof", {"tapt"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"enof", {"tapt"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"mdhd", {"mdia"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"minf", {"mdia"}, PARENT_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"hdlr", {"mdia", "meta", "minf"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, // minf parent present in chapterized {"vmhd", {"minf"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"smhd", {"minf"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"hmhd", {"minf"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"nmhd", {"minf"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"gmhd", {"minf"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, // present in chapterized {"dinf", {"minf", "meta"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, // required in minf {"dref", {"dinf"}, DUAL_STATE_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {"url ", {"dref"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM}, {"urn ", {"dref"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM}, {"alis", {"dref"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM}, {"cios", {"dref"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM}, {"stbl", {"minf"}, PARENT_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"stts", {"stbl"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {"ctts", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"stsd", {"stbl"}, DUAL_STATE_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {"stsz", {"stbl"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"stz2", {"stbl"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"stsc", {"stbl"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {"stco", {"stbl"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"co64", {"stbl"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"stss", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"stsh", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"stdp", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"padb", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"sdtp", {"stbl", "traf"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"sbgp", {"stbl", "traf"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM}, {"sbgp", {"stbl"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM}, {"stps", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"edts", {"trak"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"elst", {"edts"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"udta", {"moov", "trak"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"meta", {"FILE_LEVEL", "moov", "trak", "udta"}, DUAL_STATE_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, // optionally contains info {"mvex", {"moov"}, PARENT_ATOM, OPTIONAL_ONCE, SIMPLE_ATOM}, {"mehd", {"mvex"}, CHILD_ATOM, OPTIONAL_ONCE, VERSIONED_ATOM}, {"trex", {"mvex"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, //{"stsl", {"????"}, CHILD_ATOM, // OPTIONAL_ONE, // VERSIONED_ATOM }, //contained by a sample // entry // box {"subs", {"stbl", "traf"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"xml ", {"meta"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"bxml", {"meta"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"iloc", {"meta"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"pitm", {"meta"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"ipro", {"meta"}, PARENT_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"iinf", {"meta"}, DUAL_STATE_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"infe", {"iinf"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"sinf", {"ipro", "drms", "drmi"}, PARENT_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, // parent atom is also "Protected Sample Entry" {"frma", {"sinf"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"imif", {"sinf"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"schm", {"sinf", "srpp"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"schi", {"sinf", "srpp"}, DUAL_STATE_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"skcr", {"sinf"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"user", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"key ", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, // could be required in 'drms'/'drmi' {"iviv", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"righ", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"name", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"priv", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"iKMS", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, // 'iAEC', '264b', 'iOMA', 'ICSD' {"iSFM", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"iSLT", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, // boxes with 'k***' are also here; reserved {"IKEY", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"hint", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"dpnd", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"ipir", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"mpod", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"sync", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"chap", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, //?possible versioned? {"ipmc", {"moov", "meta"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, {"tims", {"rtp "}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"tsro", {"rtp "}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"snro", {"rtp "}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"srpp", {"srtp"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {"hnti", {"udta"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"rtp ", {"hnti"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, //'rtp ' is defined twice in different containers {"sdp ", {"hnti"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"hinf", {"udta"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"name", {"udta"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"trpy", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"nump", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"tpyl", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"totl", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"npck", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"maxr", {"hinf"}, CHILD_ATOM, OPTIONAL_MANY, SIMPLE_ATOM}, {"dmed", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"dimm", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"drep", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"tmin", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"tmax", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"pmax", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"dmax", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"payt", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"tpay", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"drms", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"drmi", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"alac", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"mp4a", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"mp4s", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"mp4v", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"avc1", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"avcp", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"text", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"jpeg", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"tx3g", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"rtp ", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, //"rtp " occurs twice; disparate meanings {"srtp", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, SIMPLE_ATOM}, {"enca", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"encv", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"enct", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"encs", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"samr", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"sawb", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"sawp", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"s263", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"sevc", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"sqcp", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"ssmv", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"tmcd", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, {"mjp2", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM}, // mjpeg2000 {"alac", {"alac"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"avcC", {"avc1", "drmi"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"damr", {"samr", "sawb"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"d263", {"s263"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"dawp", {"sawp"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"devc", {"sevc"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"dqcp", {"sqcp"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"dsmv", {"ssmv"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"bitr", {"d263"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, {"btrt", {"avc1"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, // found in NeroAVC {"m4ds", {"avc1"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, //?possible versioned? {"ftab", {"tx3g"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, {"jp2h", {"mjp2"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, // mjpeg2000 {"ihdr", {"jp2h"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, // mjpeg2000 {"colr", {"jp2h"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, // mjpeg2000 {"fiel", {"mjp2"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, // mjpeg2000 {"jp2p", {"mjp2"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM}, // mjpeg2000 {"jsub", {"mjp2"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, // mjpeg2000 {"orfo", {"mjp2"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, // mjpeg2000 {"cprt", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, // the only ISO defined metadata tag; also a 3gp asset {"titl", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, // 3gp assets {"auth", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, {"perf", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, {"gnre", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, {"dscp", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, {"albm", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, {"yrrc", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM}, {"rtng", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, {"clsf", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, {"kywd", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, {"loci", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, {"ID32", {"meta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM}, // id3v2 tag {"tsel", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, SIMPLE_ATOM}, // but only at track level in a 3gp file //{"chpl", {"udta"}, CHILD_ATOM, // OPTIONAL_ONCE, // VERSIONED_ATOM }, //Nero - seems to be versioned //{"ndrm", {"udta"}, CHILD_ATOM, // OPTIONAL_ONCE, // VERSIONED_ATOM }, //Nero - seems to be versioned //{"tags", {"udta"}, CHILD_ATOM, // OPTIONAL_ONCE, // SIMPLE_ATOM }, //Another Nero-Creation // ...so if they claim that "tags doesn't have any children", // why does nerotags.exe say "tshd atom"? If 'tags' doesn't // have any children, then tshd can't be an atom.... // Clearly, they are EternallyRight and everyone else is // always wrong. // Pish! Seems that Nero is simply unable to register any atoms. {"ilst", {"meta"}, PARENT_ATOM, OPTIONAL_ONCE, SIMPLE_ATOM}, // iTunes metadata container {"----", {"ilst"}, PARENT_ATOM, OPTIONAL_MANY, SIMPLE_ATOM}, // reverse dns metadata {"mean", {"----"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {"name", {"----"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM}, {".><.", {"dref"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM}, // support any future named child to dref; keep 4th from // end; manual return {"esds", {"SAMPLE_DESC"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM}, // multiple parents; keep 3rd from end; manual return {"(..)", {"ilst"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM}, // multiple parents; keep 2nd from end; manual return {"data", {"ITUNES_METADATA"}, CHILD_ATOM, PARENT_SPECIFIC, VERSIONED_ATOM} // multiple parents }; atomicparsley-20240608.083822.1ed9031/src/AtomicParsley.h000066400000000000000000000352221463107535600221210ustar00rootroot00000000000000#ifndef ATOMIC_PARSLEY_H #define ATOMIC_PARSLEY_H //==================================================================// /* AtomicParsley - AtomicParsley.h AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright 2005-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #if defined HAVE_WINDOWS_H && !defined _WIN32 #define _WIN32 #endif #ifdef _WIN32 #ifndef _UNICODE #define _UNICODE #endif #if defined(_MSC_VER) #define strncasecmp _strnicmp #define _CRT_SECURE_NO_WARNINGS #pragma warning(disable : 4244) // int64_t assignments to int32_t etc. #endif #endif #define __STDC_LIMIT_MACROS #define __STDC_FORMAT_MACROS #define __STDC_CONSTANT_MACROS #include #ifdef __GLIBC__ #define HAVE_LROUNDF 1 #endif #include #include #include #ifndef _WIN32 #include #endif #include #include #include #include #include #include #ifndef _WIN32 #include #include #endif #ifdef __linux__ #include #include #include #endif #ifdef _WIN32 // Don't break std::min! #define NOMINMAX #include #endif #include #ifndef _WIN32 #include #include #endif #ifdef _WIN32 #include #endif #include #ifndef _WIN32 #include #else #include "extras/getopt.h" #endif #ifndef PRIu64 #ifdef _WIN32 #define PRIu64 "I64u" #else #define PRIu64 "llu" #endif #endif #ifndef PRIu32 #define PRIu32 "u" #endif #ifndef PRIx32 #define PRIx32 "x" #endif #ifndef SCNu64 #ifdef _WIN32 #define SCNu64 "I64u" #else #define SCNu64 "llu" #endif #endif #ifndef SCNu32 #define SCNu32 "u" #endif #ifndef SCNu16 #define SCNu16 "hu" #endif #ifndef MAXPATHLEN #define MAXPATHLEN 255 #endif #include "util.h" #include #define MAX_ATOMS 2048 #define MAXDATA_PAYLOAD 1256 #define DEFAULT_PADDING_LENGTH 2048; #define MINIMUM_REQUIRED_PADDING_LENGTH 0; #define MAXIMUM_REQUIRED_PADDING_LENGTH 5000; #include "ap_types.h" extern atomDefinition KnownAtoms[]; extern bool parsedfile; extern bool file_opened; extern bool modified_atoms; extern bool alter_original; extern bool preserve_timestamps; extern bool deep_atom_scan; extern bool cvs_build; extern bool force_existing_hierarchy; extern bool move_moov_atom; extern bool moov_atom_was_mooved; extern int metadata_style; extern uint32_t brand; extern uint64_t mdatData; extern uint64_t file_size; extern uint64_t gapless_void_padding; extern EmployedCodecs track_codecs; extern AtomicInfo parsedAtoms[]; extern short atom_number; extern char *ISObasemediafile; extern FILE *source_file; extern padding_preferences pad_prefs; extern uint8_t UnicodeOutputStatus; extern uint8_t forced_suffix_type; extern char *twenty_byte_buffer; extern DynamicUpdateStat dynUpd; extern ID3FrameDefinition KnownFrames[]; extern ID3v2FieldDefinition FrameTypeConstructionList[]; extern ImageFileFormatDefinition ImageList[]; extern ID3ImageType ImageTypeList[]; void ShowVersionInfo(); void APar_FreeMemory(); short APar_FindParentAtom(int order_in_tree, uint8_t this_atom_level); AtomicInfo *APar_FindAtomInTrack(uint8_t &total_tracks, uint8_t &track_num, const char *search_atom_str); AtomicInfo *APar_FindAtom(const char *atom_name, bool createMissing, uint8_t atom_type, uint16_t atom_lang, bool match_full_uuids = false, const char *reverseDNSdomain = NULL); int APar_MatchToKnownAtom(const char *atom_name, const char *atom_container, bool fromFile, const char *find_atom_path); void APar_ScanAtoms(const char *path, bool deepscan_REQ = false); void APar_IdentifyBrand(char *file_brand); AtomicInfo *APar_CreateSparseAtom(AtomicInfo *surrogate_atom, AtomicInfo *parent_atom, short preceding_atom); void APar_Unified_atom_Put(AtomicInfo *target_atom, const char *unicode_data, uint8_t text_tag_style, uint64_t ancillary_data, uint8_t anc_bit_width); void APar_atom_Binary_Put(AtomicInfo *target_atom, const char *binary_data, uint32_t bytecount, uint64_t atomic_data_offset); /* iTunes-style metadata */ void APar_MetaData_atomArtwork_Set(const char *artworkPath, char *env_PicOptions); void APar_MetaData_atomGenre_Set(const char *atomPayload); void APar_MetaData_atomLyrics_Set(const char *lyricsPath); void APar_MetaData_atom_QuickInit(short atom_num, const uint32_t atomFlags, uint32_t supplemental_length, uint32_t allotment = MAXDATA_PAYLOAD + 1); AtomicInfo *APar_MetaData_atom_Init(const char *atom_path, const char *MD_Payload, const uint32_t atomFlags); AtomicInfo *APar_reverseDNS_atom_Init(const char *rDNS_atom_name, const char *rDNS_payload, const uint32_t *atomFlags, const char *rDNS_domain); /* uuid user extension metadata; made to look much like iTunes-style metadata * with a 4byte NULL */ AtomicInfo *APar_uuid_atom_Init(const char *atom_path, const char *uuidName, const uint32_t dataType, const char *uuidValue, bool shellAtom); // test whether the ipod uuid can be added for a video track uint16_t APar_TestVideoDescription(AtomicInfo *video_desc_atom, FILE *ISObmff_file); void APar_Generate_iPod_uuid(char *atom_path); /* 3GP-style metadata */ uint32_t APar_3GP_Keyword_atom_Format(char *keywords_globbed, uint8_t keyword_count, bool set_UTF16_text, char *&formed_keyword_struct); AtomicInfo *APar_UserData_atom_Init(const char *userdata_atom_name, const char *atom_payload, uint8_t udta_container, uint8_t track_idx, uint16_t userdata_lang); /* ID3v2 (2.4) style metadata, non-external form */ AtomicInfo *APar_ID32_atom_Init(const char *frameID_str, signed char meta_area, const char *lang_str, uint16_t id32_lang); void APar_RemoveAtom(const char *atom_path, uint8_t atom_type, uint16_t UD_lang, const char *rDNS_domain = NULL); void APar_freefree(int purge_level); void APar_MetadataFileDump(const char *ISObasemediafile); void APar_Optimize(bool mdat_test_only); void APar_DetermineAtomLengths(); void APar_WriteFile(const char *ISObasemediafile, const char *outfile, bool rewrite_original); void APar_zlib_inflate(char *in_buffer, uint32_t in_buf_len, char *out_buffer, uint32_t out_buf_len); uint32_t APar_zlib_deflate(char *in_buffer, uint32_t in_buf_len, char *out_buffer, uint32_t out_buf_len); void APar_print_uuid(ap_uuid_t *uuid, bool new_line = true); void APar_sprintf_uuid(ap_uuid_t *uuid, char *destination); uint8_t APar_uuid_scanf(char *in_formed_uuid, const char *raw_uuid); void APar_endian_uuid_bin_str_conversion(char *raw_uuid); uint8_t APar_extract_uuid_version(ap_uuid_t *uuid, char *binary_uuid_str); void APar_generate_uuid_from_atomname(char *atom_name, char *uuid_binary_str); void APar_generate_random_uuid(char *uuid_binary_str); /* Initialize structure containing state of computation. */ extern void sha1_init_ctx(struct sha1_ctx *ctx); /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is necessary that LEN is a multiple of 64!!! */ extern void sha1_process_block(const void *buffer, size_t len, struct sha1_ctx *ctx); /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is NOT required that LEN is a multiple of 64. */ extern void sha1_process_bytes(const void *buffer, size_t len, struct sha1_ctx *ctx); /* Process the remaining bytes in the buffer and put result from CTX in first 20 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. IMPORTANT: On some systems it is required that RESBUF be correctly aligned for a 32 bits value. */ extern void *sha1_finish_ctx(struct sha1_ctx *ctx, void *resbuf); /* Put result from CTX in first 20 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ extern void *sha1_read_ctx(const struct sha1_ctx *ctx, void *resbuf); /* Compute SHA1 message digest for bytes read from STREAM. The resulting message digest number will be written into the 20 bytes beginning at RESBLOCK. */ extern int sha1_stream(FILE *stream, void *resblock); /* Compute SHA1 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ extern void *sha1_buffer(const char *buffer, size_t len, void *resblock); int isolat1ToUTF8(unsigned char *out, int outlen, const unsigned char *in, int inlen); int UTF8Toisolat1(unsigned char *out, int outlen, const unsigned char *in, int inlen); int UTF16BEToUTF8(unsigned char *out, int outlen, const unsigned char *inb, int inlenb); int UTF8ToUTF16BE(unsigned char *outb, int outlen, const unsigned char *in, int inlen); int UTF16LEToUTF8(unsigned char *out, int outlen, const unsigned char *inb, int inlenb); int UTF8ToUTF16LE(unsigned char *outb, int outlen, const unsigned char *in, int inlen); int isUTF8(const char *in_string); unsigned int utf8_length(const char *in_string, unsigned int char_limit); int strip_bogusUTF16toRawUTF8(unsigned char *out, int inlen, wchar_t *in, int outlen); int test_conforming_alpha_string(char *in_string); bool test_limited_ascii(char *in_string, unsigned int str_len); void APar_ExtractDetails(FILE *isofile, uint8_t optional_output); void APar_ExtractBrands(char *filepath); void printBOM(); void APar_fprintf_UTF8_data(const char *utf8_encoded_data); void APar_unicode_win32Printout(wchar_t *unicode_out, char *utf8_out); void APar_Extract_uuid_binary_file(AtomicInfo *uuid_atom, const char *originating_file, char *output_path); void APar_Print_APuuid_atoms(const char *path, char *output_path, uint8_t target_information); void APar_Print_iTunesData(const char *path, char *output_path, uint8_t supplemental_info, uint8_t target_information, AtomicInfo *ilstAtom = NULL); void APar_PrintUserDataAssests(bool quantum_listing = false); void APar_Extract_ID3v2_file(AtomicInfo *id32_atom, const char *frame_str, const char *originfile, const char *destination_folder, AdjunctArgs *id3args); void APar_Print_ID3v2_tags(AtomicInfo *id32_atom); void APar_Print_ISO_UserData_per_track(); void APar_Mark_UserData_area(uint8_t track_num, short userdata_atom, bool quantum_listing); // trees void APar_PrintAtomicTree(); void APar_SimpleAtomPrintout(); uint32_t APar_4CC_CreatorCode(const char *filepath, uint32_t new_type_code); void APar_SupplySelectiveTypeCreatorCodes(const char *inputPath, const char *outputPath, uint8_t forced_type_code); bool ResizeGivenImage(const char *filePath, PicPrefs myPicPrefs, char *resized_path, size_t resized_path_len); char *GenreIntToString(int genre); uint8_t StringGenreToInt(const char *genre_string); void ListGenresValues(); stiks *MatchStikString(const char *stik_string); stiks *MatchStikNumber(uint8_t in_stik_num); void ListStikValues(); sfIDs *MatchStoreFrontNumber(uint32_t storefrontnum); bool MatchLanguageCode(const char *in_code); void ListLanguageCodes(); void ListMediaRatings(); void ListTVGenreIDValues(); void ListMovieGenreIDValues(); const char *Expand_cli_mediastring(const char *cli_rating); char *ID3GenreIntToString(int genre); uint8_t ID3StringGenreToInt(const char *genre_string); #endif /* ATOMIC_PARSLEY_H */ // vim:ts=2:sw=2:et: atomicparsley-20240608.083822.1ed9031/src/CDtoc.cpp000066400000000000000000000506551463107535600207030ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - CDtoc.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C)2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// // gathering of a CD's Table of Contents is going to be hardware specific // currently only Mac OS X is implemented - using IOKit framework. // another avenue (applicable to other *nix platforms): ioctl #include "AtomicParsley.h" #include "CDtoc.h" #if defined(__APPLE__) #include #include #include #include #include const uint8_t MACOSX_LEADOUT_TRACK = 0xA2; #endif const uint8_t CDOBJECT_DATACD = 0; const uint8_t CDOBJECT_AUDIOCD = 1; struct CD_TDesc { uint8_t session; uint8_t controladdress; uint8_t unused1; // refered to as 'tno' which equates to "track number" - // but... its 'point' that actually is the tracknumber in // mode1 TOC. set to zero for all mode1 TOC uint8_t tracknumber; // refered to as 'point', but this is actually the // tracknumber in mode1 audio toc entries. uint8_t rel_minutes; uint8_t rel_seconds; uint8_t rel_frames; uint8_t zero_space; uint8_t abs_minutes; uint8_t abs_seconds; uint8_t abs_frames; void *next_description; }; typedef struct CD_TDesc CD_TDesc; struct CD_TOC_ { uint16_t toc_length; uint8_t first_session; uint8_t last_session; CD_TDesc *track_description; // entry to the first track in the linked list }; typedef struct CD_TOC_ CD_TOC_; CD_TOC_ *cdTOC = NULL; #if defined(__APPLE__) uint8_t LEADOUT_TRACK_NUMBER = MACOSX_LEADOUT_TRACK; #elif defined(__linux__) uint8_t LEADOUT_TRACK_NUMBER = CDROM_LEADOUT; #elif defined(_WIN32) uint8_t LEADOUT_TRACK_NUMBER = 0xAA; // NOTE: for WinXP IOCTL_CDROM_READ_TOC_EX code, its 0xA2 #else uint8_t LEADOUT_TRACK_NUMBER = 0xAA; #endif /* MCDI describes the CD TOC - actually talks about "a binary dump of the TOC". So, a TOC is made up of: a 4 byte TOC header (2 bytes length of the entire TOC, 1 byte start session, 1 byte end session) an array of track entries, and depending on the mode, of varying lengths. For audio CDs, TOC track entries are mode1 (or for CD-R/RW mode5, but lets stick to mode1) a mode1 track entry is 11 bytes: 1byte session, 1 byte (packed control/address), 1byte NULL (unused TNO), 1 byte for tracknumber (expressed as the word POINT in mmc nomenclature), 3bytes relative start frametime, 1 byte NULL, 3 bytes duration timeframe while "binary dump of the TOC" is there, its also modified so that the timeframe listing in mm:ss::frames (3bytes) is converted to a 4byte LBA timecode. Combining the first 4 bytes of the "binary dump of the TOC" with the modifications of the 3byte(frame)->4byte(block), we arrive at MCDI as: struct mcdi_track_entry { uint8_t cd_toc_session; uint8_t cd_toc_controladdress; //bitpacked uint4_t of control & address uint8_t cd_toc_TNO = 0; //hardcoded to 0 for mode1 audio tracks in the TOC uint8_t cd_toc_tracknumber; //this is the 1-99 tracknumber (listed in mmc-2 as POINT) uint32_t cd_frame_address; //converted from the 3byte mm:ss:frame absolute duration }; struct toc_header { uint16_t toc_length; uin8_t first_track; uint8_t last_track; }; struct mcdi_frame { struct toc_header; struct mcdi_track_entry[total_audio_tracks]; struct mcdi_track_entry lead_out; }; The problem with including the TOC header is that it can't be used directly because on the CD toc entries are 3byte msf address, but here they are 4byte LBA. In any event this header should not have ever been included because the length can be deduced from the frame length & tracks by dividing by 8. So, the header length that MCDI refers to: is it for MSF or LBA addressing? Well, since the rest of MCDI is LBA-based, lets say LBA - which means it needs to be calculated. As it just so happens, then its the length of the frame. All that needs to be added are the first & last tracks. Unfortunately, this frame can't be used as a CD Identifier *AS IS* across platforms. Because the leadout track is platform specific (a Linux leadout is 0xAA, MacOSX leadout is 0xA2), consideration of the leadout track would have to be given by anything else using this frame. */ /////////////////////////////////////////////////////////////////////////// // Generating MCDI data from a CD TOC // /////////////////////////////////////////////////////////////////////////// uint8_t DataControlField(uint8_t controlfield) { #if defined(__ppc__) || defined(__ppc64__) if (controlfield & 0x04) { // data uninterrupted or increment OR reserved; // this field is already bitpacked as controlfield return 1; } #else if (controlfield & 0x40) { // data uninterrupted or increment OR reserved; bitpacked already return 1; } #endif return 0; } uint8_t DetermineCDType(CD_TOC_ *cdTOCdata) { CD_TDesc *track_TOC_desc = cdTOCdata->track_description; while (track_TOC_desc != NULL) { if (track_TOC_desc->tracknumber >= 1 && track_TOC_desc->tracknumber <= 99 && !DataControlField(track_TOC_desc->controladdress)) { return CDOBJECT_AUDIOCD; } track_TOC_desc = (CD_TDesc *)track_TOC_desc->next_description; } return CDOBJECT_DATACD; } CD_TDesc *LeadOutTrack(CD_TOC_ *cdTOCdata) { CD_TDesc *track_TOC_desc = cdTOCdata->track_description; while (track_TOC_desc != NULL) { if (track_TOC_desc->tracknumber == LEADOUT_TRACK_NUMBER) { return track_TOC_desc; } track_TOC_desc = (CD_TDesc *)track_TOC_desc->next_description; } return NULL; } uint8_t FillSingleMCDIentry(CD_TDesc *atrack, char *mcdi_data_entry) { mcdi_data_entry[0] = atrack->session; mcdi_data_entry[1] = atrack->controladdress; mcdi_data_entry[2] = 0; mcdi_data_entry[3] = atrack->tracknumber; // LBA=(M*60+S)*75+F - 150 (table 374) uint32_t frameduration = ((((atrack->abs_minutes * 60) + atrack->abs_seconds) * 75) + atrack->abs_frames) - 150; UInt32_TO_String4(frameduration, mcdi_data_entry + 4); return 8; } uint16_t FormMCDIdata(char *mcdi_data) { uint16_t mcdi_len = 0; uint8_t first_track = 0; uint8_t last_track = 0; CD_TDesc *track_TOC_desc = cdTOC->track_description; if (cdTOC->track_description != NULL) { mcdi_len += 4; while (track_TOC_desc != NULL) { if (track_TOC_desc->tracknumber >= 1 && track_TOC_desc->tracknumber <= 99 && !DataControlField(track_TOC_desc->controladdress)) { mcdi_len += FillSingleMCDIentry(track_TOC_desc, mcdi_data + mcdi_len); if (first_track == 0) { first_track = track_TOC_desc->tracknumber; } last_track = track_TOC_desc->tracknumber; } track_TOC_desc = (CD_TDesc *)track_TOC_desc->next_description; } if (mcdi_len > 0) { CD_TDesc *leadout = LeadOutTrack(cdTOC); if (leadout != NULL) { mcdi_len += FillSingleMCDIentry(leadout, mcdi_data + mcdi_len); } } // backtrack & fill in the header UInt16_TO_String2(mcdi_len, mcdi_data); mcdi_data[2] = first_track; mcdi_data[3] = last_track; } return mcdi_len; } ///////////////////////////////////////////////////////////////////////////// // Platform Specifics // ///////////////////////////////////////////////////////////////////////////// #if defined(__linux__) void Linux_ReadCDTOC(int cd_fd) { cdrom_tochdr toc_header; cdrom_tocentry toc_entry; CD_TDesc *a_TOC_desc = NULL; CD_TDesc *prev_desc = NULL; if (ioctl(cd_fd, CDROMREADTOCHDR, &toc_header) == -1) { fprintf(stderr, "AtomicParsley error: there was an error reading the CD " "Table of Contents header.\n"); return; } cdTOC = (CD_TOC_ *)calloc(1, sizeof(CD_TOC_)); cdTOC->track_description = NULL; for (uint8_t i = toc_header.cdth_trk0; i <= toc_header.cdth_trk1 + 1; i++) { memset(&toc_entry, 0, sizeof(toc_entry)); if (i == toc_header.cdth_trk1 + 1) { toc_entry.cdte_track = CDROM_LEADOUT; } else { toc_entry.cdte_track = i; } toc_entry.cdte_format = CDROM_MSF; // although it could just be easier to use CDROM_LBA if (ioctl(cd_fd, CDROMREADTOCENTRY, &toc_entry) == -1) { fprintf(stderr, "AtomicParsley error: there was an error reading a " "CD Table of Contents entry (linux cdrom).\n"); return; } if (cdTOC->track_description == NULL) { cdTOC->track_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc))); a_TOC_desc = cdTOC->track_description; prev_desc = a_TOC_desc; } else { prev_desc->next_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc))); a_TOC_desc = (CD_TDesc *)prev_desc->next_description; prev_desc = a_TOC_desc; } a_TOC_desc->session = 1; // and for vanilla audio CDs it is session 1, but // for multi-session... #if defined(__ppc__) || defined(__ppc64__) a_TOC_desc->controladdress = (toc_entry.cdte_ctrl << 4) | toc_entry.cdte_adr; #else a_TOC_desc->controladdress = (toc_entry.cdte_adr << 4) | toc_entry.cdte_ctrl; #endif a_TOC_desc->unused1 = 0; a_TOC_desc->tracknumber = toc_entry.cdte_track; a_TOC_desc->rel_minutes = 0; // is there anyway to even find this out on // linux without playing the track? //cdmsf_min0 a_TOC_desc->rel_seconds = 0; a_TOC_desc->rel_frames = 0; a_TOC_desc->zero_space = 0; a_TOC_desc->abs_minutes = toc_entry.cdte_addr.msf.minute; a_TOC_desc->abs_seconds = toc_entry.cdte_addr.msf.second; a_TOC_desc->abs_frames = toc_entry.cdte_addr.msf.frame; } return; } uint16_t Linux_ioctlProbeTargetDrive(const char *id3args_drive, char *mcdi_data) { uint16_t mcdi_data_len = 0; int cd_fd = 0; cd_fd = open(id3args_drive, O_RDONLY | O_NONBLOCK); if (cd_fd != -1) { int cd_mode = ioctl(cd_fd, CDROM_DISC_STATUS); if (cd_mode != CDS_AUDIO || cd_mode != CDS_MIXED) { Linux_ReadCDTOC(cd_fd); mcdi_data_len = FormMCDIdata(mcdi_data); } else { // scan for available devices } } else { // scan for available devices } return mcdi_data_len; } #endif #if defined(__APPLE__) uint16_t Extract_cdTOCrawdata(CFDataRef cdTOCdata, char *cdTOCrawdata) { CFRange cdrange; CFIndex cdTOClen = CFDataGetLength(cdTOCdata); cdTOCrawdata = (char *)calloc(1, sizeof(char) * cdTOClen + 1); cdrange = CFRangeMake(0, cdTOClen + 1); CFDataGetBytes(cdTOCdata, cdrange, (unsigned char *)cdTOCrawdata); cdTOC = (CD_TOC_ *)calloc(1, sizeof(CD_TOC_)); cdTOC->toc_length = UInt16FromBigEndian(cdTOCrawdata); cdTOC->first_session = cdTOCrawdata[2]; cdTOC->first_session = cdTOCrawdata[3]; cdTOC->track_description = NULL; CD_TDesc *a_TOC_desc = NULL; CD_TDesc *prev_desc = NULL; uint16_t toc_offset = 0; for (toc_offset = 4; toc_offset <= cdTOClen; toc_offset += 11) { if (cdTOC->track_description == NULL) { cdTOC->track_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc))); a_TOC_desc = cdTOC->track_description; prev_desc = a_TOC_desc; } else { prev_desc->next_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc))); a_TOC_desc = (CD_TDesc *)prev_desc->next_description; prev_desc = a_TOC_desc; } a_TOC_desc->session = cdTOCrawdata[toc_offset]; a_TOC_desc->controladdress = cdTOCrawdata[toc_offset + 1]; a_TOC_desc->unused1 = cdTOCrawdata[toc_offset + 2]; a_TOC_desc->tracknumber = cdTOCrawdata[toc_offset + 3]; a_TOC_desc->rel_minutes = cdTOCrawdata[toc_offset + 4]; a_TOC_desc->rel_seconds = cdTOCrawdata[toc_offset + 5]; a_TOC_desc->rel_frames = cdTOCrawdata[toc_offset + 6]; a_TOC_desc->zero_space = 0; a_TOC_desc->abs_minutes = cdTOCrawdata[toc_offset + 8]; a_TOC_desc->abs_seconds = cdTOCrawdata[toc_offset + 9]; a_TOC_desc->abs_frames = cdTOCrawdata[toc_offset + 10]; } return (uint16_t)cdTOClen; } void OSX_ReadCDTOC(io_object_t cdobject) { CFMutableDictionaryRef cd_props = 0; CFDataRef cdTOCdata = NULL; char *cdTOCrawdata = NULL; if (IORegistryEntryCreateCFProperties( cdobject, &cd_props, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) return; cdTOCdata = (CFDataRef)CFDictionaryGetValue(cd_props, CFSTR(kIOCDMediaTOCKey)); if (cdTOCdata != NULL) { Extract_cdTOCrawdata(cdTOCdata, cdTOCrawdata); } CFRelease(cd_props); cd_props = NULL; return; } void OSX_ScanForCDDrive() { io_iterator_t drive_iter = MACH_PORT_NULL; io_object_t driveobject = MACH_PORT_NULL; CFTypeRef drive_path = NULL; char drive_path_str[20]; if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(kIOCDMediaClass), &drive_iter) != kIOReturnSuccess) { // create the iterator fprintf(stdout, "No device capable of reading cd media present\n"); } driveobject = IOIteratorNext(drive_iter); while (driveobject != MACH_PORT_NULL) { drive_path = IORegistryEntryCreateCFProperty( driveobject, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0); if (drive_path != NULL) { CFStringGetCString((CFStringRef)drive_path, (char *)&drive_path_str, 20, kCFStringEncodingASCII); fprintf(stdout, "Device '%s' contains cd media\n", drive_path_str); OSX_ReadCDTOC(driveobject); if (cdTOC != NULL) { uint8_t cdType = DetermineCDType(cdTOC); if (cdType == CDOBJECT_AUDIOCD) { fprintf(stdout, "Good news, device '%s' is an Audio CD and can be used for " "'MCDI' setting\n", drive_path_str); } else { fprintf(stdout, "Tragically, it was a data CD.\n"); } free(cdTOC); // the other malloced members should be freed also } } IOObjectRelease(driveobject); driveobject = IOIteratorNext(drive_iter); } if (drive_path_str[0] == (uint8_t)0x00) { fprintf(stdout, "No CD media was found in any device\n"); } IOObjectRelease(drive_iter); drive_iter = MACH_PORT_NULL; exit(0); } uint16_t OSX_ProbeTargetDrive(const char *id3args_drive, char *mcdi_data) { uint16_t mcdi_data_len = 0; io_object_t cdobject = MACH_PORT_NULL; if (strncmp(id3args_drive, "disk", 4) != 0) { OSX_ScanForCDDrive(); exit(0); } cdobject = IOServiceGetMatchingService( kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, id3args_drive)); if (cdobject == MACH_PORT_NULL) { fprintf(stdout, "No device found at %s; searching for possible drives...\n", id3args_drive); OSX_ScanForCDDrive(); } else if (IOObjectConformsTo(cdobject, kIOCDMediaClass) == false) { fprintf(stdout, "No cd present in drive at %s\n", id3args_drive); IOObjectRelease(cdobject); cdobject = MACH_PORT_NULL; OSX_ScanForCDDrive(); } else { // we now have a cd object OSX_ReadCDTOC(cdobject); if (cdTOC != NULL) { uint8_t cdType = DetermineCDType(cdTOC); if (cdType == CDOBJECT_AUDIOCD) { mcdi_data_len = FormMCDIdata(mcdi_data); } } } IOObjectRelease(cdobject); cdobject = MACH_PORT_NULL; return mcdi_data_len; } #endif #if defined(_WIN32) void Windows_ioctlReadCDTOC(HANDLE cdrom_device) { DWORD bytes_returned; CDROM_TOC win_cdrom_toc; // WARNING: "This IOCTL is obsolete beginning with the Microsoft Windows // Vista. Do not use this IOCTL to develop drivers in Microsoft Windows // Vista." if (DeviceIoControl(cdrom_device, IOCTL_CDROM_READ_TOC, NULL, 0, &win_cdrom_toc, sizeof(CDROM_TOC), &bytes_returned, NULL) == 0) { fprintf(stderr, "AtomicParsley error: there was an error reading the CD " "Table of Contents header (win32).\n"); return; } cdTOC = (CD_TOC_ *)calloc(1, sizeof(CD_TOC_)); cdTOC->toc_length = ((win_cdrom_toc.Length[0] & 0xFF) << 8) | (win_cdrom_toc.Length[1] & 0xFF); // cdTOC->first_session = 0; //seems windows doesn't store session info with // IOCTL_CDROM_READ_TOC, all tracks from all sessions are available // cdTOC->last_session = 0; //not used anyway; IOCTL_CDROM_READ_TOC_EX could // be used to get session information, but its only available on XP //...............and interestingly enough in XP, IOCTL_CDROM_TOC_EX returns // the leadout track as 0xA2, which makes a Win2k MCDI & a WinXP MCDI // different (by 1 byte) cdTOC->track_description = NULL; CD_TDesc *a_TOC_desc = NULL; CD_TDesc *prev_desc = NULL; for (uint8_t i = win_cdrom_toc.FirstTrack; i <= win_cdrom_toc.LastTrack + 1; i++) { TRACK_DATA *thisTrackData = &(win_cdrom_toc.TrackData[i - 1]); if (cdTOC->track_description == NULL) { cdTOC->track_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc))); a_TOC_desc = cdTOC->track_description; prev_desc = a_TOC_desc; } else { prev_desc->next_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc))); a_TOC_desc = (CD_TDesc *)prev_desc->next_description; prev_desc = a_TOC_desc; } a_TOC_desc->session = 1; // and for vanilla audio CDs it is session 1, but // for multi-session... #if defined(__ppc__) || defined(__ppc64__) a_TOC_desc->controladdress = (thisTrackData->Control << 4) | thisTrackData->Adr; #else a_TOC_desc->controladdress = (thisTrackData->Adr << 4) | thisTrackData->Control; #endif a_TOC_desc->unused1 = 0; a_TOC_desc->tracknumber = thisTrackData->TrackNumber; a_TOC_desc->rel_minutes = 0; // didn't look too much into finding this since it is unused a_TOC_desc->rel_seconds = 0; a_TOC_desc->rel_frames = 0; a_TOC_desc->zero_space = 0; a_TOC_desc->abs_minutes = thisTrackData->Address[1]; a_TOC_desc->abs_seconds = thisTrackData->Address[2]; a_TOC_desc->abs_frames = thisTrackData->Address[3]; } return; } uint16_t Windows_ioctlProbeTargetDrive(const char *id3args_drive, char *mcdi_data) { uint16_t mcdi_data_len = 0; char cd_device_path[16]; memset(cd_device_path, 0, 16); sprintf(cd_device_path, "\\\\.\\%s:", id3args_drive); HANDLE cdrom_device = APar_OpenFileWin32(cd_device_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (cdrom_device != INVALID_HANDLE_VALUE) { Windows_ioctlReadCDTOC(cdrom_device); if (cdTOC != NULL) { uint8_t cdType = DetermineCDType(cdTOC); if (cdType == CDOBJECT_AUDIOCD) { mcdi_data_len = FormMCDIdata(mcdi_data); } } CloseHandle(cdrom_device); } return mcdi_data_len; } #endif //////////////////////////////////////////////////////////////////////////// // CD TOC Entry Area // //////////////////////////////////////////////////////////////////////////// uint16_t GenerateMCDIfromCD(const char *drive, char *dest_buffer) { uint16_t mcdi_bytes = 0; #if defined(__APPLE__) mcdi_bytes = OSX_ProbeTargetDrive(drive, dest_buffer); #elif defined(__linux__) mcdi_bytes = Linux_ioctlProbeTargetDrive(drive, dest_buffer); #elif defined(_WIN32) mcdi_bytes = Windows_ioctlProbeTargetDrive(drive, dest_buffer); #endif return mcdi_bytes; } atomicparsley-20240608.083822.1ed9031/src/CDtoc.h000066400000000000000000000043511463107535600203400ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - CDtoc.h AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #include "ap_types.h" #if defined(_WIN32) // these #defines & structs are copied from the MS W2k DDK headers so the entire // DDK isn't required to be installed to compile AP_CDTOC for MCDI support #ifndef DEVICE_TYPE #define DEVICE_TYPE ULONG #endif #define FILE_DEVICE_CD_ROM 0x00000002 #define IOCTL_CDROM_BASE FILE_DEVICE_CD_ROM #define METHOD_BUFFERED 0 #define FILE_READ_ACCESS (0x0001) // file & pipe #define CTL_CODE(DeviceType, Function, Method, Access) \ (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)) #define IOCTL_CDROM_READ_TOC \ CTL_CODE(IOCTL_CDROM_BASE, 0x0000, METHOD_BUFFERED, FILE_READ_ACCESS) #define MAXIMUM_NUMBER_TRACKS 100 // // CD ROM Table OF Contents (TOC) // Format 0 - Get table of contents // typedef struct _TRACK_DATA { UCHAR Reserved; UCHAR Control : 4; UCHAR Adr : 4; UCHAR TrackNumber; UCHAR Reserved1; UCHAR Address[4]; } TRACK_DATA, *PTRACK_DATA; typedef struct _CDROM_TOC { // // Header // UCHAR Length[2]; UCHAR FirstTrack; UCHAR LastTrack; // // Track data // TRACK_DATA TrackData[MAXIMUM_NUMBER_TRACKS]; } CDROM_TOC, *PCDROM_TOC; #endif uint16_t GenerateMCDIfromCD(const char *drive, char *dest_buffer); atomicparsley-20240608.083822.1ed9031/src/ap_types.h000066400000000000000000000227021463107535600211700ustar00rootroot00000000000000#ifndef AP_TYPES_H #define AP_TYPES_H // Atom version 1byte/ Atom flags 3 bytes; 0x00 00 00 00 #define AtomFlags_Data_Binary 0 // UTF-8, no termination #define AtomFlags_Data_Text 1 // \x0D #define AtomFlags_Data_JPEGBinary 13 // \x0E #define AtomFlags_Data_PNGBinary 14 // \x15 for cpil, tmpo, rtng; iTMS atoms: cnID, atID, plID, geID, sfID, akID #define AtomFlags_Data_UInt 21 // 0x58 for uuid atoms that contain files #define AtomFlags_Data_uuid_binary 88 enum { UTF8_iTunesStyle_256glyphLimited = 0, // no NULL termination UTF8_iTunesStyle_Unlimited = 1, // no NULL termination UTF8_iTunesStyle_Binary = 3, // no NULL termination, used in purl & egid UTF8_3GP_Style = 8, // terminated with a NULL uint8_t UTF16_3GP_Style = 16 // terminated with a NULL uint16_t }; enum { UNDEFINED_STYLE = 0, ITUNES_STYLE = 100, THIRD_GEN_PARTNER = 300, // 3gpp files prior to 3gp6 THIRD_GEN_PARTNER_VER1_REL6 = 306, // 3GPP Release6 the first spec to contain the complement of assets THIRD_GEN_PARTNER_VER1_REL7 = 307, // 3GPP Release7 introduces ID32 atoms THIRD_GEN_PARTNER_VER2 = 320, // 3gp2 files THIRD_GEN_PARTNER_VER2_REL_A = 321, // 3gp2 files, 3GPP2 C.S0050-A introduces 'gadi' MOTIONJPEG2000 = 400 }; #include "id3v2types.h" struct AtomicInfo { short AtomicNumber; uint64_t AtomicStart; uint64_t AtomicLength; uint64_t AtomicLengthExtended; char *AtomicName; char *ReverseDNSname; char *ReverseDNSdomain; uint8_t AtomicContainerState; uint8_t AtomicClassification; uint32_t AtomicVerFlags; // used by versioned atoms and derivatives uint16_t AtomicLanguage; // used by 3gp assets & ID32 atoms only uint8_t AtomicLevel; char *AtomicData; int NextAtomNumber; // our first atom is numbered 0; the last points back to // it - so watch it! uint32_t ancillary_data; // just contains a simple number for atoms that contains // some interesting info (like stsd codec used) uint8_t uuid_style; char *uuid_ap_atomname; ID3v2Tag *ID32_TagInfo; }; #include "id3v2.h" // currently this is only used on Mac OS X to set type/creator for generic // '.mp4' file extension files. The Finder 4 character code TYPE is what // determines whether a file appears as a video or an audio file in a broad // sense. struct EmployedCodecs { bool has_avc1; bool has_mp4v; bool has_drmi; bool has_alac; bool has_mp4a; bool has_drms; bool has_timed_text; // carries the URL - in the mdat stream at a specific // time - thus it too is timed. bool has_timed_jpeg; // no idea of podcasts support 'png ' or 'tiff' bool has_timed_tx3g; // this IS true timed text stream bool has_mp4s; // MPEG-4 Systems bool has_rtp_hint; //'rtp '; implies hinting }; enum { MEDIADATA__PRECEDES__MOOV = 2, ROOT_META__PRECEDES__MOOV = 4, MOOV_META__PRECEDES__TRACKS = 8, MOOV_UDTA__PRECEDES__TRACKS = 16, PADDING_AT_EOF = 0x1000000 }; struct FreeAtomListing { AtomicInfo *free_atom; FreeAtomListing *next_free_listing; }; struct DynamicUpdateStat { bool updage_by_padding; bool reorder_moov; bool moov_was_mooved; bool prevent_dynamic_update; uint32_t optimization_flags; uint64_t padding_bytes; short consolidated_padding_insertion; AtomicInfo *last_trak_child_atom; AtomicInfo *moov_atom; AtomicInfo *moov_udta_atom; AtomicInfo *iTunes_list_handler_atom; AtomicInfo *moov_meta_atom; AtomicInfo *file_meta_atom; AtomicInfo *first_mdat_atom; AtomicInfo *first_movielevel_metadata_tagging_atom; AtomicInfo *initial_update_atom; AtomicInfo *first_otiose_freespace_atom; AtomicInfo *padding_store; AtomicInfo *padding_resevoir; FreeAtomListing *first_padding_atom; FreeAtomListing *last_padding_atom; }; struct padding_preferences { uint32_t default_padding_size; uint32_t minimum_required_padding_size; uint32_t maximum_present_padding_size; }; // Structure that defines the known atoms used by mpeg-4 family of // specifications. typedef struct { const char *known_atom_name; const char *known_parent_atoms[5]; // max known to be tested uint32_t container_state; int presence_requirements; uint32_t box_type; } atomDefinition; typedef struct { uint8_t uuid_form; char *binary_uuid; char *uuid_AP_atom_name; } uuid_vitals; enum { PARENT_ATOM = 0, // container atom SIMPLE_PARENT_ATOM = 1, DUAL_STATE_ATOM = 2, // acts as both parent (contains other atoms) & child (carries data) CHILD_ATOM = 3, // atom that does NOT contain any children UNKNOWN_ATOM_TYPE = 4 }; enum { REQUIRED_ONCE = 30, // means total of 1 atom per file (or total of 1 if // parent atom is required to be present) REQUIRED_ONE = 31, // means 1 atom per container atom; totalling many per file // (or required present if optional parent atom is present) REQUIRED_VARIABLE = 32, // means 1 or more atoms per container atom are required to be present PARENT_SPECIFIC = 33, // means (iTunes-style metadata) the atom defines how many are // present; most are MAX 1 'data' atoms; 'covr' is ?unlimited? OPTIONAL_ONCE = 34, // means total of 1 atom per file, but not required OPTIONAL_ONE = 35, // means 1 atom per container atom but not required; many // may be present in a file OPTIONAL_MANY = 36, // means more than 1 occurrence per container atom REQ_FAMILIAL_ONE = OPTIONAL_ONE, // means that one of the family of atoms defined by the spec // is required by the parent atom UKNOWN_REQUIREMENTS = 38 }; enum { SIMPLE_ATOM = 50, VERSIONED_ATOM = 51, EXTENDED_ATOM = 52, PACKED_LANG_ATOM = 53, UNKNOWN_ATOM = 59 }; enum { PRINT_DATA = 1, PRINT_FREE_SPACE = 2, PRINT_PADDING_SPACE = 4, PRINT_USER_DATA_SPACE = 8, PRINT_MEDIA_SPACE = 16, EXTRACT_ARTWORK = 20, EXTRACT_ALL_UUID_BINARYS = 21 }; typedef struct { const char *stik_string; uint8_t stik_number; } stiks; typedef struct { const char *storefront_string; uint32_t storefront_number; } sfIDs; typedef struct { const char *iso639_2_code; const char *iso639_1_code; const char *language_in_english; } iso639_lang; typedef struct { const char *media_rating; const char *media_rating_cli_str; } m_ratings; typedef struct { const char *genre_id_movie_string; uint16_t genre_id_movie_value; } geIDMovie; typedef struct { const char *genre_id_tv_string; uint16_t genre_id_tv_value; } geIDTV; enum { UNIVERSAL_UTF8, WIN32_UTF16 }; enum { FORCE_M4B_TYPE = 85, NO_TYPE_FORCING = 90 }; enum { FILE_LEVEL_ATOM, MOVIE_LEVEL_ATOM, ALL_TRACKS_ATOM, SINGLE_TRACK_ATOM }; enum { UUID_DEPRECATED_FORM, UUID_SHA1_NAMESPACE, UUID_AP_SHA1_NAMESPACE, UUID_OTHER }; /* Declarations of functions and data types used for SHA1 sum library functions. Copyright (C) 2000, 2001, 2003, 2005 Free Software Foundation, Inc. */ typedef struct { uint32_t time_low; uint16_t time_mid; uint16_t time_hi_and_version; uint8_t clock_seq_hi_and_reserved; uint8_t clock_seq_low; unsigned char node[6]; } ap_uuid_t; typedef uint32_t md5_uint32; /* Structure to save state of computation between the single steps. */ struct sha1_ctx { md5_uint32 A; md5_uint32 B; md5_uint32 C; md5_uint32 D; md5_uint32 E; md5_uint32 total[2]; md5_uint32 buflen; char buffer[128]; // char buffer[128] __attribute__ ((__aligned__ (__alignof__ // (md5_uint32)))); }; typedef struct { // if any of these are unused, they are set to 0xFF uint8_t od_profile_level; uint8_t scene_profile_level; uint8_t audio_profile; uint8_t video_profile_level; uint8_t graphics_profile_level; } iods_OD; typedef struct { uint64_t creation_time; uint64_t modified_time; uint64_t duration; bool track_enabled; unsigned char unpacked_lang[4]; char track_hdlr_name[100]; char encoder_name[100]; uint32_t track_type; uint32_t track_codec; uint32_t protected_codec; bool contains_esds; uint64_t section3_length; uint64_t section4_length; uint8_t ObjectTypeIndication; uint32_t max_bitrate; uint32_t avg_bitrate; uint64_t section5_length; uint8_t descriptor_object_typeID; uint16_t channels; uint64_t section6_length; // unused // specifics uint8_t m4v_profile; uint8_t avc_version; uint8_t profile; uint8_t level; uint16_t video_height; uint16_t video_width; uint32_t macroblocks; uint64_t sample_aggregate; uint16_t amr_modes; uint8_t type_of_track; } TrackInfo; typedef struct { uint64_t creation_time; uint64_t modified_time; uint32_t timescale; uint32_t duration; uint32_t playback_rate; // fixed point 16.16 uint16_t volume; // fixed 8.8 point double seconds; double simple_bitrate_calc; bool contains_iods; } MovieInfo; typedef struct { uint8_t total_tracks; uint8_t track_num; short track_atom; } Trackage; typedef struct { uint8_t hours; uint8_t minutes; uint8_t seconds; double rem_millisecs; } ap_time; enum { UNKNOWN_TRACK = 0, VIDEO_TRACK = 2, AUDIO_TRACK = 4, DRM_PROTECTED_TRACK = 8, OTHER_TRACK = 16 }; enum { MP4V_TRACK = 65, AVC1_TRACK = 66, S_AMR_TRACK = 67, S263_TRACK = 68, EVRC_TRACK = 69, QCELP_TRACK = 70, SMV_TRACK = 71 }; enum { SHOW_TRACK_INFO = 2, SHOW_DATE_INFO = 4 }; struct PicPrefs { int max_dimension; int dpi; int max_Kbytes; bool squareUp; bool allJPEG; bool allPNG; bool addBOTHpix; bool removeTempPix; bool force_dimensions; int force_height; int force_width; }; #endif /* vim:ts=2:sw=2:et: */ atomicparsley-20240608.083822.1ed9031/src/arrays.cpp000066400000000000000000001025521463107535600212020ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - arrays.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C) 2005-2007 puck_lock with contributions from others; see the CREDITS file ---------------------- Code Contributions by: * Mellow_Flow - fix genre matching/verify genre limits */ //==================================================================// #include "AtomicParsley.h" ////////////// static const char *ID3v1GenreList[] = {"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A Capella", "Euro-House", "Dance Hall"}; /* "Goa", "Drum & Bass", "Club House", "Hardcore", "Terror", "Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat", "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary C", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", "SynthPop", }; */ //apparently the other winamp id3v1 extensions aren't valid stiks stikArray[] = {{"Home Video", 0}, {"Normal", 1}, {"Audiobook", 2}, {"Whacked Bookmark", 5}, {"Music Video", 6}, {"Movie", 9}, {"Short Film", 9}, {"TV Show", 10}, {"Booklet", 11}}; geIDMovie genreidmovie[] = {{"Action & Adventure", 4401}, {"Anime", 4402}, {"Classics", 4403}, {"Comedy", 4404}, {"Documentary", 4405}, {"Drama", 4406}, {"Foreign", 4407}, {"Horror", 4408}, {"Independent", 4409}, {"Kids & Family", 4410}, {"Musicals", 4411}, {"Romance", 4412}, {"Sci-Fi & Fantasy", 4413}, {"Short Films", 4414}, {"Special Interest", 4415}, {"Thriller", 4416}, {"Sports", 4417}, {"Western", 4418}, {"Urban", 4419}, {"Holiday", 4420}, {"Made for TV", 4421}, {"Concert Films", 4422}, {"Music Documentaries", 4423}, {"Music Feature Films", 4424}, {"Japanese Cinema", 4425}, {"Jidaigeki", 4426}, {"Tokusatsu", 4427}, {"Korean Cinema", 4428}}; geIDTV genreidtv[] = {{"Comedy", 4000}, {"Drama", 4001}, {"Animation", 4002}, {"Action & Adventure", 4003}, {"Classic", 4004}, {"Kids", 4005}, {"Nonfiction", 4005}, {"Reality TV", 4007}, {"Sci-Fi & Fantasy", 4008}, {"Sports", 4009}, {"Teens", 4010}, {"Latino TV", 4011}}; // from William Herrera: // http://search.cpan.org/src/BILLH/LWP-UserAgent-iTMS_Client-0.16/lib/LWP/UserAgent/iTMS_Client.pm sfIDs storefronts[] = { {"United States", 143441}, {"France", 143442}, {"Germany", 143443}, {"United Kingdom", 143444}, {"Austria", 143445}, {"Belgium", 143446}, {"Finland", 143447}, {"Greece", 143448}, {"Ireland", 143449}, {"Italy", 143450}, {"Luxembourg", 143451}, {"Netherlands", 143452}, {"Portugal", 143453}, {"Spain", 143454}, {"Canada", 143455}, {"Sweden", 143456}, {"Norway", 143457}, {"Denmark", 143458}, {"Switzerland", 143459}, {"Australia", 143460}, {"New Zealand", 143461}, {"Brazil", 143503}, {"India", 143467}, {"Japan", 143462}}; iso639_lang known_languages[] = { {"aar", "aa", "Afar"}, {"abk", "ab", "Abkhazian"}, {"ace", NULL, "Achinese"}, {"ach", NULL, "Acoli"}, {"ada", NULL, "Adangme"}, {"ady", NULL, "Adyghe; Adygei"}, {"afa", NULL, "Afro-Asiatic (Other)"}, {"afh", NULL, "Afrihili"}, {"afr", "af", "Afrikaans"}, {"ain", NULL, "Ainu"}, {"aka", "ak", "Akan"}, {"akk", NULL, "Akkadian"}, {"alb/sqi", "sq", "Albanian"}, // dual codes {"ale", NULL, "Aleut"}, {"alg", NULL, "Algonquian languages"}, {"alt", NULL, "Southern Altai"}, {"amh", "am", "Amharic"}, {"ang", NULL, "English, Old (ca.450-1100)"}, {"anp", NULL, "Angika"}, {"apa", NULL, "Apache languages"}, {"ara", "ar", "Arabic"}, {"arc", NULL, "Aramaic"}, {"arg", "an", "Aragonese"}, {"arm/hye", "hy", "Armenian"}, // dual codes {"arn", NULL, "Araucanian"}, {"arp", NULL, "Arapaho"}, {"art", NULL, "Artificial (Other)"}, {"arw", NULL, "Arawak"}, {"asm", "as", "Assamese"}, {"ast", NULL, "Asturian; Bable"}, {"ath", NULL, "Athapascan languages"}, {"aus", NULL, "Australian languages"}, {"ava", "av", "Avaric"}, {"ave", "ae", "Avestan"}, {"awa", NULL, "Awadhi"}, {"aym", "ay", "Aymara"}, {"aze", "az", "Azerbaijani"}, {"bad", NULL, "Banda"}, {"bai", NULL, "Bamileke languages"}, {"bak", "ba", "Bashkir"}, {"bal", NULL, "Baluchi"}, {"bam", "bm", "Bambara"}, {"ban", NULL, "Balinese"}, {"baq/eus", "eu", "Basque"}, // dual codes {"bas", NULL, "Basa"}, {"bat", NULL, "Baltic (Other)"}, {"bej", NULL, "Beja"}, {"bel", "be", "Belarusian"}, {"bem", NULL, "Bemba"}, {"ben", "bn", "Bengali"}, {"ber", NULL, "Berber (Other)"}, {"bho", NULL, "Bhojpuri"}, {"bih", "bh", "Bihari"}, {"bik", NULL, "Bikol"}, {"bin", NULL, "Bini"}, {"bis", "bi", "Bislama"}, {"bla", NULL, "Siksika"}, {"bnt", NULL, "Bantu (Other)"}, {"bos", "bs", "Bosnian"}, {"bra", NULL, "Braj"}, {"bre", "br", "Breton"}, {"btk", NULL, "Batak (Indonesia)"}, {"bua", NULL, "Buriat"}, {"bug", NULL, "Buginese"}, {"bul", "bg", "Bulgarian"}, {"bur/mya", "my", "Burmese"}, // dual codes {"byn", NULL, "Blin; Bilin"}, {"cad", NULL, "Caddo"}, {"cai", NULL, "Central American Indian (Other)"}, {"car", NULL, "Carib"}, {"cat", "ca", "Catalan; Valencian"}, {"cau", NULL, "Caucasian (Other)"}, {"ceb", NULL, "Cebuano"}, {"cel", NULL, "Celtic (Other)"}, {"cha", "ch", "Chamorro"}, {"chb", NULL, "Chibcha"}, {"che", "ce", "Chechen"}, {"chg", NULL, "Chagatai"}, {"chk", NULL, "Chuukese"}, {"chm", NULL, "Mari"}, {"chn", NULL, "Chinook jargon"}, {"cho", NULL, "Choctaw"}, {"chp", NULL, "Chipewyan"}, {"chr", NULL, "Cherokee"}, {"chu", "cu", "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church " "Slavonic"}, {"chv", "cv", "Chuvash"}, {"chy", NULL, "Cheyenne"}, {"cmc", NULL, "Chamic languages"}, {"cop", NULL, "Coptic"}, {"cor", "kw", "Cornish"}, {"cos", "co", "Corsican"}, {"cpe", NULL, "Creoles and pidgins, English based (Other)"}, {"cpf", NULL, "Creoles and pidgins, French-based (Other)"}, {"cpp", NULL, "Creoles and pidgins, Portuguese-based (Other)"}, {"cre", "cr", "Cree"}, {"crh", NULL, "Crimean Tatar; Crimean Turkish"}, {"crp", NULL, "Creoles and pidgins (Other)"}, {"csb", NULL, "Kashubian"}, {"cus", NULL, "Cushitic (Other)"}, {"cze/ces", "cs", "Czech"}, // dual codes {"dak", NULL, "Dakota"}, {"dan", "da", "Danish"}, {"dar", NULL, "Dargwa"}, {"day", NULL, "Dayak"}, {"del", NULL, "Delaware"}, {"den", NULL, "Slave (Athapascan)"}, {"dgr", NULL, "Dogrib"}, {"din", NULL, "Dinka"}, {"div", "dv", "Divehi; Dhivehi; Maldivian"}, {"doi", NULL, "Dogri"}, {"dra", NULL, "Dravidian (Other)"}, {"dsb", NULL, "Lower Sorbian"}, {"dua", NULL, "Duala"}, {"dum", NULL, "Dutch, Middle (ca.1050-1350)"}, {"dut/nld", "nl", "Dutch; Flemish"}, // dual codes {"dyu", NULL, "Dyula"}, {"dzo", "dz", "Dzongkha"}, {"efi", NULL, "Efik"}, {"egy", NULL, "Egyptian (Ancient)"}, {"eka", NULL, "Ekajuk"}, {"elx", NULL, "Elamite"}, {"eng", "en", "English"}, {"enm", NULL, "English, Middle (1100-1500)"}, {"epo", "eo", "Esperanto"}, {"est", "et", "Estonian"}, {"ewe", "ee", "Ewe"}, {"ewo", NULL, "Ewondo"}, {"fan", NULL, "Fang"}, {"fao", "fo", "Faroese"}, {"fat", NULL, "Fanti"}, {"fij", "fj", "Fijian"}, {"fil", NULL, "Filipino; Pilipino"}, {"fin", "fi", "Finnish"}, {"fiu", NULL, "Finno-Ugrian (Other)"}, {"fon", NULL, "Fon"}, {"fre/fra", "fr", "French"}, // dual codes {"frm", NULL, "French, Middle (ca.1400-1600)"}, {"fro", NULL, "French, Old (842-ca.1400)"}, {"frr", NULL, "Northern Frisian"}, {"frs", NULL, "Eastern Frisian"}, {"fry", "fy", "Western Frisian"}, {"ful", "ff", "Fulah"}, {"fur", NULL, "Friulian"}, {"gaa", NULL, "Ga"}, {"gay", NULL, "Gayo"}, {"gba", NULL, "Gbaya"}, {"gem", NULL, "Germanic (Other)"}, {"geo/kat", "ka", "Georgian"}, // dual codes {"ger/deu", "de", "German"}, // dual codes {"gez", NULL, "Geez"}, {"gil", NULL, "Gilbertese"}, {"gla", "gd", "Gaelic; Scottish Gaelic"}, {"gle", "ga", "Irish"}, {"glg", "gl", "Galician"}, {"glv", "gv", "Manx"}, {"gmh", NULL, "German, Middle High (ca.1050-1500)"}, {"goh", NULL, "German, Old High (ca.750-1050)"}, {"gon", NULL, "Gondi"}, {"gor", NULL, "Gorontalo"}, {"got", NULL, "Gothic"}, {"grb", NULL, "Grebo"}, {"grc", NULL, "Greek, Ancient (to 1453)"}, {"gre/ell", "el", "Greek, Modern (1453-)"}, // dual codes {"grn", "gn", "Guarani"}, {"gsw", NULL, "Alemanic; Swiss German"}, {"guj", "gu", "Gujarati"}, {"gwi", NULL, "Gwich\u00abin"}, {"hai", NULL, "Haida"}, {"hat", "ht", "Haitian; Haitian Creole"}, {"hau", "ha", "Hausa"}, {"haw", NULL, "Hawaiian"}, {"heb", "he", "Hebrew"}, {"her", "hz", "Herero"}, {"hil", NULL, "Hiligaynon"}, {"him", NULL, "Himachali"}, {"hin", "hi", "Hindi"}, {"hit", NULL, "Hittite"}, {"hmn", NULL, "Hmong"}, {"hmo", "ho", "Hiri Motu"}, {"hsb", NULL, "Upper Sorbian"}, {"hun", "hu", "Hungarian"}, {"hup", NULL, "Hupa"}, {"arm/hye", "hy", "Armenian"}, {"iba", NULL, "Iban"}, {"ibo", "ig", "Igbo"}, {"ice/isl", "is", "Icelandic"}, // dual codes {"ido", "io", "Ido"}, {"iii", "ii", "Sichuan Yi"}, {"ijo", NULL, "Ijo"}, {"iku", "iu", "Inuktitut"}, {"ile", "ie", "Interlingue"}, {"ilo", NULL, "Iloko"}, {"ina", "ia", "Interlingua (International Auxiliary, Language Association)"}, {"inc", NULL, "Indic (Other)"}, {"ind", "id", "Indonesian"}, {"ine", NULL, "Indo-European (Other)"}, {"inh", NULL, "Ingush"}, {"ipk", "ik", "Inupiaq"}, {"ira", NULL, "Iranian (Other)"}, {"iro", NULL, "Iroquoian languages"}, {"ita", "it", "Italian"}, {"jav", "jv", "Javanese"}, {"jbo", NULL, "Lojban"}, {"jpn", "ja", "Japanese"}, {"jpr", NULL, "Judeo-Persian"}, {"jrb", NULL, "Judeo-Arabic"}, {"kaa", NULL, "Kara-Kalpak"}, {"kab", NULL, "Kabyle"}, {"kac", NULL, "Kachin"}, {"kal", "kl", "Kalaallisut; Greenlandic"}, {"kam", NULL, "Kamba"}, {"kan", "kn", "Kannada"}, {"kar", NULL, "Karen"}, {"kas", "ks", "Kashmiri"}, {"kau", "kr", "Kanuri"}, {"kaw", NULL, "Kawi"}, {"kaz", "kk", "Kazakh"}, {"kbd", NULL, "Kabardian"}, {"kha", NULL, "Khasi"}, {"khi", NULL, "Khoisan (Other)"}, {"khm", "km", "Khmer"}, {"kho", NULL, "Khotanese"}, {"kik", "ki", "Kikuyu; Gikuyu"}, {"kin", "rw", "Kinyarwanda"}, {"kir", "ky", "Kirghiz"}, {"kmb", NULL, "Kimbundu"}, {"kok", NULL, "Konkani"}, {"kom", "kv", "Komi"}, {"kon", "kg", "Kongo"}, {"kor", "ko", "Korean"}, {"kos", NULL, "Kosraean"}, {"kpe", NULL, "Kpelle"}, {"krc", NULL, "Karachay-Balkar"}, {"krl", NULL, "Karelian"}, {"kro", NULL, "Kru"}, {"kru", NULL, "Kurukh"}, {"kua", "kj", "Kuanyama; Kwanyama"}, {"kum", NULL, "Kumyk"}, {"kur", "ku", "Kurdish"}, {"kut", NULL, "Kutenai"}, {"lad", NULL, "Ladino"}, {"lah", NULL, "Lahnda"}, {"lam", NULL, "Lamba"}, {"lao", "lo", "Lao"}, {"lat", "la", "Latin"}, {"lav", "lv", "Latvian"}, {"lez", NULL, "Lezghian"}, {"lim", "li", "Limburgan; Limburger; Limburgish"}, {"lin", "ln", "Lingala"}, {"lit", "lt", "Lithuanian"}, {"lol", NULL, "Mongo"}, {"loz", NULL, "Lozi"}, {"ltz", "lb", "Luxembourgish; Letzeburgesch"}, {"lua", NULL, "Luba-Lulua"}, {"lub", "lu", "Luba-Katanga"}, {"lug", "lg", "Ganda"}, {"lui", NULL, "Luiseno"}, {"lun", NULL, "Lunda"}, {"luo", NULL, "Luo (Kenya and Tanzania)"}, {"lus", NULL, "Lushai"}, {"mad", NULL, "Madurese"}, {"mag", NULL, "Magahi"}, {"mah", "mh", "Marshallese"}, {"mai", NULL, "Maithili"}, {"mak", NULL, "Makasar"}, {"mal", "ml", "Malayalam"}, {"man", NULL, "Mandingo"}, {"map", NULL, "Austronesian (Other)"}, {"mar", "mr", "Marathi"}, {"mas", NULL, "Masai"}, {"may/msa", "ms", "Malay"}, // dual codes {"mdf", NULL, "Moksha"}, {"mdr", NULL, "Mandar"}, {"men", NULL, "Mende"}, {"mga", NULL, "Irish, Middle (900-1200)"}, {"mic", NULL, "Mi'kmaq; Micmac"}, {"min", NULL, "Minangkabau"}, {"mis", NULL, "Miscellaneous languages"}, {"mac/mkd", "mk", "Macedonian"}, // dual codes {"mkh", NULL, "Mon-Khmer (Other)"}, {"mlg", "mg", "Malagasy"}, {"mlt", "mt", "Maltese"}, {"mnc", NULL, "Manchu"}, {"mni", NULL, "Manipuri"}, {"mno", NULL, "Manobo languages"}, {"moh", NULL, "Mohawk"}, {"mol", "mo", "Moldavian"}, {"mon", "mn", "Mongolian"}, {"mos", NULL, "Mossi"}, {"mao/mri", "mi", "Maori"}, // dual codes {"mul", NULL, "Multiple languages"}, {"mun", NULL, "Munda languages"}, {"mus", NULL, "Creek"}, {"mwl", NULL, "Mirandese"}, {"mwr", NULL, "Marwari"}, {"myn", NULL, "Mayan languages"}, {"myv", NULL, "Erzya"}, {"nah", NULL, "Nahuatl"}, {"nai", NULL, "North American Indian"}, {"nap", NULL, "Neapolitan"}, {"nau", "na", "Nauru"}, {"nav", "nv", "Navajo; Navaho"}, {"nbl", "nr", "Ndebele, South; South Ndebele"}, {"nde", "nd", "Ndebele, North; North Ndebele"}, {"ndo", "ng", "Ndonga"}, {"nds", NULL, "Low German; Low Saxon; German, Low; Saxon, Low"}, {"nep", "ne", "Nepali"}, {"new", NULL, "Newari; Nepal Bhasa"}, {"nia", NULL, "Nias"}, {"nic", NULL, "Niger-Kordofanian (Other)"}, {"niu", NULL, "Niuean"}, {"nno", "nn", "Norwegian Nynorsk; Nynorsk, Norwegian"}, {"nob", "nb", "Norwegian Bokm\x8cl; Bokm\x8cl, Norwegian"}, {"nog", NULL, "Nogai"}, {"non", NULL, "Norse, Old"}, {"nor", "no", "Norwegian"}, {"nqo", NULL, "N'ko"}, {"nso", NULL, "Northern Sotho, Pedi; Sepedi"}, {"nub", NULL, "Nubian languages"}, {"nwc", NULL, "Classical Newari; Old Newari; Classical Nepal Bhasa"}, {"nya", "ny", "Chichewa; Chewa; Nyanja"}, {"nym", NULL, "Nyamwezi"}, {"nyn", NULL, "Nyankole"}, {"nyo", NULL, "Nyoro"}, {"nzi", NULL, "Nzima"}, {"oci", "oc", "Occitan (post 1500); Proven\u00c7al"}, {"oji", "oj", "Ojibwa"}, {"ori", "or", "Oriya"}, {"orm", "om", "Oromo"}, {"osa", NULL, "Osage"}, {"oss", "os", "Ossetian; Ossetic"}, {"ota", NULL, "Turkish, Ottoman (1500-1928)"}, {"oto", NULL, "Otomian languages"}, {"paa", NULL, "Papuan (Other)"}, {"pag", NULL, "Pangasinan"}, {"pal", NULL, "Pahlavi"}, {"pam", NULL, "Pampanga"}, {"pan", "pa", "Panjabi; Punjabi"}, {"pap", NULL, "Papiamento"}, {"pau", NULL, "Palauan"}, {"peo", NULL, "Persian, Old (ca.600-400 B.C.)"}, {"per/fas", "fa", "Persian"}, // dual codes {"phi", NULL, "Philippine (Other)"}, {"phn", NULL, "Phoenician"}, {"pli", "pi", "Pali"}, {"pol", "pl", "Polish"}, {"pon", NULL, "Pohnpeian"}, {"por", "pt", "Portuguese"}, {"pra", NULL, "Prakrit languages"}, {"pro", NULL, "Proven\u00c7al, Old (to 1500)"}, {"pus", "ps", "Pushto"}, //{ "qaa-qtz", NULL, "Reserved for local use" }, {"que", "qu", "Quechua"}, {"raj", NULL, "Rajasthani"}, {"rap", NULL, "Rapanui"}, {"rar", NULL, "Rarotongan"}, {"roa", NULL, "Romance (Other)"}, {"roh", "rm", "Raeto-Romance"}, {"rom", NULL, "Romany"}, {"rum/ron", "ro", "Romanian"}, // dual codes {"run", "rn", "Rundi"}, {"rup", NULL, "Aromanian; Arumanian; Macedo-Romanian"}, {"rus", "ru", "Russian"}, {"sad", NULL, "Sandawe"}, {"sag", "sg", "Sango"}, {"sah", NULL, "Yakut"}, {"sai", NULL, "South American Indian (Other)"}, {"sal", NULL, "Salishan languages"}, {"sam", NULL, "Samaritan Aramaic"}, {"san", "sa", "Sanskrit"}, {"sas", NULL, "Sasak"}, {"sat", NULL, "Santali"}, {"scn", NULL, "Sicilian"}, {"sco", NULL, "Scots"}, {"scr/hrv", "hr", "Croatian"}, // dual codes {"sel", NULL, "Selkup"}, {"sem", NULL, "Semitic (Other)"}, {"sga", NULL, "Irish, Old (to 900)"}, {"sgn", NULL, "Sign Languages"}, {"shn", NULL, "Shan"}, {"sid", NULL, "Sidamo"}, {"sin", "si", "Sinhala; Sinhalese"}, {"sio", NULL, "Siouan languages"}, {"sit", NULL, "Sino-Tibetan (Other)"}, {"sla", NULL, "Slavic (Other)"}, {"slo/slk", "sk", "Slovak"}, // dual codes {"slv", "sl", "Slovenian"}, {"sma", NULL, "Southern Sami"}, {"sme", "se", "Northern Sami"}, {"smi", NULL, "Sami languages (Other)"}, {"smj", NULL, "Lule Sami"}, {"smn", NULL, "Inari Sami"}, {"smo", "sm", "Samoan"}, {"sms", NULL, "Skolt Sami"}, {"sna", "sn", "Shona"}, {"snd", "sd", "Sindhi"}, {"snk", NULL, "Soninke"}, {"sog", NULL, "Sogdian"}, {"som", "so", "Somali"}, {"son", NULL, "Songhai"}, {"sot", "st", "Sotho, Southern"}, {"spa", "es", "Spanish; Castilian"}, {"srd", "sc", "Sardinian"}, {"srn", NULL, "Sranan Togo"}, {"scc/srp", "sr", "Serbian"}, // dual codes {"srr", NULL, "Serer"}, {"ssa", NULL, "Nilo-Saharan (Other)"}, {"ssw", "ss", "Swati"}, {"suk", NULL, "Sukuma"}, {"sun", "su", "Sundanese"}, {"sus", NULL, "Susu"}, {"sux", NULL, "Sumerian"}, {"swa", "sw", "Swahili"}, {"swe", "sv", "Swedish"}, {"syr", NULL, "Syriac"}, {"tah", "ty", "Tahitian"}, {"tai", NULL, "Tai (Other)"}, {"tam", "ta", "Tamil"}, {"tat", "tt", "Tatar"}, {"tel", "te", "Telugu"}, {"tem", NULL, "Timne"}, {"ter", NULL, "Tereno"}, {"tet", NULL, "Tetum"}, {"tgk", "tg", "Tajik"}, {"tgl", "tl", "Tagalog"}, {"tha", "th", "Thai"}, {"tib/bod", "bo", "Tibetan"}, // dual codes {"tig", NULL, "Tigre"}, {"tir", "ti", "Tigrinya"}, {"tiv", NULL, "Tiv"}, {"tkl", NULL, "Tokelau"}, {"tlh", NULL, "Klingon; tlhIngan-Hol"}, {"tli", NULL, "Tlingit"}, {"tmh", NULL, "Tamashek"}, {"tog", NULL, "Tonga (Nyasa)"}, {"ton", "to", "Tonga (Tonga Islands)"}, {"tpi", NULL, "Tok Pisin"}, {"tsi", NULL, "Tsimshian"}, {"tsn", "tn", "Tswana"}, {"tso", "ts", "Tsonga"}, {"tuk", "tk", "Turkmen"}, {"tum", NULL, "Tumbuka"}, {"tup", NULL, "Tupi languages"}, {"tur", "tr", "Turkish"}, {"tut", NULL, "Altaic (Other)"}, {"tvl", NULL, "Tuvalu"}, {"twi", "tw", "Twi"}, {"tyv", NULL, "Tuvinian"}, {"udm", NULL, "Udmurt"}, {"uga", NULL, "Ugaritic"}, {"uig", "ug", "Uighur; Uyghur"}, {"ukr", "uk", "Ukrainian"}, {"umb", NULL, "Umbundu"}, {"und", NULL, "Undetermined"}, {"urd", "ur", "Urdu"}, {"uzb", "uz", "Uzbek"}, {"vai", NULL, "Vai"}, {"ven", "ve", "Venda"}, {"vie", "vi", "Vietnamese"}, {"vol", "vo", "Volap\u00fck"}, {"vot", NULL, "Votic"}, {"wak", NULL, "Wakashan languages"}, {"wal", NULL, "Walamo"}, {"war", NULL, "Waray"}, {"was", NULL, "Washo"}, {"wel/cym", "cy", "Welsh"}, // //dual codes {"wen", NULL, "Sorbian languages"}, {"wln", "wa", "Walloon"}, {"wol", "wo", "Wolof"}, {"xal", NULL, "Kalmyk; Oirat"}, {"xho", "xh", "Xhosa"}, {"yao", NULL, "Yao"}, {"yap", NULL, "Yapese"}, {"yid", "yi", "Yiddish"}, {"yor", "yo", "Yoruba"}, {"ypk", NULL, "Yupik languages"}, {"zap", NULL, "Zapotec"}, {"zen", NULL, "Zenaga"}, {"zha", "za", "Zhuang; Chuang"}, {"chi/zho", "zh", "Chinese"}, // dual codes {"znd", NULL, "Zande"}, {"zul", "zu", "Zulu"}, {"zun", NULL, "Zuni"}, {"zxx", NULL, "No linguistic content"}}; m_ratings known_ratings[] = { {"us-tv|TV-MA|600|", "TV-MA"}, {"us-tv|TV-14|500|", "TV-14"}, {"us-tv|TV-PG|400|", "TV-PG"}, {"us-tv|TV-G|300|", "TV-G"}, {"us-tv|TV-Y7|200|", "TV-Y7"}, {"us-tv|TV-Y|100|", "TV-Y"}, //{ "us-tv||0|", "not-applicable" }, //though its a valid flag & // some files have this, AP won't be setting it. {"mpaa|UNRATED|600|", "Unrated"}, {"mpaa|NC-17|500|", "NC-17"}, {"mpaa|R|400|", "R"}, {"mpaa|PG-13|300|", "PG-13"}, {"mpaa|PG|200|", "PG"}, {"mpaa|G|100|", "G"} //{ "mpaa||0|", "not-applicable" } //see above }; char *GenreIntToString(int genre) { char *return_string = NULL; if (genre > 0 && genre <= (int)(sizeof(ID3v1GenreList) / sizeof(*ID3v1GenreList))) { return_string = (char *)ID3v1GenreList[genre - 1]; } return return_string; } uint8_t StringGenreToInt(const char *genre_string) { uint8_t return_genre = 0; uint8_t total_genres = (uint8_t)(sizeof(ID3v1GenreList) / sizeof(*ID3v1GenreList)); for (uint8_t i = 0; i < total_genres; i++) { if (strcmp(genre_string, ID3v1GenreList[i]) == 0) { return_genre = i + 1; // the list starts at 0; the embedded genres start at 1 // fprintf(stdout, "Genre %s is %i\n", ID3v1GenreList[i], return_genre); break; } } if (return_genre > total_genres) { return_genre = 0; } return return_genre; } void ListGenresValues() { uint8_t total_genres = (uint8_t)(sizeof(ID3v1GenreList) / sizeof(*ID3v1GenreList)); fprintf(stdout, "\tAvailable standard genres - case sensitive.\n"); for (uint8_t i = 0; i < total_genres; i++) { fprintf(stdout, "(%i.) %s\n", i + 1, ID3v1GenreList[i]); } return; } stiks *MatchStikString(const char *in_stik_string) { stiks *matching_stik = NULL; uint8_t total_known_stiks = (sizeof(stikArray) / sizeof(*stikArray)); for (uint8_t i = 0; i < total_known_stiks; i++) { if (strcmp(in_stik_string, stikArray[i].stik_string) == 0) { matching_stik = &stikArray[i]; break; } } return matching_stik; } stiks *MatchStikNumber(uint8_t in_stik_num) { stiks *matching_stik = NULL; uint8_t total_known_stiks = (sizeof(stikArray) / sizeof(*stikArray)); for (uint8_t i = 0; i < total_known_stiks; i++) { if (stikArray[i].stik_number == in_stik_num) { matching_stik = &stikArray[i]; break; } } return matching_stik; } void ListStikValues() { uint8_t total_known_stiks = (sizeof(stikArray) / sizeof(*stikArray)); fprintf(stdout, "\tAvailable stik settings - case sensitive (number in " "parens shows the stik value).\n"); for (uint8_t i = 0; i < total_known_stiks; i++) { fprintf(stdout, "(%u) %s\n", stikArray[i].stik_number, stikArray[i].stik_string); } return; } sfIDs *MatchStoreFrontNumber(uint32_t storefrontnum) { sfIDs *matching_sfID = NULL; uint8_t total_known_sfs = (sizeof(storefronts) / sizeof(*storefronts)); for (uint8_t i = 0; i < total_known_sfs; i++) { if (storefronts[i].storefront_number == storefrontnum) { matching_sfID = &storefronts[i]; break; } } return matching_sfID; } bool MatchLanguageCode(const char *in_code) { bool matching_lang = false; uint16_t total_known_langs = (uint16_t)(sizeof(known_languages) / sizeof(*known_languages)); for (uint16_t i = 0; i < total_known_langs; i++) { if (strncmp(in_code, known_languages[i].iso639_2_code, 3) == 0) { matching_lang = true; break; } if (strlen(known_languages[i].iso639_2_code) > 3) { if (strncmp(in_code, known_languages[i].iso639_2_code + 4, 3) == 0) { matching_lang = true; break; } } } return matching_lang; } void ListLanguageCodes() { uint16_t total_known_langs = (uint16_t)(sizeof(known_languages) / sizeof(*known_languages)); fprintf(stdout, "\tAvailable language codes\nISO639-2 code ... English name:\n"); for (uint16_t i = 0; i < total_known_langs; i++) { fprintf(stdout, " %s ... %s\n", known_languages[i].iso639_2_code, known_languages[i].language_in_english); } return; } void ListMediaRatings() { uint16_t total_known_ratings = (uint16_t)(sizeof(known_ratings) / sizeof(*known_ratings)); fprintf(stdout, "\tAvailable ratings for the U.S. rating system:\n"); for (uint16_t i = 0; i < total_known_ratings; i++) { fprintf(stdout, " %s\n", known_ratings[i].media_rating_cli_str); } return; } void ListTVGenreIDValues() { uint16_t total_genreidtv = (uint16_t)(sizeof(genreidtv) / sizeof(*genreidtv)); fprintf(stdout, "\tAvailable iTunes TV Genre IDs:\n"); for (uint16_t i = 0; i < total_genreidtv; i++) { fprintf(stdout, "(%u) %s\n", genreidtv[i].genre_id_tv_value, genreidtv[i].genre_id_tv_string); } return; } void ListMovieGenreIDValues() { uint16_t total_genreidmovie = (uint16_t)(sizeof(genreidmovie) / sizeof(*genreidmovie)); fprintf(stdout, "\tAvailable iTunes Movie Genre IDs:\n"); for (uint16_t i = 0; i < total_genreidmovie; i++) { fprintf(stdout, "(%u) %s\n", genreidmovie[i].genre_id_movie_value, genreidmovie[i].genre_id_movie_string); } return; } const char *Expand_cli_mediastring(const char *cli_rating) { const char *media_rating = NULL; uint16_t total_known_ratings = (uint16_t)(sizeof(known_ratings) / sizeof(*known_ratings)); uint8_t rating_len = strlen(cli_rating); for (uint16_t i = 0; i < total_known_ratings; i++) { if (strncasecmp(known_ratings[i].media_rating_cli_str, cli_rating, rating_len + 1) == 0) { media_rating = known_ratings[i].media_rating; break; } } return media_rating; } // ID32 for ID3 frame functions char *ID3GenreIntToString(int genre) { char *return_string = NULL; if (genre >= 0 && genre <= 79) { return_string = (char *)ID3v1GenreList[genre]; } return return_string; } uint8_t ID3StringGenreToInt(const char *genre_string) { uint8_t return_genre = 0xFF; uint8_t total_genres = 80; for (uint8_t i = 0; i < total_genres; i++) { if (strcmp(genre_string, ID3v1GenreList[i]) == 0) { return i; } } if (return_genre > total_genres) { return_genre = 0xFF; } return return_genre; } atomicparsley-20240608.083822.1ed9031/src/compress.cpp000066400000000000000000000056161463107535600215370ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - compress.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C) 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #include "AtomicParsley.h" #ifdef HAVE_ZLIB_H #include #endif static void *zalloc(void *opaque, unsigned int items, unsigned int size) { return calloc(items, size); } static void zfree(void *opaque, void *ptr) { free(ptr); } /*---------------------- APar_zlib_inflate in_buffer - pointer to already compressed data in_buf_len - length of compressed data out_buffer - pointer to a buffer to store decompressed/inflated data out_buf_len - length of the out_buffer/max allowable decompressed size fill ----------------------*/ void APar_zlib_inflate(char *in_buffer, uint32_t in_buf_len, char *out_buffer, uint32_t out_buf_len) { #if defined HAVE_ZLIB_H z_stream zlib; memset(&zlib, 0, sizeof(zlib)); // Decompress to another buffer zlib.zalloc = zalloc; zlib.zfree = zfree; zlib.opaque = NULL; zlib.avail_out = out_buf_len + 1; zlib.next_out = (unsigned char *)out_buffer; zlib.avail_in = in_buf_len; zlib.next_in = (unsigned char *)in_buffer; inflateInit(&zlib); inflate(&zlib, Z_PARTIAL_FLUSH); inflateEnd(&zlib); #endif return; } uint32_t APar_zlib_deflate(char *in_buffer, uint32_t in_buf_len, char *out_buffer, uint32_t out_buf_len) { uint32_t compressed_bytes = 0; #if defined HAVE_ZLIB_H z_stream zlib; memset(&zlib, 0, sizeof(zlib)); // Compress(default level 6) to another buffer zlib.zalloc = zalloc; zlib.zfree = zfree; zlib.opaque = NULL; zlib.avail_out = out_buf_len + 1; zlib.next_out = (unsigned char *)out_buffer; zlib.avail_in = in_buf_len; zlib.next_in = (unsigned char *)in_buffer; zlib.total_out = 0; deflateInit(&zlib, Z_DEFAULT_COMPRESSION); if (Z_STREAM_END == deflate(&zlib, Z_FINISH)) { compressed_bytes = zlib.total_out; deflateEnd(&zlib); } #endif return compressed_bytes; } atomicparsley-20240608.083822.1ed9031/src/extracts.cpp000066400000000000000000002061771463107535600215460ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - extracts.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C) 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #include "AtomicParsley.h" MovieInfo movie_info = {0}; iods_OD iods_info = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; /////////////////////////////////////////////////////////////////////////////////////// // File reading routines // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_skip_filler isofile - the file to be scanned start_position - the offset from the start of file where to commence possible skipping I can't remember where exactly I stumbled over what to skip, but this touches on it: http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt (not that everything there is the gospel truth). ----------------------*/ uint8_t APar_skip_filler(FILE *isofile, uint32_t start_position) { uint8_t skip_bytes = 0; while (true) { uint8_t eval_byte = APar_read8(isofile, start_position + skip_bytes); if (eval_byte == 0x80 || eval_byte == 0x81 || eval_byte == 0xFE) { // seems sometimes QT writes 0x81 skip_bytes++; } else { break; } } return skip_bytes; } /////////////////////////////////////////////////////////////////////////////////////// // string routines // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- uint32tochar4 lnum - the number to convert to a string data - the string to hold the conversion Returns a pointer to the originating string (used to print out a string; different from the other function which converts returning void) ----------------------*/ char *uint32tochar4(uint32_t lnum, char *data) { data[0] = (lnum >> 24) & 0xff; data[1] = (lnum >> 16) & 0xff; data[2] = (lnum >> 8) & 0xff; data[3] = (lnum >> 0) & 0xff; return data; } /*---------------------- purge_extraneous_characters data - the string which may contain low or high ascii Just change most non-textual characters (like a pesky new line char) to something less objectionable - for stdout formatting only ----------------------*/ uint16_t purge_extraneous_characters(char *data) { uint16_t purgings = 0; uint16_t str_len = strlen(data); for (uint16_t str_offset = 0; str_offset < str_len; str_offset++) { if (data[str_offset] < 32 || data[str_offset] == 127) { data[str_offset] = 19; purgings++; break; } } return purgings; } void mem_append(const char *add_string, char *dest_string) { uint8_t str_len = strlen(dest_string); if (str_len > 0) { memcpy(dest_string + str_len, ", ", 2); memcpy(dest_string + str_len + 2, add_string, strlen(add_string)); } else { memcpy(dest_string, add_string, strlen(add_string)); } return; } /*---------------------- secsTOtime seconds - duration in seconds as a floating point number Convert decimal seconds to hh:mm:ss.milliseconds. Take the whole seconds and manually separate out the hours, minutes and remaining seconds. For the milliseconds, sprintf into a separate string because there doesn't seem to be a way to print without the leading zero; so copy form that string the digits we want then. ----------------------*/ char *secsTOtime(double seconds) { ap_time time_duration = {0}; uint32_t whole_secs = (uint32_t)(seconds / 1); time_duration.rem_millisecs = seconds - (double)whole_secs; time_duration.hours = whole_secs / 3600; whole_secs -= time_duration.hours * 3600; time_duration.minutes = whole_secs / 60; whole_secs -= time_duration.minutes * 60; time_duration.seconds = whole_secs; static char hhmmss_time[20]; memset(hhmmss_time, 0, 20); char milli[5]; memset(milli, 0, 5); uint8_t time_offset = 0; if (time_duration.hours > 0) { if (time_duration.hours < 10) { sprintf(hhmmss_time, "0%u:", time_duration.hours); } else { sprintf(hhmmss_time, "%u:", time_duration.hours); } time_offset += 3; } if (time_duration.minutes > 0) { if (time_duration.minutes < 10) { sprintf(hhmmss_time + time_offset, "0%u:", time_duration.minutes); } else { sprintf(hhmmss_time + time_offset, "%u:", time_duration.minutes); } time_offset += 3; } else { memcpy(hhmmss_time + time_offset, "0:", 2); time_offset += 2; } if (time_duration.seconds > 0) { if (time_duration.seconds < 10) { sprintf(hhmmss_time + time_offset, "0%u", time_duration.seconds); } else { sprintf(hhmmss_time + time_offset, "%u", time_duration.seconds); } time_offset += 2; } else { memcpy(hhmmss_time + time_offset, "0.", 2); time_offset += 1; } sprintf( milli, "%.2lf", time_duration.rem_millisecs); // sprintf the double float into a new // string because I don't know if there is a // way to print without a leading zero memcpy(hhmmss_time + time_offset, milli + 1, 3); return *&hhmmss_time; } /////////////////////////////////////////////////////////////////////////////////////// // Print Profile Info // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_ShowMPEG4VisualProfileInfo track_info - a pointer to the struct holding all the information gathered as a single 'trak' atom was traversed If a movie-level iods (containing profiles on a movie-level basis), prefer that mechanism for choosing which profile, otherwise fall back to 'esds' profiles. Much of this was garnered from ISO 14496-2:2001 - up to 'Simple Studio'. ----------------------*/ void APar_ShowMPEG4VisualProfileInfo(TrackInfo *track_info) { fprintf(stdout, " MPEG-4 Visual "); uint8_t mp4v_profile = 0; if (movie_info.contains_iods) { mp4v_profile = iods_info.video_profile_level; } else { mp4v_profile = track_info->m4v_profile; } // unparalleled joy - Annex G table g1 - a binary listing (this from // 14496-2:2001) if (mp4v_profile == 0x01) { fprintf(stdout, "Simple Profile, Level 1"); // 00000001 } else if (mp4v_profile == 0x02) { fprintf(stdout, "Simple Profile, Level 2"); // 00000010 } else if (mp4v_profile == 0x03) { fprintf(stdout, "Simple Profile, Level 3"); // most files will land here //00000011 } else if (mp4v_profile == 0x08) { // Compressor can create these in 3gp files fprintf(stdout, "Simple Profile, Level 0"); // ISO 14496-2:2004(e) // //00001000 // Reserved 00000100 - 00000111 } else if (mp4v_profile == 0x10) { fprintf(stdout, "Simple Scalable Profile, Level 0"); // 00010000 } else if (mp4v_profile == 0x11) { fprintf(stdout, "Simple Scalable Profile, Level 1"); // 00010001 } else if (mp4v_profile == 0x12) { fprintf(stdout, "Simple Scalable Profile, Level 2"); // 00010010 // Reserved 00010011 - 00100000 } else if (mp4v_profile == 0x21) { fprintf(stdout, "Core Profile, Level 1"); // 00100001 } else if (mp4v_profile == 0x22) { fprintf(stdout, "Core Profile, Level 2"); // 00100010 // Reserved 00100011 - 00110001 } else if (mp4v_profile == 0x32) { fprintf(stdout, "Main Profile, Level 2"); // 00110010 } else if (mp4v_profile == 0x33) { fprintf(stdout, "Main Profile, Level 3"); // 00110011 } else if (mp4v_profile == 0x34) { fprintf(stdout, "Main Profile, Level 4"); // 00110100 // Reserved 00110101 - 01000001 } else if (mp4v_profile == 0x42) { fprintf(stdout, "N-bit Profile, Level 2"); // 01000010 // Reserved 01000011 - 01010000 } else if (mp4v_profile == 0x51) { fprintf(stdout, "Scalable Texture Profile, Level 1"); // 01010001 // Reserved 01010010 - 01100000 } else if (mp4v_profile == 0x61) { fprintf(stdout, "Simple Face Animation, Level 1"); // 01100001 } else if (mp4v_profile == 0x62) { fprintf(stdout, "Simple Face Animation, Level 2"); // 01100010 } else if (mp4v_profile == 0x63) { fprintf(stdout, "Simple FBA Profile, Level 1"); // 01100011 } else if (mp4v_profile == 0x64) { fprintf(stdout, "Simple FBA Profile, Level 2"); // 01100100 // Reserved 01100101 - 01110000 } else if (mp4v_profile == 0x71) { fprintf(stdout, "Basic Animated Texture Profile, Level 1"); // 01110001 } else if (mp4v_profile == 0x72) { fprintf(stdout, "Basic Animated Texture Profile, Level 2"); // 01110010 // Reserved 01110011 - 10000000 } else if (mp4v_profile == 0x81) { fprintf(stdout, "Hybrid Profile, Level 1"); // 10000001 } else if (mp4v_profile == 0x82) { fprintf(stdout, "Hybrid Profile, Level 2"); // 10000010 // Reserved 10000011 - 10010000 } else if (mp4v_profile == 0x91) { fprintf(stdout, "Advanced Real Time Simple Profile, Level 1"); // 10010001 } else if (mp4v_profile == 0x92) { fprintf(stdout, "Advanced Real Time Simple Profile, Level 2"); // 10010010 } else if (mp4v_profile == 0x93) { fprintf(stdout, "Advanced Real Time Simple Profile, Level 3"); // 10010011 } else if (mp4v_profile == 0x94) { fprintf(stdout, "Advanced Real Time Simple Profile, Level 4"); // 10010100 // Reserved 10010101 - 10100000 } else if (mp4v_profile == 0xA1) { fprintf(stdout, "Core Scalable Profile, Level 1"); // 10100001 } else if (mp4v_profile == 0xA2) { fprintf(stdout, "Core Scalable Profile, Level 2"); // 10100010 } else if (mp4v_profile == 0xA3) { fprintf(stdout, "Core Scalable Profile, Level 3"); // 10100011 // Reserved 10100100 - 10110000 } else if (mp4v_profile == 0xB1) { fprintf(stdout, "Advanced Coding Efficiency Profile, Level 1"); // 10110001 } else if (mp4v_profile == 0xB2) { fprintf(stdout, "Advanced Coding Efficiency Profile, Level 2"); // 10110010 } else if (mp4v_profile == 0xB3) { fprintf(stdout, "Advanced Coding Efficiency Profile, Level 3"); // 10110011 } else if (mp4v_profile == 0xB4) { fprintf(stdout, "Advanced Coding Efficiency Profile, Level 4"); // 10110100 // Reserved 10110101 11000000 } else if (mp4v_profile == 0xC1) { fprintf(stdout, "Advanced Core Profile, Level 1"); // 11000001 } else if (mp4v_profile == 0xC2) { fprintf(stdout, "Advanced Core Profile, Level 2"); // 11000010 // Reserved 11000011 11010000 } else if (mp4v_profile == 0xD1) { fprintf(stdout, "Advanced Scalable Texture, Level 1"); // 11010001 } else if (mp4v_profile == 0xD2) { fprintf(stdout, "Advanced Scalable Texture, Level 2"); // 11010010 } else if (mp4v_profile == 0xD2) { fprintf(stdout, "Advanced Scalable Texture, Level 3"); // 11010011 // from a draft document - 1999 (earlier than the 2000 above!!) } else if (mp4v_profile == 0xE1) { fprintf(stdout, "Simple Studio Profile, Level 1"); // 11100001 } else if (mp4v_profile == 0xE2) { fprintf(stdout, "Simple Studio Profile, Level 2"); // 11100010 } else if (mp4v_profile == 0xE3) { fprintf(stdout, "Simple Studio Profile, Level 3"); // 11100011 } else if (mp4v_profile == 0xE4) { fprintf(stdout, "Simple Studio Profile, Level 4"); // 11100100 } else if (mp4v_profile == 0xE5) { fprintf(stdout, "Core Studio Profile, Level 1"); // 11100101 } else if (mp4v_profile == 0xE6) { fprintf(stdout, "Core Studio Profile, Level 2"); // 11100110 } else if (mp4v_profile == 0xE7) { fprintf(stdout, "Core Studio Profile, Level 3"); // 11100111 } else if (mp4v_profile == 0xE8) { fprintf(stdout, "Core Studio Profile, Level 4"); // 11101000 // Reserved 11101001 - 11101111 // ISO 14496-2:2004(e) } else if (mp4v_profile == 0xF0) { fprintf(stdout, "Advanced Simple Profile, Level 0"); // 11110000 } else if (mp4v_profile == 0xF1) { fprintf(stdout, "Advanced Simple Profile, Level 1"); // 11110001 } else if (mp4v_profile == 0xF2) { fprintf( stdout, "Advanced Simple Profile, Level 2"); // 11110010 ////3gp files that QT // says is H.263 have esds to 0xF2 // & their ObjectType set to 0x20 // (mpeg-4 visual) ////...and its been figured out - /// FILE EXTENSION of all things /// determines mpeg-4 ASP or H.263 } else if (mp4v_profile == 0xF3) { fprintf(stdout, "Advanced Simple Profile, Level 3"); // 11110011 } else if (mp4v_profile == 0xF4) { fprintf(stdout, "Advanced Simple Profile, Level 4"); // 11110100 } else if (mp4v_profile == 0xF5) { fprintf(stdout, "Advanced Simple Profile, Level 5"); // 11110101 // Reserved 11110110 } else if (mp4v_profile == 0xF7) { fprintf(stdout, "Advanced Simple Profile, Level 3b"); // 11110111 } else if (mp4v_profile == 0xF7) { fprintf(stdout, "Fine Granularity Scalable Profile/Level 0"); // 11111000 } else if (mp4v_profile == 0xF7) { fprintf(stdout, "Fine Granularity Scalable Profile/Level 1"); // 11111001 } else if (mp4v_profile == 0xF7) { fprintf(stdout, "Fine Granularity Scalable Profile/Level 2"); // 11111010 } else if (mp4v_profile == 0xF7) { fprintf(stdout, "Fine Granularity Scalable Profile/Level 3"); // 11111011 } else if (mp4v_profile == 0xF7) { fprintf(stdout, "Fine Granularity Scalable Profile/Level 4"); // 11111100 } else if (mp4v_profile == 0xF7) { fprintf(stdout, "Fine Granularity Scalable Profile/Level 5"); // 11111101 // Reserved 11111110 // Reserved for Escape 11111111 } else { fprintf(stdout, "Unknown profile: 0x%X", mp4v_profile); } return; } /*---------------------- APar_ShowMPEG4AACProfileInfo track_info - a pointer to the struct holding all the information gathered as a single 'trak' atom was traversed ----------------------*/ void APar_ShowMPEG4AACProfileInfo(TrackInfo *track_info) { if (track_info->descriptor_object_typeID == 1) { fprintf(stdout, " MPEG-4 AAC Main Profile"); } else if (track_info->descriptor_object_typeID == 2) { fprintf( stdout, " MPEG-4 AAC Low Complexity/LC Profile"); // most files will land here } else if (track_info->descriptor_object_typeID == 3) { fprintf(stdout, " MPEG-4 AAC Scaleable Sample Rate/SSR Profile"); } else if (track_info->descriptor_object_typeID == 4) { fprintf(stdout, " MPEG-4 AAC Long Term Prediction Profile"); } else if (track_info->descriptor_object_typeID == 5) { fprintf(stdout, " MPEG-4 AAC High Efficiency/HE Profile"); } else if (track_info->descriptor_object_typeID == 6) { fprintf(stdout, " MPEG-4 AAC Scalable Profile"); } else if (track_info->descriptor_object_typeID == 7) { fprintf(stdout, " MPEG-4 AAC Transform domain Weighted INterleave Vector " "Quantization/TwinVQ Profile"); } else if (track_info->descriptor_object_typeID == 8) { fprintf(stdout, " MPEG-4 AAC Code Excited Linear Predictive/CELP Profile"); } else if (track_info->descriptor_object_typeID == 9) { fprintf(stdout, " MPEG-4 AAC HVXC Profile"); } else if (track_info->descriptor_object_typeID == 12) { fprintf(stdout, " MPEG-4 AAC TTSI Profile"); } else if (track_info->descriptor_object_typeID == 13) { fprintf(stdout, " MPEG-4 AAC Main Synthesis Profile"); } else if (track_info->descriptor_object_typeID == 14) { fprintf(stdout, " MPEG-4 AAC Wavetable Synthesis Profile"); } else if (track_info->descriptor_object_typeID == 15) { fprintf(stdout, " MPEG-4 AAC General MIDI Profile"); } else if (track_info->descriptor_object_typeID == 16) { fprintf(stdout, " MPEG-4 AAC Algorithmic Synthesis & Audio FX Profile"); } else if (track_info->descriptor_object_typeID == 17) { fprintf(stdout, " MPEG-4 AAC AAC Low Complexity/LC (+error recovery) Profile"); } else if (track_info->descriptor_object_typeID == 19) { fprintf(stdout, " MPEG-4 AAC Long Term Prediction (+error recovery) Profile"); } else if (track_info->descriptor_object_typeID == 20) { fprintf(stdout, " MPEG-4 AAC Scalable (+error recovery) Profile"); } else if (track_info->descriptor_object_typeID == 21) { fprintf(stdout, " MPEG-4 AAC Transform domain Weighted INterleave Vector " "Quantization/TwinVQ (+error recovery) Profile"); } else if (track_info->descriptor_object_typeID == 22) { fprintf(stdout, " MPEG-4 AAC Bit Sliced Arithmetic Coding/BSAC (+error " "recovery) Profile"); } else if (track_info->descriptor_object_typeID == 23) { fprintf(stdout, " MPEG-4 AAC Low Delay/LD (+error recovery) Profile"); } else if (track_info->descriptor_object_typeID == 24) { fprintf(stdout, " MPEG-4 AAC Code Excited Linear Predictive/CELP (+error " "recovery) Profile"); } else if (track_info->descriptor_object_typeID == 25) { fprintf(stdout, " MPEG-4 AAC HXVC (+error recovery) Profile"); } else if (track_info->descriptor_object_typeID == 26) { fprintf(stdout, " MPEG-4 AAC Harmonic and Individual Lines plus " "Noise/HILN (+error recovery) Profile"); } else if (track_info->descriptor_object_typeID == 27) { fprintf(stdout, " MPEG-4 AAC Parametric (+error recovery) Profile"); } else if (track_info->descriptor_object_typeID == 31) { fprintf( stdout, " MPEG-4 ALS Audio Lossless Coding"); // I think that mp4alsRM18 writes // the channels wrong after // objectedID: 0xF880 has 0 // channels; 0xF890 is 2ch } else { fprintf(stdout, " MPEG-4 Unknown profile: 0x%X", track_info->descriptor_object_typeID); } return; } /*---------------------- APar_ShowObjectProfileInfo track_type - broadly used to determine what types of information (like channels or avc1 profiles) to display track_info - a pointer to the struct holding all the information gathered as a single 'trak' atom was traversed Based on the ObjectTypeIndication in 'esds', show the type of track. For mpeg-4 audio & mpeg-4 visual are handled in a subroutine because there are so many enumerations. avc1 contains 'avcC' which supports a different mechanism. ----------------------*/ void APar_ShowObjectProfileInfo(uint8_t track_type, TrackInfo *track_info) { if (track_info->contains_esds) { switch (track_info->ObjectTypeIndication) { // 0x00 es Lambada/Verboten/Forbidden case 0x01: case 0x02: { fprintf(stdout, " MPEG-4 Systems (BIFS/ObjDesc)"); break; } case 0x03: { fprintf(stdout, " Interaction Stream"); break; } case 0x04: { fprintf(stdout, " MPEG-4 Systems Extended BIFS"); break; } case 0x05: { fprintf(stdout, " MPEG-4 Systems AFX"); break; } case 0x06: { fprintf(stdout, " Font Data Stream"); break; } case 0x08: { fprintf(stdout, " Synthesized Texture Stream"); break; } case 0x07: { fprintf(stdout, " Streaming Text Stream"); break; } // 0x09-0x1F reserved case 0x20: { APar_ShowMPEG4VisualProfileInfo(track_info); break; } case 0x40: { // vererable mpeg-4 aac APar_ShowMPEG4AACProfileInfo(track_info); break; } // 0x41-0x5F reserved case 0x60: { fprintf(stdout, " MPEG-2 Visual Simple Profile"); //'Visual ISO/IEC 13818-2 // Simple Profile' break; } case 0x61: { fprintf(stdout, " MPEG-2 Visual Main Profile"); //'Visual ISO/IEC 13818-2 // Main Profile' break; } case 0x62: { fprintf( stdout, " MPEG-2 Visual SNR Profile"); //'Visual ISO/IEC 13818-2 SNR Profile' break; } case 0x63: { fprintf(stdout, " MPEG-2 Visual Spatial Profile"); //'Visual ISO/IEC 13818-2 // Spatial Profile' break; } case 0x64: { fprintf(stdout, " MPEG-2 Visual High Profile"); //'Visual ISO/IEC 13818-2 // High Profile' break; } case 0x65: { fprintf(stdout, " MPEG-2 Visual 4:2:2 Profile"); //'Visual ISO/IEC // 13818-2 422 Profile' break; } case 0x66: { fprintf( stdout, " MPEG-2 AAC Main Profile"); //'Audio ISO/IEC 13818-7 Main Profile' break; } case 0x67: { fprintf(stdout, " MPEG-2 AAC Low Complexity Profile"); // Audio ISO/IEC 13818-7 // LowComplexity Profile break; } case 0x68: { fprintf( stdout, " MPEG-2 AAC Scaleable Sample Rate Profile"); //'Audio ISO/IEC // 13818-7 Scaleable // Sampling Rate // Profile' break; } case 0x69: { fprintf(stdout, " MPEG-2 Audio"); //'Audio ISO/IEC 13818-3' break; } case 0x6A: { fprintf(stdout, " MPEG-1 Visual"); //'Visual ISO/IEC 11172-2' break; } case 0x6B: { fprintf(stdout, " MPEG-1 Audio"); //'Audio ISO/IEC 11172-3' break; } case 0x6C: { fprintf(stdout, " JPEG"); //'Visual ISO/IEC 10918-1' break; } case 0x6D: { fprintf(stdout, " PNG"); // http://www.mp4ra.org/object.html break; } case 0x6E: { fprintf(stdout, " JPEG2000"); //'Visual ISO/IEC 15444-1' break; } case 0xA0: { fprintf(stdout, " 3GPP2 EVRC Voice"); // http://www.mp4ra.org/object.html break; } case 0xA1: { fprintf(stdout, " 3GPP2 SMV Voice"); // http://www.mp4ra.org/object.html break; } case 0xA2: { fprintf( stdout, " 3GPP2 Compact Multimedia Format"); // http://www.mp4ra.org/object.html break; } // 0xC0-0xE0 user private case 0xE1: { fprintf(stdout, " 3GPP2 QCELP (14K Voice)"); // http://www.mp4ra.org/object.html break; } // 0xE2-0xFE user private // 0xFF no object type specified default: { // so many profiles, so little desire to list them all (in 14496-2 which I // don't have) if (movie_info.contains_iods && iods_info.audio_profile == 0xFE) { fprintf(stdout, " Private user object: 0x%X", track_info->ObjectTypeIndication); } else { fprintf( stdout, " Object Type Indicator: 0x%X Description Ojbect Type ID: 0x%X\n", track_info->ObjectTypeIndication, track_info->descriptor_object_typeID); } break; } } } else if (track_type == AVC1_TRACK) { // profiles & levels are in the 14496-10 pdf (which I don't have access to), // so... http://lists.mpegif.org/pipermail/mp4-tech/2006-January/006255.html // http://iphome.hhi.de/suehring/tml/doc/lenc/html/configfile_8c-source.html // 66=baseline, 77=main, 88=extended; 100=High, 110=High 10, 122=High 4:2:2, // 144=High 4:4:4 switch (track_info->profile) { case 66: { fprintf(stdout, " AVC Baseline Profile"); break; } case 77: { fprintf(stdout, " AVC Main Profile"); break; } case 88: { fprintf(stdout, " AVC Extended Profile"); break; } case 100: { fprintf(stdout, " AVC High Profile"); break; } case 110: { fprintf(stdout, " AVC High 10 Profile"); break; } case 122: { fprintf(stdout, " AVC High 4:2:2 Profile"); break; } case 144: { fprintf(stdout, " AVC High 4:4:4 Profile"); break; } default: { fprintf(stdout, " Unknown Profile: %u", track_info->profile); break; } } // end profile switch // Don't have access to levels either, but working off of: // http://iphome.hhi.de/suehring/tml/doc/lenc/html/configfile_8c-source.html // and the 15 levels it says here: // http://www.chiariglione.org/mpeg/technologies/mp04-avc/index.htm (1b in // http://en.wikipedia.org/wiki/H.264 seems nonsensical) working backwards, // we get... a simple 2 digit number (with '20' just drop the 0; with 21, // put in a decimal) if (track_info->level > 0) { switch (track_info->level) { case 10: case 20: case 30: case 40: case 50: { fprintf(stdout, ", Level %u", track_info->level / 10); break; } case 11: case 12: case 13: case 21: case 22: case 31: case 32: case 41: case 42: case 51: { fprintf(stdout, ", Level %u.%u", track_info->level / 10, track_info->level % 10); break; } default: { fprintf(stdout, ", Unknown level %u.%u", track_info->level / 10, track_info->level % 10); } } // end switch } // end level if } else if (track_type == S_AMR_TRACK) { char amr_modes[500] = {}; if (track_info->track_codec == 0x73616D72 || track_info->track_codec == 0x73617762) { if (track_info->amr_modes & 0x0001) mem_append("0", amr_modes); if (track_info->amr_modes & 0x0002) mem_append("1", amr_modes); if (track_info->amr_modes & 0x0004) mem_append("2", amr_modes); if (track_info->amr_modes & 0x0008) mem_append("3", amr_modes); if (track_info->amr_modes & 0x0010) mem_append("4", amr_modes); if (track_info->amr_modes & 0x0020) mem_append("5", amr_modes); if (track_info->amr_modes & 0x0040) mem_append("6", amr_modes); if (track_info->amr_modes & 0x0080) mem_append("7", amr_modes); if (track_info->amr_modes & 0x0100) mem_append("8", amr_modes); if (strlen(amr_modes) == 0) memcpy(amr_modes, "none", 4); } else if (track_info->track_codec == 0x73766D72) { if (track_info->amr_modes & 0x0001) mem_append("VMR-WB Mode 0, ", amr_modes); if (track_info->amr_modes & 0x0002) mem_append("VMR-WB Mode 1, ", amr_modes); if (track_info->amr_modes & 0x0004) mem_append("VMR-WB Mode 2, ", amr_modes); if (track_info->amr_modes & 0x0008) mem_append("VMR-WB Mode 3 (AMR-WB interoperable mode), ", amr_modes); if (track_info->amr_modes & 0x0010) mem_append("VMR-WB Mode 4, ", amr_modes); if (track_info->amr_modes & 0x0020) mem_append("VMR-WB Mode 2 with maximum half-rate, ", amr_modes); if (track_info->amr_modes & 0x0040) mem_append("VMR-WB Mode 4 with maximum half-rate, ", amr_modes); uint16_t amr_modes_len = strlen(amr_modes); if (amr_modes_len > 0) memset(amr_modes + (amr_modes_len - 1), 0, 2); } if (track_info->track_codec == 0x73616D72) { // samr fprintf(stdout, " AMR Narrow-Band. Modes: %s. Encoder vendor code: %s\n", amr_modes, track_info->encoder_name); } else if (track_info->track_codec == 0x73617762) { // sawb fprintf(stdout, " AMR Wide-Band. Modes: %s. Encoder vendor code: %s\n", amr_modes, track_info->encoder_name); } else if (track_info->track_codec == 0x73617770) { // sawp fprintf(stdout, " AMR Wide-Band WB+. Encoder vendor code: %s\n", track_info->encoder_name); } else if (track_info->track_codec == 0x73766D72) { // svmr fprintf(stdout, " AMR VBR Wide-Band. Encoder vendor code: %s\n", track_info->encoder_name); } } else if (track_type == EVRC_TRACK) { fprintf(stdout, " EVRC (Enhanced Variable Rate Coder). Encoder vendor code: %s\n", track_info->encoder_name); } else if (track_type == QCELP_TRACK) { fprintf(stdout, " QCELP (Qualcomm Code Excited Linear Prediction). Encoder vendor " "code: %s\n", track_info->encoder_name); } else if (track_type == S263_TRACK) { if (track_info->profile == 0) { fprintf(stdout, " H.263 Baseline Profile, Level %u. Encoder vendor code: %s", track_info->level, track_info->encoder_name); } else { fprintf(stdout, " H.263 Profile: %u, Level %u. Encoder vendor code: %s", track_info->profile, track_info->level, track_info->encoder_name); } } if (track_type == AUDIO_TRACK) { if (track_info->section5_length == 0) { fprintf(stdout, " channels: (%u)\n", track_info->channels); } else { fprintf(stdout, " channels: [%u]\n", track_info->channels); } } } /////////////////////////////////////////////////////////////////////////////////////// // Movie & Track Level Info // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- calcuate_sample_size uint32_buffer - a buffer to read bytes in from the file isofile - the file to be scanned stsz_atom - the atom number of the stsz atom This will get aggregate a number of the size of all chunks in the track. The stsz atom holds a table of these sizes along with a count of how many there are. Loop through the count, summing in the sizes. This is called called for all tracks, but used only when a hardcoded bitrate (in esds) isn't found (like avc1) and is displayed with the asterisk* at track-level. ----------------------*/ uint64_t calcuate_sample_size(char *uint32_buffer, FILE *isofile, short stsz_atom) { uint32_t sample_size = 0; uint32_t sample_count = 0; uint64_t total_size = 0; sample_size = APar_read32( uint32_buffer, isofile, parsedAtoms[stsz_atom].AtomicStart + 12); sample_count = APar_read32( uint32_buffer, isofile, parsedAtoms[stsz_atom].AtomicStart + 16); if (sample_size == 0) { for (uint64_t atom_offset = 20; atom_offset < parsedAtoms[stsz_atom].AtomicLength; atom_offset += 4) { total_size += APar_read32(uint32_buffer, isofile, parsedAtoms[stsz_atom].AtomicStart + atom_offset); } } else { total_size = sample_size * sample_count; } return total_size; } /*---------------------- APar_TrackLevelInfo track - pointer to a struct providing the track we are looking for track_search_atom_name - the name of the atom to be found in this track Looping through the atoms one by one, note a 'trak' atom. If we are looking for the total amount of tracks (by setting the track_num to 0), simply return the count of tracks back in the same struct to that later functions can loop through each track individually, looking for a specific atom. If track's track_num is a non-zero number, then find that atom that *matches* the atom name. Set track's track_atom to that atom for later use ----------------------*/ void APar_TrackLevelInfo(Trackage *track, const char *track_search_atom_name) { uint8_t track_tally = 0; short iter = 0; while (parsedAtoms[iter].NextAtomNumber != 0) { if (strncmp(parsedAtoms[iter].AtomicName, "trak", 4) == 0) { track_tally += 1; if (track->track_num == 0) { track->total_tracks += 1; } else if (track->track_num == track_tally) { short next_atom = parsedAtoms[iter].NextAtomNumber; while (parsedAtoms[next_atom].AtomicLevel > parsedAtoms[iter].AtomicLevel) { if (strncmp(parsedAtoms[next_atom].AtomicName, track_search_atom_name, 4) == 0) { track->track_atom = parsedAtoms[next_atom].AtomicNumber; return; } else { next_atom = parsedAtoms[next_atom].NextAtomNumber; } if (parsedAtoms[next_atom].AtomicLevel == parsedAtoms[iter].AtomicLevel) { track->track_atom = 0; } } } } iter = parsedAtoms[iter].NextAtomNumber; } return; } /*---------------------- APar_ExtractChannelInfo isofile - the file to be scanned pos - the position within the file that carries the channel info (in esds) The channel info in esds is bitpacked, so read it in isolation and shift the bits around to get at it ----------------------*/ uint8_t APar_ExtractChannelInfo(FILE *isofile, uint32_t pos) { uint8_t packed_channels = APar_read8(isofile, pos); uint8_t unpacked_channels = (packed_channels << 1); // just shift the first bit off the table unpacked_channels = (unpacked_channels >> 4); // and slide it on over back on the uint8_t return unpacked_channels; } /*---------------------- APar_Extract_iods_Info isofile - the file to be scanned iods_atom - a pointer to the struct that will store the profile levels found in iods 'iods' info mostly comes from: http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt Just as 'esds' has 'filler' bytes to skip over, so does this. Mercifully, the profiles come one right after another. The only problem is that in many files, the iods profiles don't match the esds profiles. This is resolved by ignoring it for audio (mostly, unless is 0xFE user defined). For MPEG-4 Visual, it is preferred over 'esds' (occurs in APar_ShowMPEG4VisualProfileInfo); for all other video types it is ignored. ----------------------*/ void APar_Extract_iods_Info(FILE *isofile, AtomicInfo *iods_atom) { uint64_t iods_offset = iods_atom->AtomicStart + 8; if (iods_atom->AtomicVerFlags == 0 && APar_read8(isofile, iods_offset + 4) == 0x10) { iods_offset += 5; iods_offset += APar_skip_filler(isofile, iods_offset); uint8_t iods_objdescrip_len = APar_read8(isofile, iods_offset); iods_offset++; if (iods_objdescrip_len >= 7) { iods_info.od_profile_level = APar_read8(isofile, iods_offset + 2); iods_info.scene_profile_level = APar_read8(isofile, iods_offset + 3); iods_info.audio_profile = APar_read8(isofile, iods_offset + 4); iods_info.video_profile_level = APar_read8(isofile, iods_offset + 5); iods_info.graphics_profile_level = APar_read8(isofile, iods_offset + 6); } } return; } /*---------------------- APar_Extract_AMR_Info uint32_buffer - a buffer to read bytes in from the file isofile - the file to be scanned track_level_atom - the number of the 'esds' atom in the linked list of parsed atoms track_info - a pointer to the struct carrying track-level info to be filled with information The only interesting info here is the encoding tool & the amr modes used. ffmpeg's amr output seems to lack some compliance - no damr atom for sawb ----------------------*/ void APar_Extract_AMR_Info(char *uint32_buffer, FILE *isofile, short track_level_atom, TrackInfo *track_info) { uint32_t amr_specific_offet = 8; APar_readX(track_info->encoder_name, isofile, parsedAtoms[track_level_atom].AtomicStart + amr_specific_offet, 4); if (track_info->track_codec == 0x73616D72 || track_info->track_codec == 0x73617762 || track_info->track_codec == 0x73766D72) { // samr,sawb & svmr contain modes only track_info->amr_modes = APar_read16( uint32_buffer, isofile, parsedAtoms[track_level_atom].AtomicStart + amr_specific_offet + 4 + 1); } return; } /*---------------------- APar_Extract_d263_Info uint32_buffer - a buffer to read bytes in from the file isofile - the file to be scanned track_level_atom - the number of the 'esds' atom in the linked list of parsed atoms track_info - a pointer to the struct carrying track-level info to be filled with information 'd263' only holds 4 things; the 3 of interest are gathered here. Its possible that a 'bitr' atom follows 'd263', which would hold bitrates, but isn't parsed here ----------------------*/ void APar_Extract_d263_Info(char *uint32_buffer, FILE *isofile, short track_level_atom, TrackInfo *track_info) { uint64_t offset_into_d263 = 8; APar_readX(track_info->encoder_name, isofile, parsedAtoms[track_level_atom].AtomicStart + offset_into_d263, 4); track_info->level = APar_read8(isofile, parsedAtoms[track_level_atom].AtomicStart + offset_into_d263 + 4 + 1); track_info->profile = APar_read8(isofile, parsedAtoms[track_level_atom].AtomicStart + offset_into_d263 + 4 + 2); // possible 'bitr' bitrate box afterwards return; } /*---------------------- APar_Extract_devc_Info isofile - the file to be scanned track_level_atom - the number of the 'esds' atom in the linked list of parsed atoms track_info - a pointer to the struct carrying track-level info to be filled with information 'devc' only holds 3 things: encoder vendor, decoder version & frames per sample; only encoder vendor is gathered ----------------------*/ void APar_Extract_devc_Info(FILE *isofile, short track_level_atom, TrackInfo *track_info) { uint64_t offset_into_devc = 8; APar_readX(track_info->encoder_name, isofile, parsedAtoms[track_level_atom].AtomicStart + offset_into_devc, 4); return; } /*---------------------- APar_Extract_esds_Info uint32_buffer - a buffer to read bytes in from the file isofile - the file to be scanned track_level_atom - the number of the 'esds' atom in the linked list of parsed atoms track_info - a pointer to the struct carrying track-level info to be filled with information 'esds' contains a wealth of information. Memory fails where I figured out how to parse this atom, but this seems like a decent outline in retrospect: http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt - but its misleading in lots of places too. For those tracks that support 'esds' (notably not avc1 or alac), this atom comes in at most 4 sections (section 3 to section 6). Each section is numbered (3 is 0x03) followed by an optional amount of filler (see APar_skip_filler), then the length of the section to the end of the atom or the end of another section. ----------------------*/ void APar_Extract_esds_Info(char *uint32_buffer, FILE *isofile, short track_level_atom, TrackInfo *track_info) { uint64_t offset_into_stsd = 0; while (offset_into_stsd < parsedAtoms[track_level_atom].AtomicLength) { offset_into_stsd++; if (APar_read32(uint32_buffer, isofile, parsedAtoms[track_level_atom].AtomicStart + offset_into_stsd) == 0x65736473) { track_info->contains_esds = true; uint64_t esds_start = parsedAtoms[track_level_atom].AtomicStart + offset_into_stsd - 4; uint64_t esds_length = APar_read32(uint32_buffer, isofile, esds_start); uint64_t offset_into_esds = 12; // 4bytes length + 4 bytes name + 4bytes null if (APar_read8(isofile, esds_start + offset_into_esds) == 0x03) { offset_into_esds++; offset_into_esds += APar_skip_filler(isofile, esds_start + offset_into_esds); } uint8_t section3_length = APar_read8(isofile, esds_start + offset_into_esds); if (section3_length <= esds_length && section3_length != 0) { track_info->section3_length = section3_length; } else { break; } // for whatever reason, when mp4box muxes in ogg into an mp4 container, // section 3 gets a 0x9D byte (which doesn't fall inline with what AP // considers 'filler') then again, I haven't *completely* read the ISO // specifications, so I could just be missing it the the ->voluminous<- // 14496-X specifications. uint8_t test_byte = APar_read8(isofile, esds_start + offset_into_esds + 1); if (test_byte != 0) { offset_into_esds++; } offset_into_esds += 4; // 1 bytes section 0x03 length + 2 bytes + 1 byte if (APar_read8(isofile, esds_start + offset_into_esds) == 0x04) { offset_into_esds++; offset_into_esds += APar_skip_filler(isofile, esds_start + offset_into_esds); } uint8_t section4_length = APar_read8(isofile, esds_start + offset_into_esds); if (section4_length <= section3_length && section4_length != 0) { track_info->section4_length = section4_length; if (section4_length == 0x9D) offset_into_esds++; // upper limit? when gpac puts an ogg in, section // 3 is 9D - so is sec4 (section 4 real length // with ogg = 0x0E86) offset_into_esds++; track_info->ObjectTypeIndication = APar_read8(isofile, esds_start + offset_into_esds); // this is just so that ogg in mp4 won't have some bizarre high bitrate // of like 2.8megabits/sec uint8_t a_v_flag = APar_read8(isofile, esds_start + offset_into_esds + 1); // mp4box with ogg will set this to DD, // mp4a has it as 0x40, mp4v has 0x20 if (track_info->ObjectTypeIndication < 0xC0 && a_v_flag < 0xA0) { // 0xC0 marks user streams; but things below that // might still be wrong (like 0x6D - png) offset_into_esds += 5; track_info->max_bitrate = APar_read32( uint32_buffer, isofile, esds_start + offset_into_esds); offset_into_esds += 4; track_info->avg_bitrate = APar_read32( uint32_buffer, isofile, esds_start + offset_into_esds); offset_into_esds += 4; } } else { break; } if (APar_read8(isofile, esds_start + offset_into_esds) == 0x05) { offset_into_esds++; offset_into_esds += APar_skip_filler(isofile, esds_start + offset_into_esds); uint8_t section5_length = APar_read8(isofile, esds_start + offset_into_esds); if ((section5_length <= section4_length || section4_length == 1) && section5_length != 0) { track_info->section5_length = section5_length; offset_into_esds += 1; if (track_info->type_of_track & AUDIO_TRACK) { uint8_t packed_objID = APar_read8( isofile, esds_start + offset_into_esds); // its packed with channel, but // channel is fetched separately track_info->descriptor_object_typeID = packed_objID >> 3; offset_into_esds += 1; track_info->channels = (uint16_t)APar_ExtractChannelInfo( isofile, esds_start + offset_into_esds); } else if (track_info->type_of_track & VIDEO_TRACK) { // technically, visual_object_sequence_start_code should be tested // aginst 0x000001B0 if (APar_read16(uint32_buffer, isofile, esds_start + offset_into_esds + 2) == 0x01B0) { track_info->m4v_profile = APar_read8(isofile, esds_start + offset_into_esds + 2 + 2); } } } break; // uh, I've extracted the pertinent info } } if (offset_into_stsd > parsedAtoms[track_level_atom].AtomicLength) { break; } } if ((track_info->section5_length == 0 && track_info->type_of_track & AUDIO_TRACK) || track_info->channels == 0) { track_info->channels = APar_read16( uint32_buffer, isofile, parsedAtoms[track_level_atom].AtomicStart + 40); } return; } /*---------------------- APar_ExtractTrackDetails uint32_buffer - a buffer to read bytes in from the file isofile - the file to be scanned track - the struct proving tracking of this 'trak' atom so we can jump around in this track track_info - a pointer to the struct carrying track-level info to be filled with information This function jumps all around in a single 'trak' atom gathering information from different child constituent atoms except 'esds' which is handled on its own. ----------------------*/ void APar_ExtractTrackDetails(char *uint32_buffer, FILE *isofile, Trackage *track, TrackInfo *track_info) { uint64_t _offset = 0; APar_TrackLevelInfo(track, "tkhd"); if (APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 8) == 0) { if (APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 11) & 1) { track_info->track_enabled = true; } track_info->creation_time = APar_read32(uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 12); track_info->modified_time = APar_read32(uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 16); track_info->duration = APar_read32(uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 28); } else { track_info->creation_time = APar_read64(uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 12); track_info->modified_time = APar_read64(uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 20); track_info->duration = APar_read64(uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 36); } // language code APar_TrackLevelInfo(track, "mdhd"); memset(uint32_buffer, 0, 5); uint16_t packed_language = APar_read16( uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 28); memset(track_info->unpacked_lang, 0, 4); APar_UnpackLanguage( track_info->unpacked_lang, packed_language); // http://www.w3.org/WAI/ER/IG/ert/iso639.htm // track handler type APar_TrackLevelInfo(track, "hdlr"); memset(uint32_buffer, 0, 5); track_info->track_type = APar_read32( uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 16); if (track_info->track_type == 0x736F756E) { // soun track_info->type_of_track = AUDIO_TRACK; } else if (track_info->track_type == 0x76696465) { // vide track_info->type_of_track = VIDEO_TRACK; } if (parsedAtoms[track->track_atom].AtomicLength > 34) { memset(track_info->track_hdlr_name, 0, sizeof(track_info->track_hdlr_name)); APar_readX(track_info->track_hdlr_name, isofile, parsedAtoms[track->track_atom].AtomicStart + 32, std::min((uint64_t)sizeof(track_info->track_hdlr_name), parsedAtoms[track->track_atom].AtomicLength - 32)); } // codec section APar_TrackLevelInfo(track, "stsd"); memset(uint32_buffer, 0, 5); track_info->track_codec = APar_read32( uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 20); if (track_info->type_of_track & VIDEO_TRACK) { // vide track_info->video_width = APar_read16(uint32_buffer, isofile, parsedAtoms[track->track_atom + 1].AtomicStart + 32); track_info->video_height = APar_read16(uint32_buffer, isofile, parsedAtoms[track->track_atom + 1].AtomicStart + 34); track_info->macroblocks = (track_info->video_width / 16) * (track_info->video_height / 16); // avc profile & level if (track_info->track_codec == 0x61766331 || track_info->track_codec == 0x64726D69) { // avc1 or drmi track_info->contains_esds = false; APar_TrackLevelInfo(track, "avcC"); // get avc1 profile/level; atom 'avcC' is : // byte 1 configurationVersion byte 2 AVCProfileIndication byte 3 // profile_compatibility byte 4 AVCLevelIndication track_info->avc_version = APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 8); if (track_info->avc_version == 1) { track_info->profile = APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 9); // uint8_t profile_compatibility = APar_read8(isofile, // parsedAtoms[track.track_atom].AtomicStart + 10); /* is this reserved // ?? */ track_info->level = APar_read8( isofile, parsedAtoms[track->track_atom].AtomicStart + 11); } // avc1 doesn't have a hardcoded bitrate, so calculate it (off of stsz // table summing) later } else if (track_info->track_codec == 0x73323633) { // s263 APar_TrackLevelInfo(track, "d263"); if (memcmp(parsedAtoms[track->track_atom].AtomicName, "d263", 4) == 0) { APar_Extract_d263_Info( uint32_buffer, isofile, track->track_atom, track_info); } } else { // mp4v APar_TrackLevelInfo(track, "esds"); if (memcmp(parsedAtoms[track->track_atom].AtomicName, "esds", 4) == 0) { APar_Extract_esds_Info( uint32_buffer, isofile, track->track_atom - 1, track_info); // right, backtrack to the atom before 'esds' so we can // offset_into_stsd++ } else if (track_info->track_codec == 0x73323633) { // s263 track_info->type_of_track = VIDEO_TRACK; } else if (track_info->track_codec == 0x73616D72 || track_info->track_codec == 0x73617762 || track_info->track_codec == 0x73617770 || track_info->track_codec == 0x73766D72) { // samr, sawb, sawp & svmr track_info->type_of_track = AUDIO_TRACK; } else { track_info->type_of_track = OTHER_TRACK; // a 'jpeg' track will fall // here } } } else if (track_info->type_of_track & AUDIO_TRACK) { if (track_info->track_codec == 0x73616D72 || track_info->track_codec == 0x73617762 || track_info->track_codec == 0x73617770 || track_info->track_codec == 0x73766D72) { // samr,sawb, svmr (sawp doesn't contain modes) APar_Extract_AMR_Info( uint32_buffer, isofile, track->track_atom + 2, track_info); } else if (track_info->track_codec == 0x73657663) { // sevc APar_TrackLevelInfo(track, "devc"); if (memcmp(parsedAtoms[track->track_atom].AtomicName, "devc", 4) == 0) { APar_Extract_devc_Info(isofile, track->track_atom, track_info); } } else if (track_info->track_codec == 0x73716370) { // sqcp APar_TrackLevelInfo(track, "dqcp"); if (memcmp(parsedAtoms[track->track_atom].AtomicName, "dqcp", 4) == 0) { APar_Extract_devc_Info(isofile, track->track_atom, track_info); // its the same thing } } else if (track_info->track_codec == 0x73736D76) { // ssmv APar_TrackLevelInfo(track, "dsmv"); if (memcmp(parsedAtoms[track->track_atom].AtomicName, "dsmv", 4) == 0) { APar_Extract_devc_Info(isofile, track->track_atom, track_info); // its the same thing } } else { APar_Extract_esds_Info( uint32_buffer, isofile, track->track_atom, track_info); } } // in case bitrate isn't found, manually determine it off of stsz summing if ((track_info->type_of_track & AUDIO_TRACK || track_info->type_of_track & VIDEO_TRACK) && track_info->avg_bitrate == 0) { if (track_info->track_codec == 0x616C6163) { // alac track_info->channels = APar_read16(uint32_buffer, isofile, parsedAtoms[track->track_atom + 1].AtomicStart + 24); } } APar_TrackLevelInfo(track, "stsz"); if (memcmp(parsedAtoms[track->track_atom].AtomicName, "stsz", 4) == 0) { track_info->sample_aggregate = calcuate_sample_size(uint32_buffer, isofile, track->track_atom); } // get what exactly 'drmX' stands in for if (track_info->track_codec >= 0x64726D00 && track_info->track_codec <= 0x64726DFF) { track_info->type_of_track += DRM_PROTECTED_TRACK; APar_TrackLevelInfo(track, "frma"); memset(uint32_buffer, 0, 5); track_info->protected_codec = APar_read32( uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 8); } // Encoder string; occasionally, it appears under stsd for a video track; it // is typcally preceded by ' ' (1st char is unprintable) or 0x01B2 if (track_info->contains_esds) { APar_TrackLevelInfo(track, "esds"); // technically, user_data_start_code should be tested aginst 0x000001B2; // TODO: it should only be read up to section 3's length too _offset = APar_FindValueInAtom( uint32_buffer, isofile, track->track_atom, 24, 0x01B2); if (_offset > 0 && _offset < parsedAtoms[track->track_atom].AtomicLength) { _offset += 2; memset(track_info->encoder_name, 0, parsedAtoms[track->track_atom].AtomicLength - _offset); APar_readX(track_info->encoder_name, isofile, parsedAtoms[track->track_atom].AtomicStart + _offset, parsedAtoms[track->track_atom].AtomicLength - _offset); } } return; } /*---------------------- APar_ExtractMovieDetails uint32_buffer - a buffer to read bytes in from the file isofile - the file to be scanned mvhd_atom - pointer to the 'mvhd' atom and where in the file it can be found Get information out of 'mvhd' - most important of which are timescale & duration which get used to calcuate bitrate if needed and determine duration of a track in seconds. A rough approximation of the overall bitrate is done off this too using the sum of the mdat lengths. ----------------------*/ void APar_ExtractMovieDetails(char *uint32_buffer, FILE *isofile, AtomicInfo *mvhd_atom) { if (mvhd_atom->AtomicVerFlags & 0x01000000) { movie_info.creation_time = APar_read64(uint32_buffer, isofile, mvhd_atom->AtomicStart + 12); movie_info.modified_time = APar_read64(uint32_buffer, isofile, mvhd_atom->AtomicStart + 20); movie_info.timescale = APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 28); movie_info.duration = APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 32); movie_info.timescale = APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 36); movie_info.duration = APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 40); movie_info.playback_rate = APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 44); movie_info.volume = APar_read16(uint32_buffer, isofile, mvhd_atom->AtomicStart + 48); } else { movie_info.creation_time = (uint64_t)APar_read32( uint32_buffer, isofile, mvhd_atom->AtomicStart + 12); movie_info.modified_time = (uint64_t)APar_read32( uint32_buffer, isofile, mvhd_atom->AtomicStart + 16); movie_info.timescale = APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 20); movie_info.duration = APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 24); movie_info.playback_rate = APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 28); movie_info.volume = APar_read16(uint32_buffer, isofile, mvhd_atom->AtomicStart + 32); } movie_info.seconds = (float)movie_info.duration / (float)movie_info.timescale; #if defined(_MSC_VER) __int64 media_bits = (__int64)mdatData * 8; #else uint64_t media_bits = (uint64_t)mdatData * 8; #endif movie_info.simple_bitrate_calc = ((double)media_bits / movie_info.seconds) / 1000.0; return; } /////////////////////////////////////////////////////////////////////////////////////// // Get at some track-level info // /////////////////////////////////////////////////////////////////////////////////////// void APar_Print_TrackDetails(TrackInfo *track_info) { if (track_info->max_bitrate > 0 && track_info->avg_bitrate > 0) { fprintf(stdout, " %.2f kbp/s", (float)track_info->avg_bitrate / 1000.0); } else { // some ffmpeg encodings have avg_bitrate set to 0, but an inexact // max_bitrate - actually, their esds seems a mess to me #if defined(_MSC_VER) fprintf(stdout, " %.2lf* kbp/s", ((double)((__int64)track_info->sample_aggregate) / ((double)((__int64)track_info->duration) / (double)((__int64)movie_info.timescale))) / 1000.0 * 8); fprintf(stdout, " %.3f sec", (float)track_info->duration / (float)movie_info.timescale); #else fprintf(stdout, " %.2lf* kbp/s", ((double)track_info->sample_aggregate / ((double)track_info->duration / (double)movie_info.timescale)) / 1000.0 * 8); fprintf(stdout, " %.3f sec", (float)track_info->duration / (float)movie_info.timescale); #endif } if (track_info->track_codec == 0x6D703476) { // mp4v profile APar_ShowObjectProfileInfo(MP4V_TRACK, track_info); } else if (track_info->track_codec == 0x6D703461 || track_info->protected_codec == 0x6D703461) { // mp4a profile APar_ShowObjectProfileInfo(AUDIO_TRACK, track_info); } else if (track_info->track_codec == 0x616C6163) { // alac - can't figure out a hardcoded bitrate either fprintf( stdout, " Apple Lossless channels: [%u]\n", track_info->channels); } else if (track_info->track_codec == 0x61766331 || track_info->protected_codec == 0x61766331) { if (track_info->avc_version == 1) { // avc profile & level APar_ShowObjectProfileInfo(AVC1_TRACK, track_info); } } else if (track_info->track_codec == 0x73323633) { // s263 in 3gp APar_ShowObjectProfileInfo(S263_TRACK, track_info); } else if (track_info->track_codec == 0x73616D72 || track_info->track_codec == 0x73617762 || track_info->track_codec == 0x73617770 || track_info->track_codec == 0x73766D72) { // samr,sawb,sawp & svmr in 3gp track_info->type_of_track = S_AMR_TRACK; APar_ShowObjectProfileInfo(track_info->type_of_track, track_info); } else if (track_info->track_codec == 0x73657663) { // evrc in 3gp track_info->type_of_track = EVRC_TRACK; APar_ShowObjectProfileInfo(track_info->type_of_track, track_info); } else if (track_info->track_codec == 0x73716370) { // qcelp in 3gp track_info->type_of_track = QCELP_TRACK; APar_ShowObjectProfileInfo(track_info->type_of_track, track_info); } else if (track_info->track_codec == 0x73736D76) { // smv in 3gp track_info->type_of_track = SMV_TRACK; APar_ShowObjectProfileInfo(track_info->type_of_track, track_info); } else { // unknown everything, 0 hardcoded bitrate APar_ShowObjectProfileInfo(track_info->type_of_track, track_info); fprintf(stdout, "\n"); } if (track_info->type_of_track & VIDEO_TRACK && ((track_info->max_bitrate > 0 && track_info->ObjectTypeIndication == 0x20) || track_info->avc_version == 1 || track_info->protected_codec != 0)) { fprintf(stdout, " %ux%u (%" PRIu32 " macroblocks)\n", track_info->video_width, track_info->video_height, track_info->macroblocks); } else if (track_info->type_of_track & VIDEO_TRACK) { fprintf(stdout, "\n"); } return; } void APar_ExtractDetails(FILE *isofile, uint8_t optional_output) { char uint32_buffer[8]; Trackage track = {0}; AtomicInfo *mvhdAtom = APar_FindAtom("moov.mvhd", false, VERSIONED_ATOM, 0); if (mvhdAtom != NULL) { APar_ExtractMovieDetails(uint32_buffer, isofile, mvhdAtom); fprintf(stdout, "Movie duration: %.3lf seconds (%s) - %.2lf* kbp/sec bitrate " "(*=approximate)\n", movie_info.seconds, secsTOtime(movie_info.seconds), movie_info.simple_bitrate_calc); if (optional_output & SHOW_DATE_INFO) { fprintf(stdout, " Presentation Creation Date (UTC): %s\n", APar_extract_UTC(movie_info.creation_time)); fprintf(stdout, " Presentation Modification Date (UTC): %s\n", APar_extract_UTC(movie_info.modified_time)); } } AtomicInfo *iodsAtom = APar_FindAtom("moov.iods", false, VERSIONED_ATOM, 0); if (iodsAtom != NULL) { movie_info.contains_iods = true; APar_Extract_iods_Info(isofile, iodsAtom); } if (optional_output & SHOW_TRACK_INFO) { APar_TrackLevelInfo(&track, NULL); // With track_num set to 0, it will return the // total trak atom into total_tracks here. fprintf( stdout, "Low-level details. Total tracks: %u\n", track.total_tracks); fprintf(stdout, "Trk Type Handler Kind Lang Bytes\n"); if (track.total_tracks > 0) { while (track.total_tracks > track.track_num) { track.track_num += 1; TrackInfo track_info = {0}; // tracknum, handler type, handler name APar_ExtractTrackDetails(uint32_buffer, isofile, &track, &track_info); uint16_t more_whitespace = purge_extraneous_characters(track_info.track_hdlr_name); if (strlen(track_info.track_hdlr_name) == 0) { memcpy(track_info.track_hdlr_name, "[none listed]", 13); } fprintf(stdout, "%u %s %s", track.track_num, uint32tochar4(track_info.track_type, uint32_buffer), track_info.track_hdlr_name); uint16_t handler_len = strlen(track_info.track_hdlr_name); if (handler_len < 25 + more_whitespace) { for (uint16_t i = handler_len; i < 25 + more_whitespace; i++) { fprintf(stdout, " "); } } // codec, language fprintf(stdout, " %s %s %" PRIu64, uint32tochar4(track_info.track_codec, uint32_buffer), track_info.unpacked_lang, track_info.sample_aggregate); if (track_info.encoder_name[0] != 0 && track_info.contains_esds) { purge_extraneous_characters(track_info.encoder_name); fprintf(stdout, " Encoder: %s", track_info.encoder_name); } if (track_info.type_of_track & DRM_PROTECTED_TRACK) { fprintf(stdout, " (protected %s)", uint32tochar4(track_info.protected_codec, uint32_buffer)); } fprintf(stdout, "\n"); /*---------------------------------*/ if (track_info.type_of_track & VIDEO_TRACK || track_info.type_of_track & AUDIO_TRACK) { APar_Print_TrackDetails(&track_info); } if (optional_output & SHOW_DATE_INFO) { fprintf(stdout, " Creation Date (UTC): %s\n", APar_extract_UTC(track_info.creation_time)); fprintf(stdout, " Modification Date (UTC): %s\n", APar_extract_UTC(track_info.modified_time)); } } } } } // provided as a convenience function so that 3rd party utilities can know // beforehand void APar_ExtractBrands(char *filepath) { FILE *a_file = APar_OpenISOBaseMediaFile(filepath, true); char buffer[16] = {}; uint32_t atom_length = 0; uint8_t file_type_offset = 0; uint32_t compatible_brand = 0; bool cb_V2ISOBMFF = false; APar_read32(buffer, a_file, 4); if (memcmp(buffer, "ftyp", 4) == 0) { atom_length = APar_read32(buffer, a_file, 0); } else { APar_readX(buffer, a_file, 0, 12); if (memcmp(buffer, "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A", 12) == 0) { APar_readX(buffer, a_file, 12, 12); if (memcmp(buffer + 4, "ftypmjp2", 8) == 0 || memcmp(buffer + 4, "ftypmj2s", 8) == 0) { atom_length = UInt32FromBigEndian(buffer); file_type_offset = 12; } } } if (atom_length > 0) { memset(buffer, 0, 16); APar_readX(buffer, a_file, 8 + file_type_offset, 4); printBOM(); fprintf(stdout, " Major Brand: %s", buffer); APar_IdentifyBrand(buffer); if (memcmp(buffer, "isom", 4) == 0) { APar_ScanAtoms(filepath); // scan_file = true; } uint32_t minor_version = APar_read32(buffer, a_file, 12 + file_type_offset); fprintf(stdout, " - version %" PRIu32 "\n", minor_version); fprintf(stdout, " Compatible Brands:"); for (uint64_t i = 16 + file_type_offset; i < atom_length; i += 4) { APar_readX(buffer, a_file, i, 4); compatible_brand = UInt32FromBigEndian(buffer); if (compatible_brand != 0) { fprintf(stdout, " %s", buffer); if (compatible_brand == 0x6D703432 || compatible_brand == 0x69736F32) { cb_V2ISOBMFF = true; } } } fprintf(stdout, "\n"); } APar_OpenISOBaseMediaFile(filepath, false); fprintf(stdout, " Tagging schemes available:\n"); switch (metadata_style) { case ITUNES_STYLE: { fprintf(stdout, " iTunes-style metadata allowed.\n"); break; } case THIRD_GEN_PARTNER: case THIRD_GEN_PARTNER_VER1_REL6: case THIRD_GEN_PARTNER_VER1_REL7: case THIRD_GEN_PARTNER_VER2: { fprintf(stdout, " 3GP-style asset metadata allowed.\n"); break; } case THIRD_GEN_PARTNER_VER2_REL_A: { fprintf(stdout, " 3GP-style asset metadata allowed [& unimplemented GAD " "(Geographical Area Description) asset].\n"); break; } } if (cb_V2ISOBMFF || metadata_style == THIRD_GEN_PARTNER_VER1_REL7) { fprintf(stdout, " ID3 tags on ID32 atoms @ file/movie/track level allowed.\n"); } fprintf(stdout, " ISO-copyright notices @ movie and/or track level " "allowed.\n uuid private user extension tags allowed.\n"); } atomicparsley-20240608.083822.1ed9031/src/extras/000077500000000000000000000000001463107535600204765ustar00rootroot00000000000000atomicparsley-20240608.083822.1ed9031/src/extras/getopt.c000066400000000000000000000704571463107535600221610ustar00rootroot00000000000000/* Getopt for GNU. NOTE: getopt is now part of the C library, so if you don't know what "Keep this file name-space clean" means, talk to drepper@gnu.org before changing it! Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This tells Alpha OSF/1 not to define a getopt prototype in . Ditto for AIX 3.2 and . */ #ifndef _NO_PROTO # define _NO_PROTO #endif #ifdef HAVE_CONFIG_H # include #endif #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ # ifndef const # define const # endif #endif #include /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 # include # if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION # define ELIDE_CODE # endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ # include # include #endif /* GNU C library. */ #ifdef VMS # include # if HAVE_STRING_H - 0 # include # endif #endif #ifndef _ /* This is for other GNU distributions with internationalized messages. When compiling libc, the _ macro is predefined. */ # ifdef HAVE_LIBINTL_H # include # define _(msgid) gettext (msgid) # else # define _(msgid) (msgid) # endif #endif /* This version of `getopt' appears to the caller like standard Unix `getopt' but it behaves differently for the user, since it allows the user to intersperse the options with the other arguments. As `getopt' works, it permutes the elements of ARGV so that, when it is done, all the options precede everything else. Thus all application programs are extended to handle flexible argument order. Setting the environment variable POSIXLY_CORRECT disables permutation. Then the behavior is completely standard. GNU application programs can use a third alternative mode in which they can distinguish the relative order of options and other arguments. */ #include "getopt.h" /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ char *optarg = NULL; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* 1003.2 says this must be 1 before any call. */ int optind = 1; /* Formerly, initialization of getopt depended on optind==0, which causes problems with re-calling getopt as programs generally don't know that. */ int __getopt_initialized = 0; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ int optopt = '?'; /* Describe how to deal with options that follow non-option ARGV-elements. If the caller did not specify anything, the default is REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise. REQUIRE_ORDER means don't recognize them as options; stop option processing when the first non-option is seen. This is what Unix does. This mode of operation is selected by either setting the environment variable POSIXLY_CORRECT, or using `+' as the first character of the list of option characters. PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. RETURN_IN_ORDER is an option available to programs that were written to expect options and other ARGV-elements in any order and that care about the ordering of the two. We describe each non-option ARGV-element as if it were the argument of an option with character code 1. Using `-' as the first character of the list of option characters selects this mode of operation. The special argument `--' forces an end of option-scanning regardless of the value of `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return -1 with `optind' != ARGC. */ static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; /* Value of POSIXLY_CORRECT environment variable. */ static char *posixly_correct; #ifdef __GNU_LIBRARY__ /* We want to avoid inclusion of string.h with non-GNU libraries because there are many ways it can cause trouble. On some systems, it contains special magic macros that don't work in GCC. */ # include # define my_index strchr #else #define HAVE_STRING_H 1 # if HAVE_STRING_H # include # else # include # endif /* Avoid depending on library functions or files whose names are inconsistent. */ #ifndef getenv extern char *getenv (); #endif static char * my_index (str, chr) const char *str; int chr; { while (*str) { if (*str == chr) return (char *) str; str++; } return 0; } /* If using GCC, we can safely declare strlen this way. If not using GCC, it is ok not to declare it. */ #ifdef __GNUC__ /* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. That was relevant to code that was here before. */ # if (!defined __STDC__ || !__STDC__) && !defined strlen /* gcc with -traditional declares the built-in strlen to return int, and has done so at least since version 2.4.5. -- rms. */ extern int strlen (const char *); # endif /* not __STDC__ */ #endif /* __GNUC__ */ #endif /* not __GNU_LIBRARY__ */ /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; #ifdef _LIBC /* Bash 2.0 gives us an environment variable containing flags indicating ARGV elements that should not be considered arguments. */ /* Defined in getopt_init.c */ extern char *__getopt_nonoption_flags; static int nonoption_flags_max_len; static int nonoption_flags_len; static int original_argc; static char *const *original_argv; /* Make sure the environment variable bash 2.0 puts in the environment is valid for the getopt call we must make sure that the ARGV passed to getopt is that one passed to the process. */ static void __attribute__ ((unused)) store_args_and_env (int argc, char *const *argv) { /* XXX This is no good solution. We should rather copy the args so that we can compare them later. But we must not use malloc(3). */ original_argc = argc; original_argv = argv; } # ifdef text_set_element text_set_element (__libc_subinit, store_args_and_env); # endif /* text_set_element */ # define SWAP_FLAGS(ch1, ch2) \ if (nonoption_flags_len > 0) \ { \ char __tmp = __getopt_nonoption_flags[ch1]; \ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ __getopt_nonoption_flags[ch2] = __tmp; \ } #else /* !_LIBC */ # define SWAP_FLAGS(ch1, ch2) #endif /* _LIBC */ /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The other is elements [last_nonopt,optind), which contains all the options processed since those non-options were skipped. `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. */ #if defined __STDC__ && __STDC__ static void exchange (char **); #endif static void exchange (argv) char **argv; { int bottom = first_nonopt; int middle = last_nonopt; int top = optind; char *tem; /* Exchange the shorter segment with the far end of the longer segment. That puts the shorter segment into the right place. It leaves the longer segment in the right place overall, but it consists of two parts that need to be swapped next. */ #ifdef _LIBC /* First make sure the handling of the `__getopt_nonoption_flags' string can work normally. Our top argument must be in the range of the string. */ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { /* We must extend the array. The user plays games with us and presents new arguments. */ char *new_str = malloc (top + 1); if (new_str == NULL) nonoption_flags_len = nonoption_flags_max_len = 0; else { memset (__mempcpy (new_str, __getopt_nonoption_flags, nonoption_flags_max_len), '\0', top + 1 - nonoption_flags_max_len); nonoption_flags_max_len = top + 1; __getopt_nonoption_flags = new_str; } } #endif while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { /* Bottom segment is the short one. */ int len = middle - bottom; register int i; /* Swap it with the top part of the top segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); } /* Exclude the moved bottom segment from further swapping. */ top -= len; } else { /* Top segment is the short one. */ int len = top - middle; register int i; /* Swap it with the bottom part of the bottom segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; SWAP_FLAGS (bottom + i, middle + i); } /* Exclude the moved top segment from further swapping. */ bottom += len; } } /* Update records for the slots the non-options now occupy. */ first_nonopt += (optind - last_nonopt); last_nonopt = optind; } /* Initialize the internal data when the first call is made. */ #if defined __STDC__ && __STDC__ static const char *_getopt_initialize (int, char *const *, const char *); #endif static const char * _getopt_initialize (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { /* Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ first_nonopt = last_nonopt = optind; nextchar = NULL; posixly_correct = getenv ("POSIXLY_CORRECT"); /* Determine how to handle the ordering of options and nonoptions. */ if (optstring[0] == '-') { ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; } else if (posixly_correct != NULL) ordering = REQUIRE_ORDER; else ordering = PERMUTE; #ifdef _LIBC if (posixly_correct == NULL && argc == original_argc && argv == original_argv) { if (nonoption_flags_max_len == 0) { if (__getopt_nonoption_flags == NULL || __getopt_nonoption_flags[0] == '\0') nonoption_flags_max_len = -1; else { const char *orig_str = __getopt_nonoption_flags; int len = nonoption_flags_max_len = strlen (orig_str); if (nonoption_flags_max_len < argc) nonoption_flags_max_len = argc; __getopt_nonoption_flags = (char *) malloc (nonoption_flags_max_len); if (__getopt_nonoption_flags == NULL) nonoption_flags_max_len = -1; else memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), '\0', nonoption_flags_max_len - len); } } nonoption_flags_len = nonoption_flags_max_len; } else nonoption_flags_len = 0; #endif return optstring; } /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns -1. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal (argc, argv, optstring, longopts, longind, long_only) int argc; char *const *argv; const char *optstring; const struct option *longopts; int *longind; int long_only; { optarg = NULL; if (optind == 0 || !__getopt_initialized) { if (optind == 0) optind = 1; /* Don't scan ARGV[0], the program name. */ optstring = _getopt_initialize (argc, argv, optstring); __getopt_initialized = 1; } /* Test whether ARGV[optind] points to a non-option argument. Either it does not have option syntax, or there is an environment flag from the shell indicating it is not an option. The later information is only used when the used in the GNU libc. */ #ifdef _LIBC # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ || (optind < nonoption_flags_len \ && __getopt_nonoption_flags[optind] == '1')) #else # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif if (nextchar == NULL || *nextchar == '\0') { /* Advance to the next ARGV-element. */ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been moved back by the user (who may also have changed the arguments). */ if (last_nonopt > optind) last_nonopt = optind; if (first_nonopt > optind) first_nonopt = optind; if (ordering == PERMUTE) { /* If we have just processed some options following some non-options, exchange them so that the options come first. */ if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (last_nonopt != optind) first_nonopt = optind; /* Skip any additional non-options and extend the range of non-options previously skipped. */ while (optind < argc && NONOPTION_P) optind++; last_nonopt = optind; } /* The special ARGV-element `--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (optind != argc && !strcmp (argv[optind], "--")) { optind++; if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (first_nonopt == last_nonopt) first_nonopt = optind; last_nonopt = argc; optind = argc; } /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return -1; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if (NONOPTION_P) { if (ordering == REQUIRE_ORDER) return -1; optarg = argv[optind++]; return 1; } /* We have found another option-ARGV-element. Skip the initial punctuation. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } /* Decode the current option-ARGV-element. */ /* Check whether the ARGV-element is a long option. If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't consider it an abbreviated form of a long option that starts with f. Otherwise there would be no way to give the -f short option. On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg "u". This distinction seems to be the most useful approach. */ if (longopts != NULL && (argv[optind][1] == '-' || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = -1; int option_index; for (nameend = nextchar; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == (unsigned int) strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; optopt = 0; return '?'; } if (pfound != NULL) { option_index = indfound; optind++; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) if (argv[optind - 1][1] == '-') /* --option */ fprintf (stderr, _("%s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); else /* +option or -option */ fprintf (stderr, _("%s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); nextchar += strlen (nextchar); optopt = pfound->val; return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen (nextchar); optopt = pfound->val; return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' || my_index (optstring, *nextchar) == NULL) { if (opterr) { if (argv[optind][1] == '-') /* --option */ fprintf (stderr, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); else /* +option or -option */ fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); } nextchar = (char *) ""; optind++; optopt = 0; return '?'; } } /* Look at and handle the next short option-character. */ { char c = *nextchar++; char *temp = my_index (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') ++optind; if (temp == NULL || c == ':') { if (opterr) { if (posixly_correct) /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c); else fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c); } optopt = c; return '?'; } /* Convenience. Treat POSIX -W foo same as long option --foo */ if (temp[0] == 'W' && temp[1] == ';') { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; return c; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; /* optarg is now the argument, see if it's in the table of longopts. */ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; return '?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) fprintf (stderr, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name); nextchar += strlen (nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen (nextchar); return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } nextchar = NULL; return 'W'; /* Let the application handle it. */ } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = NULL; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } int getopt (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { return _getopt_internal (argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); } #endif /* Not ELIDE_CODE. */ atomicparsley-20240608.083822.1ed9031/src/extras/getopt.h000066400000000000000000000104551463107535600221560ustar00rootroot00000000000000/* Declarations for getopt. Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _GETOPT_H #define _GETOPT_H 1 #ifdef __cplusplus extern "C" { #endif /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ extern char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ extern int optind; /* Callers store zero here to inhibit the error message `getopt' prints for unrecognized options. */ extern int opterr; /* Set to an option character which was unrecognized. */ extern int optopt; /* Describe the long-named options requested by the application. The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector of `struct option' terminated by an element containing a name which is zero. The field `has_arg' is: no_argument (or 0) if the option does not take an argument, required_argument (or 1) if the option requires an argument, optional_argument (or 2) if the option takes an optional argument. If the field `flag' is not NULL, it points to a variable that is set to the value given in the field `val' when the option is found, but left unchanged if the option is not found. To have a long-named option do something other than set an `int' to a compiled-in constant, such as set a value from `optarg', set the option's `flag' field to zero and its `val' field to a nonzero value (the equivalent single-letter option character, if there is one). For long options that have a zero `flag' field, `getopt' returns the contents of the `val' field. */ struct option { #if defined (__STDC__) && __STDC__ const char *name; #else char *name; #endif /* has_arg can't be an enum because some compilers complain about type mismatches in all the code that assumes it is an int. */ int has_arg; int *flag; int val; }; /* Names for the values of the `has_arg' field of `struct option'. */ #define no_argument 0 #define required_argument 1 #define optional_argument 2 #ifdef __GNU_LIBRARY__ /* Many other libraries have conflicting prototypes for getopt, with differences in the consts, in stdlib.h. To avoid compilation errors, only prototype getopt for the GNU C library. */ extern int getopt (int argc, char *const *argv, const char *shortopts); #else /* not __GNU_LIBRARY__ */ extern int getopt (); #endif /* __GNU_LIBRARY__ */ extern int getopt_long (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind); extern int getopt_long_only (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind); /* Internal only. Users should not call this directly. */ extern int _getopt_internal (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind, int long_only); #ifdef __cplusplus } #endif #endif /* getopt.h */ atomicparsley-20240608.083822.1ed9031/src/extras/getopt1.c000066400000000000000000000052271463107535600222330ustar00rootroot00000000000000/* getopt_long and getopt_long_only entry points for GNU getopt. Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include "getopt.h" #include /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 #include #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ #include #endif #ifndef NULL #define NULL 0 #endif int getopt_long (int argc, char *const *argv, const char *options, const struct option *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } /* Like getopt_long, but '-' as well as '--' can indicate a long option. If an option that starts with '-' (not '--') doesn't match a long option, but does match a short option, it is parsed as a short option instead. */ int getopt_long_only (int argc, char *const *argv, const char *options, const struct option *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 1); } #endif /* Not ELIDE_CODE. */ atomicparsley-20240608.083822.1ed9031/src/iconv.cpp000066400000000000000000000742661463107535600210310ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - iconv.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C) 2005-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// //==================================================================// // utf conversion functions from libxml2 /* Copyright (C) 1998-2003 Daniel Veillard. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is fur- nished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE DANIEL VEILLARD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of Daniel Veillard shall not be used in advertising or otherwise to promote the sale, use or other deal- ings in this Software without prior written authorization from him. */ // Original code for IsoLatin1 and UTF-16 by "Martin J. Duerst" #include "AtomicParsley.h" const unsigned short cp437upperbytes[128] = { 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0}; const unsigned short cp850upperbytes[128] = { 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192, 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510, 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, 0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x0131, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE, 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4, 0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0}; const unsigned short cp852upperbytes[128] = { 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x016F, 0x0107, 0x00E7, 0x0142, 0x00EB, 0x0150, 0x0151, 0x00EE, 0x0179, 0x00C4, 0x0106, 0x00C9, 0x0139, 0x013A, 0x00F4, 0x00F6, 0x013D, 0x013E, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x0164, 0x0165, 0x0141, 0x00D7, 0x010D, 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x0104, 0x0105, 0x017D, 0x017E, 0x0118, 0x0119, 0x00AC, 0x017A, 0x010C, 0x015F, 0x00AB, 0x00BB, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x011A, 0x015E, 0x2563, 0x2551, 0x2557, 0x255D, 0x017B, 0x017C, 0x2510, 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0102, 0x0103, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, 0x0111, 0x0110, 0x010E, 0x00CB, 0x010F, 0x0147, 0x00CD, 0x00CE, 0x011B, 0x2518, 0x250C, 0x2588, 0x2584, 0x0162, 0x016E, 0x2580, 0x00D3, 0x00DF, 0x00D4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, 0x0154, 0x00DA, 0x0155, 0x0170, 0x00FD, 0x00DD, 0x0163, 0x00B4, 0x00AD, 0x02DD, 0x02DB, 0x02C7, 0x02D8, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x02D9, 0x0171, 0x0158, 0x0159, 0x25A0, 0x00A0}; const unsigned short cp855upperbytes[128] = { 0x0452, 0x0402, 0x0453, 0x0403, 0x0451, 0x0401, 0x0454, 0x0404, 0x0455, 0x0405, 0x0456, 0x0406, 0x0457, 0x0407, 0x0458, 0x0408, 0x0459, 0x0409, 0x045A, 0x040A, 0x045B, 0x040B, 0x045C, 0x040C, 0x045E, 0x040E, 0x045F, 0x040F, 0x044E, 0x042E, 0x044A, 0x042A, 0x0430, 0x0410, 0x0431, 0x0411, 0x0446, 0x0426, 0x0434, 0x0414, 0x0435, 0x0415, 0x0444, 0x0424, 0x0433, 0x0413, 0x00AB, 0x00BB, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0445, 0x0425, 0x0438, 0x0418, 0x2563, 0x2551, 0x2557, 0x255D, 0x0439, 0x0419, 0x2510, 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x043A, 0x041A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, 0x043B, 0x041B, 0x043C, 0x041C, 0x043D, 0x041D, 0x043E, 0x041E, 0x043F, 0x2518, 0x250C, 0x2588, 0x2584, 0x041F, 0x044F, 0x2580, 0x042F, 0x0440, 0x0420, 0x0441, 0x0421, 0x0442, 0x0422, 0x0443, 0x0423, 0x0436, 0x0416, 0x0432, 0x0412, 0x044C, 0x042C, 0x2116, 0x00AD, 0x044B, 0x042B, 0x0437, 0x0417, 0x0448, 0x0428, 0x044D, 0x042D, 0x0449, 0x0429, 0x0447, 0x0427, 0x00A7, 0x25A0, 0x00A0}; const unsigned short cp858upperbytes[128] = { 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192, 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510, 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, 0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x20AC, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE, 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4, 0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0}; //==================================================================// // utf conversion functions from libxml2 /* Copyright (C) 1998-2003 Daniel Veillard. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is fur- nished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE DANIEL VEILLARD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of Daniel Veillard shall not be used in advertising or otherwise to promote the sale, use or other deal- ings in this Software without prior written authorization from him. */ // Original code for IsoLatin1 and UTF-16 by "Martin J. Duerst" static int xmlLittleEndian = #ifdef WORDS_BIGENDIAN 0 #else 1 #endif ; /** * isolat1ToUTF8: * @out: a pointer to an array of bytes to store the result * @outlen: the length of @out * @in: a pointer to an array of ISO Latin 1 chars * @inlen: the length of @in * * Take a block of ISO Latin 1 chars in and try to convert it to an UTF-8 * block of chars out. * Returns the number of bytes written if success, or -1 otherwise * The value of @inlen after return is the number of octets consumed * if the return value is positive, else unpredictable. * The value of @outlen after return is the number of octets consumed. */ int isolat1ToUTF8(unsigned char *out, int outlen, const unsigned char *in, int inlen) { unsigned char *outstart = out; const unsigned char *base = in; unsigned char *outend; const unsigned char *inend; const unsigned char *instop; if ((out == NULL) || (in == NULL) || (outlen == 0) || (inlen == 0)) return (-1); outend = out + outlen; inend = in + (inlen); instop = inend; while (in < inend && out < outend - 1) { if (*in >= 0x80) { *out++ = (((*in) >> 6) & 0x1F) | 0xC0; *out++ = ((*in) & 0x3F) | 0x80; ++in; } if (instop - in > outend - out) instop = in + (outend - out); while (in < instop && *in < 0x80) { *out++ = *in++; } } if (in < inend && out < outend && *in < 0x80) { *out++ = *in++; } outlen = out - outstart; inlen = in - base; return (outlen); } /** * UTF8Toisolat1: * @out: a pointer to an array of bytes to store the result * @outlen: the length of @out * @in: a pointer to an array of UTF-8 chars * @inlen: the length of @in * * Take a block of UTF-8 chars in and try to convert it to an ISO Latin 1 * block of chars out. * * Returns the number of bytes written if success, -2 if the transcoding fails, or -1 otherwise * The value of @inlen after return is the number of octets consumed * if the return value is positive, else unpredictable. * The value of @outlen after return is the number of octets consumed. */ int UTF8Toisolat1(unsigned char *out, int outlen, const unsigned char *in, int inlen) { const unsigned char *processed = in; const unsigned char *outend; const unsigned char *outstart = out; const unsigned char *instart = in; const unsigned char *inend; unsigned int c, d; int trailing; if ((out == NULL) || (outlen == 0) || (inlen == 0)) return (-1); if (in == NULL) { /* * initialization nothing to do */ outlen = 0; inlen = 0; return (0); } inend = in + (inlen); outend = out + (outlen); while (in < inend) { d = *in++; if (d < 0x80) { c = d; trailing = 0; } else if (d < 0xC0) { /* trailing byte in leading position */ outlen = out - outstart; inlen = processed - instart; return (-2); } else if (d < 0xE0) { c = d & 0x1F; trailing = 1; } else if (d < 0xF0) { c = d & 0x0F; trailing = 2; } else if (d < 0xF8) { c = d & 0x07; trailing = 3; } else { /* no chance for this in IsoLat1 */ outlen = out - outstart; inlen = processed - instart; return (-2); } if (inend - in < trailing) { break; } for (; trailing; trailing--) { if (in >= inend) break; if (((d = *in++) & 0xC0) != 0x80) { outlen = out - outstart; inlen = processed - instart; return (-2); } c <<= 6; c |= d & 0x3F; } /* assertion: c is a single UTF-4 value */ if (c <= 0xFF) { if (out >= outend) break; *out++ = c; } else { /* no chance for this in IsoLat1 */ outlen = out - outstart; inlen = processed - instart; return (-2); } processed = in; } outlen = out - outstart; inlen = processed - instart; return (outlen); } /** * UTF16BEToUTF8: * @out: a pointer to an array of bytes to store the result * @outlen: the length of @out * @inb: a pointer to an array of UTF-16 passed as a byte array * @inlenb: the length of @in in UTF-16 chars * * Take a block of UTF-16 ushorts in and try to convert it to an UTF-8 * block of chars out. This function assumes the endian property * is the same between the native type of this machine and the * inputed one. * * Returns the number of bytes written, or -1 if lack of space, or -2 * if the transcoding fails (if *in is not a valid utf16 string) * The value of *inlen after return is the number of octets consumed * if the return value is positive, else unpredictable. */ int UTF16BEToUTF8(unsigned char *out, int outlen, const unsigned char *inb, int inlenb) { unsigned char *outstart = out; const unsigned char *processed = inb; unsigned char *outend = out + outlen; unsigned short *in = (unsigned short *)inb; unsigned short *inend; unsigned int c, d, inlen; unsigned char *tmp; int bits; if ((inlenb % 2) == 1) (inlenb)--; inlen = inlenb / 2; inend = in + inlen; while (in < inend) { if (xmlLittleEndian) { tmp = (unsigned char *)in; c = *tmp++; c = c << 8; c = c | (unsigned int)*tmp; in++; } else { c = *in++; if (c == 0xFEFF) { c = *in++; // skip BOM } } if ((c & 0xFC00) == 0xD800) { /* surrogates */ if (in >= inend) { /* (in > inend) shouldn't happens */ outlen = out - outstart; inlenb = processed - inb; return (-2); } if (xmlLittleEndian) { tmp = (unsigned char *)in; d = *tmp++; d = d << 8; d = d | (unsigned int)*tmp; in++; } else { d = *in++; } if ((d & 0xFC00) == 0xDC00) { c &= 0x03FF; c <<= 10; c |= d & 0x03FF; c += 0x10000; } else { outlen = out - outstart; inlenb = processed - inb; return (-2); } } /* assertion: c is a single UTF-4 value */ if (out >= outend) break; if (c < 0x80) { *out++ = c; bits = -6; } else if (c < 0x800) { *out++ = ((c >> 6) & 0x1F) | 0xC0; bits = 0; } else if (c < 0x10000) { *out++ = ((c >> 12) & 0x0F) | 0xE0; bits = 6; } else { *out++ = ((c >> 18) & 0x07) | 0xF0; bits = 12; } for (; bits >= 0; bits -= 6) { if (out >= outend) break; *out++ = ((c >> bits) & 0x3F) | 0x80; } processed = (const unsigned char *)in; } outlen = out - outstart; inlenb = processed - inb; return (outlen); } /** * UTF8ToUTF16BE: * @outb: a pointer to an array of bytes to store the result * @outlen: the length of @outb * @in: a pointer to an array of UTF-8 chars * @inlen: the length of @in * * Take a block of UTF-8 chars in and try to convert it to an UTF-16BE * block of chars out. * * Returns the number of byte written, or -1 by lack of space, or -2 * if the transcoding failed. */ int UTF8ToUTF16BE(unsigned char *outb, int outlen, const unsigned char *in, int inlen) { unsigned short *out = (unsigned short *)outb; const unsigned char *processed = in; const unsigned char *const instart = in; unsigned short *outstart = out; unsigned short *outend; const unsigned char *inend = in + inlen; unsigned int c, d; int trailing; unsigned char *tmp; unsigned short tmp1, tmp2; /* UTF-16BE has no BOM */ if ((outb == NULL) || (outlen == 0) || (inlen == 0)) return (-1); if (in == NULL) { outlen = 0; inlen = 0; return (0); } outend = out + (outlen / 2); while (in < inend) { d = *in++; if (d < 0x80) { c = d; trailing = 0; } else if (d < 0xC0) { /* trailing byte in leading position */ outlen = out - outstart; inlen = processed - instart; return (-2); } else if (d < 0xE0) { c = d & 0x1F; trailing = 1; } else if (d < 0xF0) { c = d & 0x0F; trailing = 2; } else if (d < 0xF8) { c = d & 0x07; trailing = 3; } else { /* no chance for this in UTF-16 */ outlen = out - outstart; inlen = processed - instart; return (-2); } if (inend - in < trailing) { break; } for (; trailing; trailing--) { if ((in >= inend) || (((d = *in++) & 0xC0) != 0x80)) break; c <<= 6; c |= d & 0x3F; } /* assertion: c is a single UTF-4 value */ if (c < 0x10000) { if (out >= outend) break; if (xmlLittleEndian) { tmp = (unsigned char *)out; *tmp = c >> 8; *(tmp + 1) = c; out++; } else { *out++ = c; } } else if (c < 0x110000) { if (out + 1 >= outend) break; c -= 0x10000; if (xmlLittleEndian) { tmp1 = 0xD800 | (c >> 10); tmp = (unsigned char *)out; *tmp = tmp1 >> 8; *(tmp + 1) = (unsigned char)tmp1; out++; tmp2 = 0xDC00 | (c & 0x03FF); tmp = (unsigned char *)out; *tmp = tmp2 >> 8; *(tmp + 1) = (unsigned char)tmp2; out++; } else { *out++ = 0xD800 | (c >> 10); *out++ = 0xDC00 | (c & 0x03FF); } } else break; processed = in; } outlen = (out - outstart) * 2; inlen = processed - instart; return (outlen); } /** * UTF16LEToUTF8: * @out: a pointer to an array of bytes to store the result * @outlen: the length of @out * @inb: a pointer to an array of UTF-16LE passwd as a byte array * @inlenb: the length of @in in UTF-16LE chars * * Take a block of UTF-16LE ushorts in and try to convert it to an UTF-8 * block of chars out. This function assumes the endian property * is the same between the native type of this machine and the * inputed one. * * Returns the number of bytes written, or -1 if lack of space, or -2 * if the transcoding fails (if *in is not a valid utf16 string) * The value of *inlen after return is the number of octets consumed * if the return value is positive, else unpredictable. */ int UTF16LEToUTF8(unsigned char *out, int outlen, const unsigned char *inb, int inlenb) { unsigned char *outstart = out; const unsigned char *processed = inb; unsigned char *outend = out + outlen; unsigned short *in = (unsigned short *)inb; unsigned short *inend; unsigned int c, d, inlen; unsigned char *tmp; int bits; if ((inlenb % 2) == 1) (inlenb)--; inlen = inlenb / 2; inend = in + inlen; while ((in < inend) && (out - outstart + 5 < outlen)) { if (xmlLittleEndian) { c = *in++; } else { tmp = (unsigned char *)in; c = *tmp++; c = c | (((unsigned int)*tmp) << 8); in++; } if ((c & 0xFC00) == 0xD800) { /* surrogates */ if (in >= inend) { /* (in > inend) shouldn't happens */ break; } if (xmlLittleEndian) { d = *in++; } else { tmp = (unsigned char *)in; d = *tmp++; d = d | (((unsigned int)*tmp) << 8); in++; } if ((d & 0xFC00) == 0xDC00) { c &= 0x03FF; c <<= 10; c |= d & 0x03FF; c += 0x10000; } else { outlen = out - outstart; inlenb = processed - inb; return (-2); } } /* assertion: c is a single UTF-4 value */ if (out >= outend) break; if (c < 0x80) { *out++ = c; bits = -6; } else if (c < 0x800) { *out++ = ((c >> 6) & 0x1F) | 0xC0; bits = 0; } else if (c < 0x10000) { *out++ = ((c >> 12) & 0x0F) | 0xE0; bits = 6; } else { *out++ = ((c >> 18) & 0x07) | 0xF0; bits = 12; } for (; bits >= 0; bits -= 6) { if (out >= outend) break; *out++ = ((c >> bits) & 0x3F) | 0x80; } processed = (const unsigned char *)in; } outlen = out - outstart; inlenb = processed - inb; return (outlen); } /** * UTF8ToUTF16LE: * @outb: a pointer to an array of bytes to store the result * @outlen: the length of @outb * @in: a pointer to an array of UTF-8 chars * @inlen: the length of @in * * Take a block of UTF-8 chars in and try to convert it to an UTF-16LE * block of chars out. * * Returns the number of bytes written, or -1 if lack of space, or -2 * if the transcoding failed. */ int UTF8ToUTF16LE(unsigned char *outb, int outlen, const unsigned char *in, int inlen) { unsigned short *out = (unsigned short *)outb; const unsigned char *processed = in; const unsigned char *const instart = in; unsigned short *outstart = out; unsigned short *outend; const unsigned char *inend = in + inlen; unsigned int c, d; int trailing; unsigned char *tmp; unsigned short tmp1, tmp2; /* UTF16LE encoding has no BOM */ if ((out == NULL) || (outlen == 0) || (inlen == 0)) return (-1); if (in == NULL) { outlen = 0; inlen = 0; return (0); } outend = out + (outlen / 2); while (in < inend) { d = *in++; if (d < 0x80) { c = d; trailing = 0; } else if (d < 0xC0) { /* trailing byte in leading position */ outlen = (out - outstart) * 2; inlen = processed - instart; return (-2); } else if (d < 0xE0) { c = d & 0x1F; trailing = 1; } else if (d < 0xF0) { c = d & 0x0F; trailing = 2; } else if (d < 0xF8) { c = d & 0x07; trailing = 3; } else { /* no chance for this in UTF-16 */ outlen = (out - outstart) * 2; inlen = processed - instart; return (-2); } if (inend - in < trailing) { break; } for (; trailing; trailing--) { if ((in >= inend) || (((d = *in++) & 0xC0) != 0x80)) break; c <<= 6; c |= d & 0x3F; } /* assertion: c is a single UTF-4 value */ if (c < 0x10000) { if (out >= outend) break; if (xmlLittleEndian) { *out++ = c; } else { tmp = (unsigned char *)out; *tmp = c; *(tmp + 1) = c >> 8; out++; } } else if (c < 0x110000) { if (out + 1 >= outend) break; c -= 0x10000; if (xmlLittleEndian) { *out++ = 0xD800 | (c >> 10); *out++ = 0xDC00 | (c & 0x03FF); } else { tmp1 = 0xD800 | (c >> 10); tmp = (unsigned char *)out; *tmp = (unsigned char)tmp1; *(tmp + 1) = tmp1 >> 8; out++; tmp2 = 0xDC00 | (c & 0x03FF); tmp = (unsigned char *)out; *tmp = (unsigned char)tmp2; *(tmp + 1) = tmp2 >> 8; out++; } } else break; processed = in; } outlen = (out - outstart) * 2; inlen = processed - instart; return (outlen); } int isUTF8(const char *in_string) { // fprintf(stdout, "utf8 test-> %s\n", in_string); int str_bytes = 0; if (in_string != NULL) { str_bytes = strlen(in_string); } else { return -1; } bool is_validUTF8 = true; bool is_high_ascii = false; int index = 0; while (index < str_bytes && is_validUTF8) { char achar = in_string[index]; int supplemental_bytes = 0; if ((unsigned char)achar > 0x80) { is_high_ascii = true; } if ((achar & 0x80) == 0) { // 0xxxxxxx ++index; } else if ((achar & 0xF8) == 0xF0) { // 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx ++index; supplemental_bytes = 3; is_high_ascii = true; } else if ((achar & 0xE0) == 0xE0) { // 1110zzzz 10yyyyyy 10xxxxxx ++index; supplemental_bytes = 2; is_high_ascii = true; } else if ((achar & 0xE0) == 0xC0) { // 110yyyyy 10xxxxxx ++index; supplemental_bytes = 1; is_high_ascii = true; } else { is_validUTF8 = false; } while (is_validUTF8 && supplemental_bytes--) { if (index >= str_bytes) { is_validUTF8 = false; } else if ((in_string[index++] & 0xC0) != 0x80) { // 10uuzzzz is_validUTF8 = false; } } } if (is_high_ascii) { return 8; } else if (is_validUTF8) { return 1; } else { return 0; } } /*---------------------- utf8_length in_string - pointer to location of a utf8 string char_limit - either 0 (count all characters) or non-zero (limit utf8 to that character count) Because of the lovely way utf8 is aligned, test only the first byte in each. If char_limit is 0, return the number of CHARACTERS in the string, if the char_limit is not zero (the char_limit will equal utf_string_leghth because of the break), so change gears, save space and just return the byte_count. ----------------------*/ #include unsigned int utf8_length(const char *in_string, unsigned int char_limit) { const char *utf8_str = in_string; unsigned int utf8_string_length = 0; unsigned int in_str_len = strlen(in_string); unsigned int byte_count = 0; unsigned int bytes_in_char = 0; if (in_string == NULL) return 0; while (byte_count < in_str_len) { bytes_in_char = 0; if ((*utf8_str & 0x80) == 0x00) bytes_in_char = 1; else if ((*utf8_str & 0xE0) == 0xC0) bytes_in_char = 2; else if ((*utf8_str & 0xF0) == 0xE0) bytes_in_char = 3; else if ((*utf8_str & 0xF8) == 0xF0) bytes_in_char = 4; if (bytes_in_char > 0) { utf8_string_length++; utf8_str += bytes_in_char; byte_count += bytes_in_char; } else { break; } if (char_limit != 0 && char_limit == utf8_string_length) { utf8_string_length = byte_count; break; } } return utf8_string_length; } #if defined(_WIN32) && !defined(__CYGWIN__) unsigned char APar_Return_rawutf8_CP(unsigned short cp_bound_glyph) { unsigned short total_known_points = 0; unsigned int win32cp = GetConsoleCP(); if (win32cp == 437 || win32cp == 850 || win32cp == 852 || win32cp == 855 || win32cp == 858) { total_known_points = 128; } else { if (cp_bound_glyph >= 0x0080) { exit(win32cp); } } if (cp_bound_glyph < 0x0080) { return cp_bound_glyph << 0; } else if (total_known_points) { if (win32cp == 437) { for (uint16_t i = 0; i < total_known_points; i++) { if (cp_bound_glyph == cp437upperbytes[i]) { return i + 128; } } } else if (win32cp == 850) { for (uint16_t i = 0; i < total_known_points; i++) { if (cp_bound_glyph == cp850upperbytes[i]) { return i + 128; } } } else if (win32cp == 852) { for (uint16_t i = 0; i < total_known_points; i++) { if (cp_bound_glyph == cp852upperbytes[i]) { return i + 128; } } } else if (win32cp == 855) { for (uint16_t i = 0; i < total_known_points; i++) { if (cp_bound_glyph == cp855upperbytes[i]) { return i + 128; } } } else if (win32cp == 858) { for (uint16_t i = 0; i < total_known_points; i++) { if (cp_bound_glyph == cp858upperbytes[i]) { return i + 128; } } } else { fprintf(stderr, "AtomicParsley error: this windows codepage(%u) is " "unsupported.\nProvide the output of the 'CPTester' utility run " "from the bat script\n", win32cp); exit(win32cp); } } return 0; } int strip_bogusUTF16toRawUTF8(unsigned char *out, int inlen, wchar_t *in, int outlen) { unsigned char *outstart = out; unsigned char *outend; const wchar_t *inend; const wchar_t *instop; if ((out == NULL) || (in == NULL) || (outlen == 0) || (inlen == 0)) return (-1); outend = out + outlen; inend = in + (inlen); instop = inend; while (in < inend && out < outend - 1) { *out++ = APar_Return_rawutf8_CP(*in); //*in << 0; ++in; } outlen = out - outstart; return (outlen); } #endif /*---------------------- test_conforming_alpha_string in_string - pointer to location of a utf8 string limit string to A-Z or a-z ----------------------*/ int test_conforming_alpha_string(char *in_string) { int valid_bytes = 0; int string_len = 0; char *test_str = in_string; if (in_string != NULL) { string_len = strlen(in_string); } else { return -1; } while (valid_bytes < string_len) { if ((*test_str >= 65 && *test_str <= 90) // A-Z || (*test_str >= 97 && *test_str <= 122) // a-z || (*test_str >= 48 && *test_str <= 57) // 0-9 || *test_str == 32 || *test_str == 95) // space or underscore { valid_bytes++; } else { break; } test_str++; } return valid_bytes; } bool test_limited_ascii(char *in_string, unsigned int str_len) { char *test_str = in_string; while (test_str < in_string + str_len) { if (*test_str < 32 || *test_str > 126) { return false; } test_str++; } return true; } atomicparsley-20240608.083822.1ed9031/src/id3v2.cpp000066400000000000000000003533101463107535600206300ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - id3v2.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C)2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #include "AtomicParsley.h" #include "CDtoc.h" #include "id3v2defs.h" ID3v2Tag *GlobalID3Tag = NULL; // prefs uint8_t AtomicParsley_ID3v2Tag_MajorVersion = 4; uint8_t AtomicParsley_ID3v2Tag_RevisionVersion = 0; uint8_t AtomicParsley_ID3v2Tag_Flags = 0; bool ID3v2Tag_Flag_Footer = false; // bit4; MPEG-4 'ID32' requires this to be false bool ID3v2Tag_Flag_Experimental = true; // bit5 bool ID3v2Tag_Flag_ExtendedHeader = true; // bit6 bool ID3v2Tag_Flag_Unsyncronization = false; // bit7 /////////////////////////////////////////////////////////////////////////////////////// // id3 number conversion functions // /////////////////////////////////////////////////////////////////////////////////////// uint64_t syncsafeXX_to_UInt64(char *syncsafe_int, uint8_t syncsafe_len) { if (syncsafe_len == 5) { if (syncsafe_int[0] & 0x80 || syncsafe_int[1] & 0x80 || syncsafe_int[2] & 0x80 || syncsafe_int[3] & 0x80 || syncsafe_int[4] & 0x80) return 0; return ((uint64_t)syncsafe_int[0] << 28) | (syncsafe_int[1] << 21) | (syncsafe_int[2] << 14) | (syncsafe_int[3] << 7) | syncsafe_int[4]; } else if (syncsafe_len == 6) { if (syncsafe_int[0] & 0x80 || syncsafe_int[1] & 0x80 || syncsafe_int[2] & 0x80 || syncsafe_int[3] & 0x80 || syncsafe_int[4] & 0x80 || syncsafe_int[5] & 0x80) return 0; return ((uint64_t)syncsafe_int[0] << 35) | ((uint64_t)syncsafe_int[1] << 28) | (syncsafe_int[2] << 21) | (syncsafe_int[3] << 14) | (syncsafe_int[4] << 7) | syncsafe_int[5]; } else if (syncsafe_len == 7) { if (syncsafe_int[0] & 0x80 || syncsafe_int[1] & 0x80 || syncsafe_int[2] & 0x80 || syncsafe_int[3] & 0x80 || syncsafe_int[4] & 0x80 || syncsafe_int[5] & 0x80 || syncsafe_int[6] & 0x80) return 0; return ((uint64_t)syncsafe_int[0] << 42) | ((uint64_t)syncsafe_int[1] << 35) | ((uint64_t)syncsafe_int[2] << 28) | (syncsafe_int[3] << 21) | (syncsafe_int[3] << 14) | (syncsafe_int[5] << 7) | syncsafe_int[6]; } else if (syncsafe_len == 8) { if (syncsafe_int[0] & 0x80 || syncsafe_int[1] & 0x80 || syncsafe_int[2] & 0x80 || syncsafe_int[3] & 0x80 || syncsafe_int[4] & 0x80 || syncsafe_int[5] & 0x80 || syncsafe_int[6] & 0x80 || syncsafe_int[7] & 0x80) return 0; return ((uint64_t)syncsafe_int[0] << 49) | ((uint64_t)syncsafe_int[1] << 42) | ((uint64_t)syncsafe_int[2] << 35) | ((uint64_t)syncsafe_int[3] << 28) | (syncsafe_int[4] << 21) | (syncsafe_int[5] << 14) | (syncsafe_int[6] << 7) | syncsafe_int[7]; } else if (syncsafe_len == 9) { if (syncsafe_int[0] & 0x80 || syncsafe_int[1] & 0x80 || syncsafe_int[2] & 0x80 || syncsafe_int[3] & 0x80 || syncsafe_int[4] & 0x80 || syncsafe_int[5] & 0x80 || syncsafe_int[6] & 0x80 || syncsafe_int[7] & 0x80 || syncsafe_int[8] & 0x80) return 0; return ((uint64_t)syncsafe_int[0] << 56) | ((uint64_t)syncsafe_int[1] << 49) | ((uint64_t)syncsafe_int[2] << 42) | ((uint64_t)syncsafe_int[3] << 35) | ((uint64_t)syncsafe_int[4] << 28) | (syncsafe_int[5] << 21) | (syncsafe_int[6] << 14) | (syncsafe_int[7] << 7) | syncsafe_int[8]; } return 0; } uint32_t syncsafe32_to_UInt32(char *syncsafe_int) { if (syncsafe_int[0] & 0x80 || syncsafe_int[1] & 0x80 || syncsafe_int[2] & 0x80 || syncsafe_int[3] & 0x80) return 0; return (syncsafe_int[0] << 21) | (syncsafe_int[1] << 14) | (syncsafe_int[2] << 7) | syncsafe_int[3]; } uint16_t syncsafe16_to_UInt16(char *syncsafe_int) { if (syncsafe_int[0] & 0x80 || syncsafe_int[1] & 0x80) return 0; return (syncsafe_int[0] << 7) | syncsafe_int[1]; } void convert_to_syncsafe32(uint32_t in_uint, char *buffer) { buffer[0] = (in_uint >> 21) & 0x7F; buffer[1] = (in_uint >> 14) & 0x7F; buffer[2] = (in_uint >> 7) & 0x7F; buffer[3] = (in_uint >> 0) & 0x7F; return; } uint8_t convert_to_syncsafeXX(uint64_t in_uint, char *buffer) { if #if defined(_MSC_VER) (in_uint <= (uint64_t)34359738367) #else (in_uint <= 34359738367ULL) #endif { buffer[0] = (in_uint >> 28) & 0x7F; buffer[1] = (in_uint >> 21) & 0x7F; buffer[2] = (in_uint >> 14) & 0x7F; buffer[3] = (in_uint >> 7) & 0x7F; buffer[4] = (in_uint >> 0) & 0x7F; return 5; #if defined(_MSC_VER) } else if (in_uint <= (uint64_t)4398046511103) { #else } else if (in_uint <= 4398046511103ULL) { #endif buffer[0] = (in_uint >> 35) & 0x7F; buffer[1] = (in_uint >> 28) & 0x7F; buffer[2] = (in_uint >> 21) & 0x7F; buffer[3] = (in_uint >> 14) & 0x7F; buffer[4] = (in_uint >> 7) & 0x7F; buffer[5] = (in_uint >> 0) & 0x7F; return 6; #if defined(_MSC_VER) } else if (in_uint <= (uint64_t)562949953421311) { #else } else if (in_uint <= 562949953421311ULL) { #endif buffer[0] = (in_uint >> 42) & 0x7F; buffer[1] = (in_uint >> 35) & 0x7F; buffer[2] = (in_uint >> 28) & 0x7F; buffer[3] = (in_uint >> 21) & 0x7F; buffer[4] = (in_uint >> 14) & 0x7F; buffer[5] = (in_uint >> 7) & 0x7F; buffer[6] = (in_uint >> 0) & 0x7F; return 7; #if defined(_MSC_VER) } else if (in_uint <= (uint64_t)72057594037927935) { #else } else if (in_uint <= 72057594037927935ULL) { #endif buffer[0] = (in_uint >> 49) & 0x7F; buffer[1] = (in_uint >> 42) & 0x7F; buffer[2] = (in_uint >> 35) & 0x7F; buffer[3] = (in_uint >> 28) & 0x7F; buffer[4] = (in_uint >> 21) & 0x7F; buffer[5] = (in_uint >> 14) & 0x7F; buffer[6] = (in_uint >> 7) & 0x7F; buffer[7] = (in_uint >> 0) & 0x7F; return 8; #if defined(_MSC_VER) } else if (in_uint <= (uint64_t)9223372036854775807) { #else } else if (in_uint <= 9223372036854775807ULL) { // that is some hardcore // lovin' #endif buffer[0] = (in_uint >> 56) & 0x7F; buffer[1] = (in_uint >> 49) & 0x7F; buffer[2] = (in_uint >> 42) & 0x7F; buffer[3] = (in_uint >> 35) & 0x7F; buffer[4] = (in_uint >> 28) & 0x7F; buffer[5] = (in_uint >> 21) & 0x7F; buffer[6] = (in_uint >> 14) & 0x7F; buffer[7] = (in_uint >> 7) & 0x7F; buffer[8] = (in_uint >> 0) & 0x7F; return 9; } return 0; } uint32_t UInt24FromBigEndian(const char *string) { // v2.2 frame lengths return ((0 << 24) | ((string[0] & 0xff) << 16) | ((string[1] & 0xff) << 8) | (string[2] & 0xff) << 0); } uint32_t ID3v2_desynchronize(char *buffer, uint32_t bufferlen) { char *buf_ptr = buffer; uint32_t desync_count = 0; for (uint32_t i = 0; i < bufferlen; i++) { if ((unsigned char)buffer[i] == 0xFF && (unsigned char)buffer[i + 1] == 0x00) { buf_ptr[desync_count] = buffer[i]; i++; } else { buf_ptr[desync_count] = buffer[i]; } desync_count++; } return desync_count; } /////////////////////////////////////////////////////////////////////////////////////// // bit tests & generic functions // /////////////////////////////////////////////////////////////////////////////////////// bool ID3v2_PaddingTest(char *buffer) { if (buffer[0] & 0x00 || buffer[1] & 0x00 || buffer[2] & 0x00 || buffer[3] & 0x00) return true; return false; } bool ID3v2_TestFrameID_NonConformance(char *frameid) { for (uint8_t i = 0; i < 4; i++) { if (!((frameid[i] >= '0' && frameid[i] <= '9') || (frameid[i] >= 'A' && frameid[i] <= 'Z'))) { return true; } } return false; } bool ID3v2_TestTagFlag(uint8_t TagFlag, uint8_t TagBit) { if (TagFlag & TagBit) return true; return false; } bool ID3v2_TestFrameFlag(uint16_t FrameFlag, uint16_t FrameBit) { if (FrameFlag & FrameBit) return true; return false; } uint8_t TextField_TestBOM(char *astring) { if (((unsigned char *)astring)[0] == 0xFE && ((unsigned char *)astring)[1] == 0xFF) return 13; // 13 looks like a B for BE if (((unsigned char *)astring)[0] == 0xFF && ((unsigned char *)astring)[1] == 0xFE) return 1; // 1 looks like a l for LE return 0; } void APar_LimitBufferRange(uint32_t max_allowed, uint32_t target_amount) { if (target_amount > max_allowed) { fprintf( stderr, "AtomicParsley error: insufficient memory to process ID3 tags (%" PRIu32 ">%" PRIu32 "). Exiting.\n", target_amount, max_allowed); exit(target_amount - max_allowed); } return; } void APar_ValidateNULLTermination8bit(ID3v2Fields *this_field) { if (this_field->field_string[0] == 0) { this_field->field_length = 1; } else if (this_field->field_string[this_field->field_length - 1] != 0) { this_field->field_length += 1; } return; } void APar_ValidateNULLTermination16bit(ID3v2Fields *this_field, uint8_t encoding) { if (this_field->field_string[0] == 0 && this_field->field_string[1] == 0) { this_field->field_length = 2; if (encoding == TE_UTF16LE_WITH_BOM) { if (((uint8_t)(this_field->field_string[0]) != 0xFF && (uint8_t)(this_field->field_string[1]) != 0xFE) || ((uint8_t)(this_field->field_string[0]) != 0xFE && (uint8_t)(this_field->field_string[1]) != 0xFF)) { memcpy(this_field->field_string, "\xFF\xFE", 2); this_field->field_length = 4; } } } else if (this_field->field_string[this_field->field_length - 2] != 0 && this_field->field_string[this_field->field_length - 1] != 0) { this_field->field_length += 2; } return; } bool APar_EvalFrame_for_Field(int frametype, int fieldtype) { uint8_t frametype_idx = GetFrameCompositionDescription(frametype); for (uint8_t fld_i = 0; fld_i < FrameTypeConstructionList[frametype_idx].ID3_FieldCount; fld_i++) { if (FrameTypeConstructionList[frametype_idx].ID3_FieldComponents[fld_i] == fieldtype) { return true; } } return false; } uint8_t TestCharInRange(uint8_t testchar, uint8_t lowerlimit, uint8_t upperlimit) { if (testchar >= lowerlimit && testchar <= upperlimit) { return 1; } return 0; } uint8_t ImageListMembers() { return (uint8_t)(sizeof(ImageList) / sizeof(*ImageList)); } /////////////////////////////////////////////////////////////////////////////////////// // test functions // /////////////////////////////////////////////////////////////////////////////////////// void WriteZlibData(char *buffer, uint32_t buff_len) { char *indy_atom_path = (char *)malloc( sizeof(char) * MAXPATHLEN); // this malloc can escape memset because its // only for in-house testing strcat(indy_atom_path, "/Users/"); strcat(indy_atom_path, getenv("USER")); strcat(indy_atom_path, "/Desktop/id3framedata.txt"); FILE *test_file = fopen(indy_atom_path, "wb"); if (test_file != NULL) { fwrite(buffer, (size_t)buff_len, 1, test_file); fclose(test_file); } free(indy_atom_path); return; } /////////////////////////////////////////////////////////////////////////////////////// // cli functions // /////////////////////////////////////////////////////////////////////////////////////// static const char *ReturnFrameTypeStr(int frametype) { if (frametype == ID3_TEXT_FRAME) { return "text frame "; } else if (frametype == ID3_TEXT_FRAME_USERDEF) { return "user defined text frame"; } else if (frametype == ID3_URL_FRAME) { return "url frame "; } else if (frametype == ID3_URL_FRAME_USERDEF) { return "user defined url frame "; } else if (frametype == ID3_UNIQUE_FILE_ID_FRAME) { return "file ID "; } else if (frametype == ID3_CD_ID_FRAME) { return "AudioCD ID frame "; } else if (frametype == ID3_DESCRIBED_TEXT_FRAME) { return "described text frame "; } else if (frametype == ID3_ATTACHED_PICTURE_FRAME) { return "picture frame "; } else if (frametype == ID3_ATTACHED_OBJECT_FRAME) { return "encapuslated object frm"; } else if (frametype == ID3_GROUP_ID_FRAME) { return "group ID frame "; } else if (frametype == ID3_SIGNATURE_FRAME) { return "signature frame "; } else if (frametype == ID3_PRIVATE_FRAME) { return "private frame "; } else if (frametype == ID3_PLAYCOUNTER_FRAME) { return "playcounter "; } else if (frametype == ID3_POPULAR_FRAME) { return "popularimeter "; } return ""; } void ListID3FrameIDstrings() { const char *frametypestr = NULL; const char *presetpadding = NULL; uint16_t total_known_frames = (uint16_t)(sizeof(KnownFrames) / sizeof(*KnownFrames)); fprintf(stdout, "ID3v2.4 Implemented Frames:\nframeID type " " alias " "Description\n-----------------------------------------------" "---------------------------\n"); for (uint16_t i = 1; i < total_known_frames; i++) { if (strlen(KnownFrames[i].ID3V2p4_FrameID) != 4) continue; frametypestr = ReturnFrameTypeStr(KnownFrames[i].ID3v2_FrameType); int strpad = 12 - strlen(KnownFrames[i].CLI_frameIDpreset); if (strpad == 12) { presetpadding = " "; } else if (strpad == 11) { presetpadding = " "; } else if (strpad == 10) { presetpadding = " "; } else if (strpad == 9) { presetpadding = " "; } else if (strpad == 8) { presetpadding = " "; } else if (strpad == 7) { presetpadding = " "; } else if (strpad == 6) { presetpadding = " "; } else if (strpad == 5) { presetpadding = " "; } else if (strpad == 4) { presetpadding = " "; } else if (strpad == 3) { presetpadding = " "; } else if (strpad == 2) { presetpadding = " "; } else if (strpad == 1) { presetpadding = " "; } else if (strpad <= 0) { presetpadding = ""; } fprintf(stdout, "%s %s %s%s | %s\n", KnownFrames[i].ID3V2p4_FrameID, frametypestr, KnownFrames[i].CLI_frameIDpreset, presetpadding, KnownFrames[i].ID3V2_FrameDescription); } fprintf( stdout, "------------------------------------------------------------------------" "--\n" "For each frame type, these parameters are available:\n" " text frames: (str) [encoding]\n" " user defined text frame : (str) [desc=(str)] [encoding]\n" " url frame : (url)\n" " user defined url frame : (url) [desc=(str)] [encoding]\n" " file ID frame : (owner) " "[uniqueID={\"randomUUIDstamp\",(str)}]\n" #if defined(__APPLE__) " AudioCD ID frame : disk(num)\n" #elif defined(__linux__) " AudioCD ID frame : (/path)\n" #elif defined(_WIN32) " AudioCD ID frame : (letter)\n" #endif " described text frame : (str) [desc=(str)] [encoding]\n" " picture frame : (/path) [desc=(str)] [mimetype=(str)] " "[imagetype=(hex)] [encoding]\n" " encapuslated object frame : (/path) [desc=(str)] [mimetype=(str)] " "[filename={\"FILENAMESTAMP\",(str)}] [encoding]\n" " group ID frame : (owner) groupsymbol=(hex) [data=(str)]\n" " signature frame : (str) groupsymbol=(hex)\n" " private frame : (owner) data=(str)\n" " playcounter : (num or \"+1\")\n" " popularimeter : (owner) rating=(1...255) [counter=(num " "or \"+1\")]\n" "\n" " Legend:\n" " parameters in brackets[] signal an optional parameter, parens() " "signal a required parameter\n" " [encoding] may be one either the default UTF8, or one of { LATIN1 " "UTF16BE UTF16LE }\n" " (str) signals a string - like \"Suzie\"\n" " (num) means a number; +1 will increment a counter by 1; (hex) " "means a hexadecimal number - like 0x11)\n" " (url) menas a url, in string form; (owner) means a url/email " "string\n" " uniqueID=randomUUIDstamp will create a high quality random uuid\n" " filename=FILENAMESTAMP will embed the name of the file given in " "the /path for GEOB\n" "\n" " All frames also take additional parameters:\n" " [{root,track=(num)}] specifies file level, track level or " "(default) movie level for an ID32 atom\n" " [compress] compresses the given frame using zlib deflate " "compression\n" " [groupsymbol=(num)] associates a frame with a GRID frame of the " "same group symbol\n" " [lang=(3char)] (default='eng') sets the language/ID32 atom to " "which the frame belongs\n" " use AP --languages-list to see a list of available " "languages\n"); return; } void List_imagtype_strings() { uint8_t total_imgtyps = (uint8_t)(sizeof(ImageTypeList) / sizeof(*ImageTypeList)); fprintf(stdout, "These 'image types' are used to identify pictures embedded in " "'APIC' ID3 tags:\n usage is \"AP --ID3Tag APIC /path.jpg " "--imagetype=\"str\"\n str can be either the hex listing *or* " "the full string\n default is 0x00 - meaning 'Other'\n Hex " " Full String\n ----------------------------\n"); for (uint8_t i = 0; i < total_imgtyps; i++) { fprintf(stdout, " %s \"%s\"\n", ImageTypeList[i].hexstring, ImageTypeList[i].imagetype_str); } return; } const char *ConvertCLIFrameStr_TO_frameID(const char *frame_str) { const char *discovered_frameID = NULL; uint16_t total_known_frames = (uint16_t)(sizeof(KnownFrames) / sizeof(*KnownFrames)); for (uint16_t i = 0; i < total_known_frames; i++) { if (strcmp(KnownFrames[i].CLI_frameIDpreset, frame_str) == 0) { if (AtomicParsley_ID3v2Tag_MajorVersion == 2) discovered_frameID = KnownFrames[i].ID3V2p2_FrameID; if (AtomicParsley_ID3v2Tag_MajorVersion == 3) discovered_frameID = KnownFrames[i].ID3V2p3_FrameID; if (AtomicParsley_ID3v2Tag_MajorVersion == 4) discovered_frameID = KnownFrames[i].ID3V2p4_FrameID; if (strlen(discovered_frameID) == 0) discovered_frameID = NULL; break; } } return discovered_frameID; } // 0 = description // 1 = mimetype // 2 = type bool TestCLI_for_FrameParams(int frametype, uint8_t testparam) { if (frametype == ID3_URL_FRAME_USERDEF && testparam == 0) return true; if (frametype == ID3_UNIQUE_FILE_ID_FRAME && testparam == 3) { return true; } else if (frametype == ID3_ATTACHED_OBJECT_FRAME && testparam == 4) { return true; } else if (frametype == ID3_POPULAR_FRAME && testparam == 5) { return true; } else if (frametype == ID3_POPULAR_FRAME && testparam == 6) { return true; } else if (frametype == ID3_GROUP_ID_FRAME && testparam == 7) { return true; } else if (frametype == ID3_PRIVATE_FRAME && testparam == 8) { return true; } else { uint8_t frametype_idx = GetFrameCompositionDescription(frametype); for (uint8_t fld_i = 0; fld_i < FrameTypeConstructionList[frametype_idx].ID3_FieldCount; fld_i++) { if (FrameTypeConstructionList[frametype_idx].ID3_FieldComponents[fld_i] == ID3_DESCRIPTION_FIELD && testparam == 0) { return true; } if (FrameTypeConstructionList[frametype_idx].ID3_FieldComponents[fld_i] == ID3_MIME_TYPE_FIELD && testparam == 1) { return true; } if (FrameTypeConstructionList[frametype_idx].ID3_FieldComponents[fld_i] == ID3_PIC_TYPE_FIELD && testparam == 2) { return true; } if (FrameTypeConstructionList[frametype_idx].ID3_FieldComponents[fld_i] == ID3_PIC_TYPE_FIELD && testparam == 3) { return true; } } } return false; } /////////////////////////////////////////////////////////////////////////////////////// // frame identity functions // /////////////////////////////////////////////////////////////////////////////////////// int MatchID3FrameIDstr(const char *foundFrameID, uint8_t tagVersion) { uint16_t total_known_frames = (uint16_t)(sizeof(KnownFrames) / sizeof(*KnownFrames)); uint8_t frameLen = (tagVersion >= 3 ? 4 : 3) + 1; for (int i = 0; i < total_known_frames; i++) { const char *testFrameID = NULL; if (tagVersion == 2) testFrameID = KnownFrames[i].ID3V2p2_FrameID; if (tagVersion == 3) testFrameID = KnownFrames[i].ID3V2p3_FrameID; if (tagVersion == 4) testFrameID = KnownFrames[i].ID3V2p4_FrameID; if (memcmp(foundFrameID, testFrameID, frameLen) == 0) { return KnownFrames[i].ID3v2_InternalFrameID; } } return ID3v2_UNKNOWN_FRAME; // return the UnknownFrame if it can't be found } uint8_t GetFrameCompositionDescription(int ID3v2_FrameTypeID) { uint8_t matchingFrameDescription = 0; // return the UnknownFrame/UnknownField if it can't be found uint8_t total_frame_descrips = (uint8_t)(sizeof(FrameTypeConstructionList) / sizeof(*FrameTypeConstructionList)); for (uint8_t i = 0; i < total_frame_descrips; i++) { if (FrameTypeConstructionList[i].ID3_FrameType == ID3v2_FrameTypeID) { matchingFrameDescription = i; break; } } return matchingFrameDescription; } int FrameStr_TO_FrameType(const char *frame_str) { const char *eval_framestr = NULL; int frame_type = 0; uint16_t total_known_frames = (uint16_t)(sizeof(KnownFrames) / sizeof(*KnownFrames)); for (uint16_t i = 0; i < total_known_frames; i++) { if (AtomicParsley_ID3v2Tag_MajorVersion == 2) eval_framestr = KnownFrames[i].ID3V2p2_FrameID; if (AtomicParsley_ID3v2Tag_MajorVersion == 3) eval_framestr = KnownFrames[i].ID3V2p3_FrameID; if (AtomicParsley_ID3v2Tag_MajorVersion == 4) eval_framestr = KnownFrames[i].ID3V2p4_FrameID; if (strcmp(frame_str, eval_framestr) == 0) { frame_type = KnownFrames[i].ID3v2_FrameType; break; } } return frame_type; } ID3v2Fields *APar_FindLastTextField(ID3v2Frame *aFrame) { ID3v2Fields *lastusedtextfield = NULL; if (aFrame->textfield_tally > 0) { lastusedtextfield = aFrame->ID3v2_Frame_Fields + 1; while (true) { if (lastusedtextfield->next_field == NULL) { break; } lastusedtextfield = lastusedtextfield->next_field; } } return lastusedtextfield; } bool APar_ExtraTextFieldInit(ID3v2Fields *lastField, uint32_t utf8len, uint8_t textencoding) { ID3v2Fields *extraField = NULL; lastField->next_field = (ID3v2Fields *)calloc(1, sizeof(ID3v2Fields) * 1); if (lastField->next_field == NULL) { fprintf(stdout, "There was insufficient memory to allocate another ID3 field\n"); exit(12); } extraField = lastField->next_field; extraField->ID3v2_Field_Type = ID3_TEXT_FIELD; extraField->field_length = 0; if (textencoding == TE_UTF16LE_WITH_BOM || textencoding == TE_UTF16BE_NO_BOM) { extraField->alloc_length = 2 + (utf8len * 2); } else { extraField->alloc_length = utf8len + 1; } if (extraField->alloc_length > 0) { extraField->field_string = (char *)calloc(1, sizeof(char *) * extraField->alloc_length); if (!APar_assert((extraField->field_string != NULL), 11, "while setting an extra text field")) exit(11); return true; } return false; } /////////////////////////////////////////////////////////////////////////////////////// // id3 parsing functions // /////////////////////////////////////////////////////////////////////////////////////// uint32_t APar_ExtractField(char *buffer, uint32_t maxFieldLen, ID3v2Frame *thisFrame, ID3v2Fields *thisField, int fieldType, uint8_t textEncoding) { uint32_t bytes_used = 0; thisField->next_field = NULL; switch (fieldType) { case ID3_UNKNOWN_FIELD: { // the difference between this unknown field & say a // binary data field is the unknown field is always // the first (and only) field thisField->ID3v2_Field_Type = ID3_UNKNOWN_FIELD; thisField->field_length = maxFieldLen; thisField->field_string = (char *)calloc( 1, sizeof(char) * (maxFieldLen + 1 > 16 ? maxFieldLen + 1 : 16)); thisField->alloc_length = sizeof(char) * (maxFieldLen + 1 > 16 ? maxFieldLen + 1 : 16); memcpy(thisField->field_string, buffer, maxFieldLen); bytes_used = maxFieldLen; break; } case ID3_PIC_TYPE_FIELD: case ID3_GROUPSYMBOL_FIELD: case ID3_TEXT_ENCODING_FIELD: { thisField->ID3v2_Field_Type = fieldType; thisField->field_length = 1; thisField->field_string = (char *)calloc(1, sizeof(char) * 16); thisField->field_string[0] = buffer[0]; // memcpy(thisField->field_string, buffer, 1); thisField->alloc_length = sizeof(char) * 16; bytes_used = 1; break; } case ID3_LANGUAGE_FIELD: { thisField->ID3v2_Field_Type = ID3_LANGUAGE_FIELD; thisField->field_length = 3; thisField->field_string = (char *)calloc(1, sizeof(char) * 16); memcpy(thisField->field_string, buffer, 3); thisField->alloc_length = sizeof(char) * 16; bytes_used = 3; break; } case ID3_TEXT_FIELD: case ID3_URL_FIELD: case ID3_COUNTER_FIELD: case ID3_BINARY_DATA_FIELD: { // this class of fields may contains NULLs but // is *NOT* NULL terminated in any form thisField->ID3v2_Field_Type = fieldType; thisField->field_length = maxFieldLen; thisField->field_string = (char *)calloc( 1, sizeof(char) * maxFieldLen + 1 > 16 ? maxFieldLen + 1 : 16); memcpy(thisField->field_string, buffer, maxFieldLen); thisField->alloc_length = (sizeof(char) * maxFieldLen + 1 > 16 ? maxFieldLen + 1 : 16); if (fieldType == ID3_TEXT_FIELD) { bytes_used = findstringNULLterm(buffer, textEncoding, maxFieldLen); } else { bytes_used = maxFieldLen; } break; } case ID3_MIME_TYPE_FIELD: case ID3_OWNER_FIELD: { // difference between ID3_OWNER_FIELD & // ID3_DESCRIPTION_FIELD field classes is the owner // field is always 8859-1 encoded (single NULL term) thisField->ID3v2_Field_Type = fieldType; thisField->field_length = findstringNULLterm(buffer, 0, maxFieldLen); thisField->field_string = (char *)calloc(1, sizeof(char) * thisField->field_length + 1 > 16 ? thisField->field_length + 1 : 16); memcpy(thisField->field_string, buffer, thisField->field_length); thisField->alloc_length = (sizeof(char) * maxFieldLen + 1 > 16 ? maxFieldLen + 1 : 16); bytes_used = thisField->field_length; break; } case ID3_FILENAME_FIELD: case ID3_DESCRIPTION_FIELD: { thisField->ID3v2_Field_Type = fieldType; thisField->field_length = findstringNULLterm(buffer, textEncoding, maxFieldLen); thisField->field_string = (char *)calloc(1, sizeof(char) * thisField->field_length + 1 > 16 ? thisField->field_length + 1 : 16); memcpy(thisField->field_string, buffer, thisField->field_length); thisField->alloc_length = (sizeof(char) * thisField->field_length + 1 > 16 ? thisField->field_length + 1 : 16); bytes_used = thisField->field_length; break; } } // fprintf(stdout, "%" PRIu32 ", %s, %s\n", bytes_used, buffer, // (thisFrame->ID3v2_Frame_Fields+fieldNum)->field_string); return bytes_used; } void APar_ScanID3Frame(ID3v2Frame *targetframe, char *frame_ptr, uint32_t frameLen) { uint64_t offset_into_frame = 0; switch (targetframe->ID3v2_FrameType) { case ID3_UNKNOWN_FRAME: { APar_ExtractField(frame_ptr, frameLen, targetframe, targetframe->ID3v2_Frame_Fields, ID3_UNKNOWN_FIELD, 0); break; } case ID3_TEXT_FRAME: { uint8_t textencoding = 0xFF; offset_into_frame += APar_ExtractField(frame_ptr, 1, targetframe, targetframe->ID3v2_Frame_Fields, ID3_TEXT_ENCODING_FIELD, 0); offset_into_frame += APar_ExtractField(frame_ptr + 1, frameLen - 1, targetframe, targetframe->ID3v2_Frame_Fields + 1, ID3_TEXT_FIELD, targetframe->ID3v2_Frame_Fields->field_string[0]); targetframe->textfield_tally++; if (offset_into_frame >= frameLen) break; textencoding = targetframe->ID3v2_Frame_Fields->field_string[0]; if (offset_into_frame < frameLen) { while (true) { if (offset_into_frame >= frameLen) break; // skip the required separator for multiple strings if (textencoding == TE_LATIN1 || textencoding == TE_UTF8) { offset_into_frame += 1; } else if (textencoding == TE_UTF16LE_WITH_BOM) { offset_into_frame += 2; } // multiple id3v2.4 strings should be separated with a single NULL byte; // some implementations might terminate the string AND use a NULL // separator if (textencoding == TE_LATIN1 || textencoding == TE_UTF8) { if ((frame_ptr + offset_into_frame)[0] == 0) offset_into_frame += 1; } else if (textencoding == TE_UTF16LE_WITH_BOM) { if ((frame_ptr + offset_into_frame)[0] == 0 && (frame_ptr + offset_into_frame)[1] == 0) offset_into_frame += 2; } // a 3rd NULL would not be good if (textencoding == TE_LATIN1 || textencoding == TE_UTF8) { if ((frame_ptr + offset_into_frame)[0] == 0) break; } else if (textencoding == TE_UTF16LE_WITH_BOM) { if ((frame_ptr + offset_into_frame)[0] == 0 && (frame_ptr + offset_into_frame)[1] == 0) break; } ID3v2Fields *last_textfield = APar_FindLastTextField(targetframe); if (APar_ExtraTextFieldInit( last_textfield, frameLen - offset_into_frame, textencoding)) { offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, last_textfield->next_field, ID3_TEXT_FIELD, textencoding); targetframe->textfield_tally++; } // copy the string to the new field break; } } break; } case ID3_URL_FRAME: { APar_ExtractField(frame_ptr, frameLen, targetframe, targetframe->ID3v2_Frame_Fields, ID3_URL_FIELD, 0); break; } case ID3_TEXT_FRAME_USERDEF: case ID3_URL_FRAME_USERDEF: { offset_into_frame += APar_ExtractField(frame_ptr, 1, targetframe, targetframe->ID3v2_Frame_Fields, ID3_TEXT_ENCODING_FIELD, 0); offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 1, ID3_DESCRIPTION_FIELD, targetframe->ID3v2_Frame_Fields->field_string[0]); offset_into_frame += skipNULLterm(frame_ptr + offset_into_frame, targetframe->ID3v2_Frame_Fields->field_string[0], frameLen - offset_into_frame); if (targetframe->ID3v2_FrameType == ID3_TEXT_FRAME_USERDEF) { APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 2, ID3_TEXT_FIELD, targetframe->ID3v2_Frame_Fields->field_string[0]); } else if (targetframe->ID3v2_FrameType == ID3_URL_FRAME_USERDEF) { APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 2, ID3_URL_FIELD, targetframe->ID3v2_Frame_Fields->field_string[0]); } break; } case ID3_UNIQUE_FILE_ID_FRAME: { offset_into_frame += APar_ExtractField(frame_ptr, frameLen, targetframe, targetframe->ID3v2_Frame_Fields, ID3_OWNER_FIELD, 0); offset_into_frame++; // iso-8859-1 owner field is NULL terminated APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 1, ID3_BINARY_DATA_FIELD, 0); break; } case ID3_CD_ID_FRAME: { APar_ExtractField(frame_ptr, frameLen, targetframe, targetframe->ID3v2_Frame_Fields, ID3_BINARY_DATA_FIELD, 0); break; } case ID3_DESCRIBED_TEXT_FRAME: { offset_into_frame += APar_ExtractField(frame_ptr, 1, targetframe, targetframe->ID3v2_Frame_Fields, ID3_TEXT_ENCODING_FIELD, 0); offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, 3, targetframe, targetframe->ID3v2_Frame_Fields + 1, ID3_LANGUAGE_FIELD, 0); offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 2, ID3_DESCRIPTION_FIELD, targetframe->ID3v2_Frame_Fields->field_string[0]); offset_into_frame += skipNULLterm(frame_ptr + offset_into_frame, targetframe->ID3v2_Frame_Fields->field_string[0], frameLen - offset_into_frame); if (frameLen > offset_into_frame) { APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 3, ID3_TEXT_FIELD, targetframe->ID3v2_Frame_Fields->field_string[0]); } break; } case ID3_ATTACHED_OBJECT_FRAME: case ID3_ATTACHED_PICTURE_FRAME: { offset_into_frame += APar_ExtractField(frame_ptr, 1, targetframe, targetframe->ID3v2_Frame_Fields, ID3_TEXT_ENCODING_FIELD, 0); offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, frameLen - 1, targetframe, targetframe->ID3v2_Frame_Fields + 1, ID3_MIME_TYPE_FIELD, 0); offset_into_frame += 1; // should only be 1 NULL if (targetframe->ID3v2_FrameType == ID3_ATTACHED_PICTURE_FRAME) { offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, 1, targetframe, targetframe->ID3v2_Frame_Fields + 2, ID3_PIC_TYPE_FIELD, 0); } else { offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 2, ID3_FILENAME_FIELD, 0); offset_into_frame += skipNULLterm(frame_ptr + offset_into_frame, targetframe->ID3v2_Frame_Fields->field_string[0], frameLen - offset_into_frame); } offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 3, ID3_DESCRIPTION_FIELD, targetframe->ID3v2_Frame_Fields->field_string[0]); offset_into_frame += skipNULLterm(frame_ptr + offset_into_frame, targetframe->ID3v2_Frame_Fields->field_string[0], frameLen - offset_into_frame); if (frameLen > offset_into_frame) { offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 4, ID3_BINARY_DATA_FIELD, 0); } break; } case ID3_PRIVATE_FRAME: { // the only difference between the 'priv' frame & // the 'ufid' frame is ufid is limited to 64 bytes offset_into_frame += APar_ExtractField(frame_ptr, frameLen, targetframe, targetframe->ID3v2_Frame_Fields, ID3_OWNER_FIELD, 0); offset_into_frame++; // iso-8859-1 owner field is NULL terminated APar_ExtractField(frame_ptr + offset_into_frame, frameLen - 1, targetframe, targetframe->ID3v2_Frame_Fields + 1, ID3_BINARY_DATA_FIELD, 0); break; } case ID3_GROUP_ID_FRAME: { offset_into_frame += APar_ExtractField(frame_ptr, frameLen, targetframe, targetframe->ID3v2_Frame_Fields, ID3_OWNER_FIELD, 0); offset_into_frame++; // iso-8859-1 owner field is NULL terminated offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, 1, targetframe, targetframe->ID3v2_Frame_Fields + 1, ID3_GROUPSYMBOL_FIELD, 0); if (frameLen > offset_into_frame) { APar_ExtractField(frame_ptr + offset_into_frame, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 2, ID3_BINARY_DATA_FIELD, 0); } break; } case ID3_SIGNATURE_FRAME: { APar_ExtractField(frame_ptr, 1, targetframe, targetframe->ID3v2_Frame_Fields, ID3_GROUPSYMBOL_FIELD, 0); APar_ExtractField(frame_ptr + 1, frameLen - 1, targetframe, targetframe->ID3v2_Frame_Fields + 1, ID3_BINARY_DATA_FIELD, 0); break; } case ID3_PLAYCOUNTER_FRAME: { APar_ExtractField(frame_ptr, frameLen, targetframe, targetframe->ID3v2_Frame_Fields, ID3_COUNTER_FIELD, 0); break; } case ID3_POPULAR_FRAME: { offset_into_frame += APar_ExtractField(frame_ptr, frameLen, targetframe, targetframe->ID3v2_Frame_Fields, ID3_OWNER_FIELD, 0); // surrogate for 'emai to user' field offset_into_frame++; // iso-8859-1 email address field is NULL terminated offset_into_frame += APar_ExtractField(frame_ptr + offset_into_frame, 1, targetframe, targetframe->ID3v2_Frame_Fields + 1, ID3_BINARY_DATA_FIELD, 0); if (frameLen > offset_into_frame) { APar_ExtractField(frame_ptr, frameLen - offset_into_frame, targetframe, targetframe->ID3v2_Frame_Fields + 2, ID3_COUNTER_FIELD, 0); } break; } case ID3_OLD_V2P2_PICTURE_FRAME: { break; // unimplemented } } return; } void APar_ID32_ScanID3Tag(FILE *source_file, AtomicInfo *id32_atom) { char *id32_fulltag = (char *)calloc(1, sizeof(char) * id32_atom->AtomicLength); char *fulltag_ptr = id32_fulltag; if (id32_atom->AtomicLength < 20) return; APar_readX(id32_fulltag, source_file, id32_atom->AtomicStart + 14, id32_atom->AtomicLength - 14); //+10 = 4bytes ID32 atom length + 4bytes ID32 atom name + // 2 bytes packed lang if (memcmp(id32_fulltag, "ID3", 3) != 0) return; fulltag_ptr += 3; id32_atom->ID32_TagInfo = (ID3v2Tag *)calloc(1, sizeof(ID3v2Tag)); id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion = *fulltag_ptr; fulltag_ptr++; id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion = *fulltag_ptr; fulltag_ptr++; id32_atom->ID32_TagInfo->ID3v2Tag_Flags = *fulltag_ptr; fulltag_ptr++; if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion != 4) { fprintf(stdout, "AtomicParsley warning: an ID32 atom was encountered using an " "unsupported ID3v2 tag version: %u. Skipping\n", id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion); return; } if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 4 && id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion != 0) { fprintf(stdout, "AtomicParsley warning: an ID32 atom was encountered using an " "unsupported ID3v2.4 tag revision: %u. Skipping\n", id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion); return; } if (ID3v2_TestTagFlag(id32_atom->ID32_TagInfo->ID3v2Tag_Flags, ID32_TAGFLAG_BIT0 + ID32_TAGFLAG_BIT1 + ID32_TAGFLAG_BIT2 + ID32_TAGFLAG_BIT3)) return; if (ID3v2_TestTagFlag(id32_atom->ID32_TagInfo->ID3v2Tag_Flags, ID32_TAGFLAG_FOOTER)) { fprintf(stdout, "AtomicParsley error: an ID32 atom was encountered with a " "forbidden footer flag. Exiting.\n"); free(id32_fulltag); id32_fulltag = NULL; return; } if (ID3v2_TestTagFlag(id32_atom->ID32_TagInfo->ID3v2Tag_Flags, ID32_TAGFLAG_EXPERIMENTAL)) { #if defined(DEBUG_V) fprintf(stdout, "AtomicParsley warning: an ID32 atom was encountered with " "an experimental flag set.\n"); #endif } if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 4) { id32_atom->ID32_TagInfo->ID3v2Tag_Length = syncsafe32_to_UInt32(fulltag_ptr); fulltag_ptr += 4; } else if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 3) { id32_atom->ID32_TagInfo->ID3v2Tag_Length = UInt32FromBigEndian( fulltag_ptr); // TODO: when testing ends, this switches to syncsafe fulltag_ptr += 4; } else if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 2) { id32_atom->ID32_TagInfo->ID3v2Tag_Length = UInt24FromBigEndian(fulltag_ptr); fulltag_ptr += 3; } if (ID3v2_TestTagFlag(id32_atom->ID32_TagInfo->ID3v2Tag_Flags, ID32_TAGFLAG_UNSYNCRONIZATION)) { // uint32_t newtagsize = ID3v2_desynchronize(id32_fulltag, // id32_atom->ID32_TagInfo->ID3v2Tag_Length); fprintf(stdout, "New tag size // is %" PRIu32 "\n", newtagsize); WriteZlibData(id32_fulltag, newtagsize); // exit(0); fprintf(stdout, "AtomicParsley error: an ID3 tag with the unsynchronized " "flag set which is not supported. Skipping.\n"); free(id32_fulltag); id32_fulltag = NULL; return; } if (ID3v2_TestTagFlag(id32_atom->ID32_TagInfo->ID3v2Tag_Flags, ID32_TAGFLAG_EXTENDEDHEADER)) { if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 4) { id32_atom->ID32_TagInfo->ID3v2_Tag_ExtendedHeader_Length = syncsafe32_to_UInt32(fulltag_ptr); } else { id32_atom->ID32_TagInfo->ID3v2_Tag_ExtendedHeader_Length = UInt32FromBigEndian( fulltag_ptr); // TODO: when testing ends, this switches to // syncsafe; 2.2 doesn't have it } fulltag_ptr += id32_atom->ID32_TagInfo->ID3v2_Tag_ExtendedHeader_Length; } id32_atom->ID32_TagInfo->ID3v2_FirstFrame = NULL; id32_atom->ID32_TagInfo->ID3v2_FrameList = NULL; // loop through parsing frames while (fulltag_ptr < id32_fulltag + (id32_atom->AtomicLength - 14)) { uint32_t fullframesize = 0; if (ID3v2_PaddingTest(fulltag_ptr)) break; if (ID3v2_TestFrameID_NonConformance(fulltag_ptr)) break; ID3v2Frame *target_list_frameinfo = (ID3v2Frame *)calloc(1, sizeof(ID3v2Frame)); target_list_frameinfo->ID3v2_NextFrame = NULL; target_list_frameinfo->ID3v2_Frame_ID = MatchID3FrameIDstr( fulltag_ptr, id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion); target_list_frameinfo->ID3v2_FrameType = KnownFrames[target_list_frameinfo->ID3v2_Frame_ID + 1].ID3v2_FrameType; uint8_t FrameCompositionList = GetFrameCompositionDescription(target_list_frameinfo->ID3v2_FrameType); target_list_frameinfo->ID3v2_FieldCount = FrameTypeConstructionList[FrameCompositionList].ID3_FieldCount; target_list_frameinfo->ID3v2_Frame_ExpandedLength = 0; target_list_frameinfo->textfield_tally = 0; target_list_frameinfo->eliminate_frame = false; uint8_t frame_offset = 0; if (id32_atom->ID32_TagInfo->ID3v2_FrameList != NULL) id32_atom->ID32_TagInfo->ID3v2_FrameList->ID3v2_NextFrame = target_list_frameinfo; // need to lookup how many components this Frame_ID is associated with. Do // this by using the corresponding KnownFrames.ID3v2_FrameType // ID3v2_FrameType describes the general form this frame takes (text, text // with description, attached object, attached picture) the general form is // composed of several fields; that number of fields needs to be malloced to // target_list_frameinfo->ID3v2_Frame_Fields and each // target_list_frameinfo->ID3v2_Frame_Fields+num->field_string needs to be // malloced and copied from id32_fulltag memset(target_list_frameinfo->ID3v2_Frame_Namestr, 0, 5); if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 2) { memcpy(target_list_frameinfo->ID3v2_Frame_Namestr, fulltag_ptr, 3); fulltag_ptr += 3; } else { memcpy(target_list_frameinfo->ID3v2_Frame_Namestr, fulltag_ptr, 4); fulltag_ptr += 4; } if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 4) { target_list_frameinfo->ID3v2_Frame_Length = syncsafe32_to_UInt32(fulltag_ptr); fulltag_ptr += 4; } else if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 3) { target_list_frameinfo->ID3v2_Frame_Length = UInt32FromBigEndian( fulltag_ptr); // TODO: when testing ends, this switches to syncsafe fulltag_ptr += 4; } else if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 2) { target_list_frameinfo->ID3v2_Frame_Length = UInt24FromBigEndian(fulltag_ptr); fulltag_ptr += 3; } if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion >= 3) { target_list_frameinfo->ID3v2_Frame_Flags = UInt16FromBigEndian( fulltag_ptr); // v2.2 doesn't have frame level flags (but it does have // field level flags) fulltag_ptr += 2; if (ID3v2_TestFrameFlag(target_list_frameinfo->ID3v2_Frame_Flags, ID32_FRAMEFLAG_UNSYNCED)) { // DE-UNSYNC frame fullframesize = target_list_frameinfo->ID3v2_Frame_Length; target_list_frameinfo->ID3v2_Frame_Length = ID3v2_desynchronize(fulltag_ptr + frame_offset, target_list_frameinfo->ID3v2_Frame_Length); target_list_frameinfo->ID3v2_Frame_Flags -= ID32_FRAMEFLAG_UNSYNCED; } // info based on frame flags (order based on the order of flags defined by // the frame flags if (ID3v2_TestFrameFlag(target_list_frameinfo->ID3v2_Frame_Flags, ID32_FRAMEFLAG_GROUPING)) { #if defined(DEBUG_V) fprintf(stdout, "Frame %s has a grouping flag set\n", target_list_frameinfo->ID3v2_Frame_Namestr); #endif target_list_frameinfo->ID3v2_Frame_GroupingSymbol = *fulltag_ptr; // er, uh... wouldn't this also require // ID32_FRAMEFLAG_LENINDICATED to be set??? frame_offset++; } if (ID3v2_TestFrameFlag( target_list_frameinfo->ID3v2_Frame_Flags, ID32_FRAMEFLAG_COMPRESSED)) { // technically // ID32_FRAMEFLAG_LENINDICATED // should also be tested #if defined(DEBUG_V) fprintf(stdout, "Frame %s has a compressed flag set\n", target_list_frameinfo->ID3v2_Frame_Namestr); #endif if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 4) { target_list_frameinfo->ID3v2_Frame_ExpandedLength = syncsafe32_to_UInt32(fulltag_ptr + frame_offset); frame_offset += 4; } else if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 3) { target_list_frameinfo ->ID3v2_Frame_ExpandedLength = UInt32FromBigEndian( fulltag_ptr + frame_offset); // TODO: when testing ends, switch this to syncsafe frame_offset += 4; } } } target_list_frameinfo->ID3v2_Frame_Fields = (ID3v2Fields *)calloc( 1, sizeof(ID3v2Fields) * target_list_frameinfo->ID3v2_FieldCount); char *expanded_frame = NULL; char *frame_ptr = NULL; uint32_t frameLen = 0; if (target_list_frameinfo->ID3v2_Frame_ExpandedLength != 0) { #ifdef HAVE_ZLIB_H expanded_frame = (char *)calloc( 1, sizeof(char) * target_list_frameinfo->ID3v2_Frame_ExpandedLength + 1); APar_zlib_inflate(fulltag_ptr + frame_offset, target_list_frameinfo->ID3v2_Frame_Length, expanded_frame, target_list_frameinfo->ID3v2_Frame_ExpandedLength); WriteZlibData(expanded_frame, target_list_frameinfo->ID3v2_Frame_ExpandedLength); frame_ptr = expanded_frame; frameLen = target_list_frameinfo->ID3v2_Frame_ExpandedLength; #else target_list_frameinfo->ID3v2_FrameType = ID3_UNKNOWN_FRAME; frame_ptr = fulltag_ptr + frame_offset; frameLen = target_list_frameinfo->ID3v2_Frame_ExpandedLength; #endif } else { frame_ptr = fulltag_ptr + frame_offset; frameLen = target_list_frameinfo->ID3v2_Frame_Length; } APar_ScanID3Frame(target_list_frameinfo, frame_ptr, frameLen); if (expanded_frame != NULL) { free(expanded_frame); expanded_frame = NULL; } if (target_list_frameinfo != NULL) { if (id32_atom->ID32_TagInfo->ID3v2_FrameCount == 0) { id32_atom->ID32_TagInfo->ID3v2_FirstFrame = target_list_frameinfo; // entrance to the linked list } id32_atom->ID32_TagInfo->ID3v2_FrameList = target_list_frameinfo; // this always points to the last frame that // had the scan completed } if (fullframesize != 0) { fulltag_ptr += fullframesize; } else { fulltag_ptr += target_list_frameinfo->ID3v2_Frame_Length; } if (ID3v2_TestFrameFlag(target_list_frameinfo->ID3v2_Frame_Flags, ID32_FRAMEFLAG_GROUPING)) { fulltag_ptr++; } id32_atom->ID32_TagInfo->ID3v2_FrameCount++; } id32_atom->ID32_TagInfo->modified_tag = false; // if a frame is altered/added/removed, change this to true and // render the tag & fill id32_atom-AtomicData with the tag return; } /////////////////////////////////////////////////////////////////////////////////////// // id3 rendering functions // /////////////////////////////////////////////////////////////////////////////////////// bool APar_LocateFrameSymbol(AtomicInfo *id32_atom, ID3v2Frame *targetFrame, uint8_t groupsymbol) { ID3v2Frame *testFrame = id32_atom->ID32_TagInfo->ID3v2_FirstFrame; while (testFrame != NULL) { if (targetFrame->ID3v2_Frame_ID == ID3v2_FRAME_GRID && testFrame->ID3v2_Frame_ID != ID3v2_FRAME_GRID) { if (testFrame->ID3v2_Frame_GroupingSymbol == groupsymbol) { return true; } } else if (targetFrame->ID3v2_Frame_ID != ID3v2_FRAME_GRID) { if (testFrame->ID3v2_Frame_ID == ID3v2_FRAME_GRID && groupsymbol == (uint8_t)(testFrame->ID3v2_Frame_Fields + 1)->field_string[0]) { return true; } } testFrame = testFrame->ID3v2_NextFrame; } return false; } void APar_FrameFilter(AtomicInfo *id32_atom) { ID3v2Frame *MCDI_frame = NULL; ID3v2Frame *TRCK_frame = NULL; ID3v2Frame *thisFrame = id32_atom->ID32_TagInfo->ID3v2_FirstFrame; while (thisFrame != NULL) { if (!thisFrame->eliminate_frame) { if (thisFrame->ID3v2_FrameType == ID3_CD_ID_FRAME) { MCDI_frame = thisFrame; } if (thisFrame->ID3v2_Frame_ID == ID3v2_FRAME_TRACKNUM) { TRCK_frame = thisFrame; } if (thisFrame->ID3v2_Frame_ID == ID3v2_FRAME_GRID) { // find any frames containing this symbol; if none // are present this frame will be discarded thisFrame->eliminate_frame = !APar_LocateFrameSymbol( id32_atom, thisFrame, (uint8_t)(thisFrame->ID3v2_Frame_Fields + 1)->field_string[0]); if (!thisFrame->eliminate_frame) { thisFrame->ID3v2_Frame_Flags |= ID32_FRAMEFLAG_GROUPING; } } else if (thisFrame->ID3v2_Frame_ID == ID3v2_FRAME_SIGNATURE) { // find a GRID frame that contains // this symbol (@ field_string, not // ID3v2_Frame_GroupingSymbol) thisFrame->eliminate_frame = !APar_LocateFrameSymbol( id32_atom, thisFrame, (uint8_t)thisFrame->ID3v2_Frame_Fields->field_string[0]); // since the group symbol is carried as a field for SIGN, no need to set // the frame's grouping bit in the frame flags } else if (thisFrame->ID3v2_Frame_GroupingSymbol > 0) { // find a GRID frame that contains this symbol, otherwise // discard it thisFrame->eliminate_frame = !APar_LocateFrameSymbol( id32_atom, thisFrame, thisFrame->ID3v2_Frame_GroupingSymbol); if (!thisFrame->eliminate_frame) { thisFrame->ID3v2_Frame_Flags |= ID32_FRAMEFLAG_GROUPING; } } } thisFrame = thisFrame->ID3v2_NextFrame; } if (MCDI_frame != NULL && TRCK_frame == NULL) { fprintf( stderr, "AP warning: the MCDI frame was skipped due to a missing TRCK frame\n"); MCDI_frame->eliminate_frame = true; } return; } uint32_t APar_GetTagSize( AtomicInfo *id32_atom) { // a rough approximation of how much to malloc; this will // be larger than will be ultimately required uint32_t tag_len = 0; uint16_t surviving_frame_count = 0; if (id32_atom->ID32_TagInfo->modified_tag == false) return tag_len; if (id32_atom->ID32_TagInfo->ID3v2_FrameCount == 0) return tag_len; // but a frame isn't removed by AP; its just marked for // elimination if (id32_atom->ID32_TagInfo->ID3v2_FrameList == NULL) return tag_len; // something went wrong somewhere if this wasn't an entry to // a linked list of frames if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion != 4) return tag_len; // only id3 version 2.4 tags are written ID3v2Frame *eval_frame = id32_atom->ID32_TagInfo->ID3v2_FirstFrame; while (eval_frame != NULL) { if (eval_frame->eliminate_frame == true) { eval_frame = eval_frame->ID3v2_NextFrame; continue; } tag_len += 15; // 4bytes frameID 'TCON', 4bytes frame length (syncsafe int), // 2 bytes frame flags; optional group symbol: 1byte + // decompressed length 4bytes tag_len += 2 * eval_frame->ID3v2_FieldCount; // excess amount to ensure that text // fields have utf16 BOMs & 2 byte // NULL terminations as required if (ID3v2_TestFrameFlag(eval_frame->ID3v2_Frame_Flags, ID32_FRAMEFLAG_COMPRESSED)) { tag_len += eval_frame->ID3v2_Frame_ExpandedLength; } else { tag_len += eval_frame->ID3v2_Frame_Length; } surviving_frame_count++; eval_frame = eval_frame->ID3v2_NextFrame; if (surviving_frame_count == 0 && eval_frame == NULL) { } } if (surviving_frame_count == 0) return 0; // the 'ID3' header alone isn't going to be written with 0 // existing frames return tag_len; } void APar_RenderFields(char *dest_buffer, uint32_t max_alloc, ID3v2Tag *id3_tag, ID3v2Frame *id3v2_frame, uint32_t *frame_header_len, uint32_t *frame_length) { uint8_t encoding_val = 0; if (id3v2_frame->ID3v2_Frame_Fields == NULL) { *frame_header_len = 0; *frame_length = 0; return; } for (uint8_t fld_idx = 0; fld_idx < id3v2_frame->ID3v2_FieldCount; fld_idx++) { ID3v2Fields *this_field = id3v2_frame->ID3v2_Frame_Fields + fld_idx; // fprintf(stdout, "Total Fields for %s: %u (this is %u, %u)\n", // id3v2_frame->ID3v2_Frame_Namestr, id3v2_frame->ID3v2_FieldCount, fld_idx, // this_field->ID3v2_Field_Type); switch (this_field->ID3v2_Field_Type) { // these are raw data fields of variable/fixed length and are not NULL // terminated case ID3_UNKNOWN_FIELD: case ID3_PIC_TYPE_FIELD: case ID3_GROUPSYMBOL_FIELD: case ID3_TEXT_ENCODING_FIELD: case ID3_LANGUAGE_FIELD: case ID3_COUNTER_FIELD: case ID3_IMAGEFORMAT_FIELD: case ID3_URL_FIELD: case ID3_BINARY_DATA_FIELD: { APar_LimitBufferRange(max_alloc, *frame_header_len + *frame_length); if (this_field->field_string != NULL) { memcpy(dest_buffer + *frame_length, this_field->field_string, this_field->field_length); *frame_length += this_field->field_length; // fprintf(stdout, "Field idx %u(%d) is now %" PRIu32 " bytes long (+%" // PRIu32 ")\n", fld_idx, this_field->ID3v2_Field_Type, *frame_length, // this_field->field_length); } break; } // these fields are subject to NULL byte termination - based on what the // text encoding field says the encoding of this string is case ID3_TEXT_FIELD: case ID3_FILENAME_FIELD: case ID3_DESCRIPTION_FIELD: { if (this_field->field_string == NULL) { *frame_header_len = 0; *frame_length = 0; return; } else { APar_LimitBufferRange(max_alloc, *frame_header_len + *frame_length + 2); //+2 for a possible extra NULLs encoding_val = id3v2_frame->ID3v2_Frame_Fields ->field_string[0]; // ID3_TEXT_ENCODING_FIELD is always the // first field, and should have an encoding if ((id3_tag->ID3v2Tag_MajorVersion == 4 && encoding_val == TE_UTF8) || encoding_val == TE_LATIN1) { if (this_field->ID3v2_Field_Type != ID3_TEXT_FIELD) APar_ValidateNULLTermination8bit(this_field); memcpy(dest_buffer + *frame_length, this_field->field_string, this_field->field_length); *frame_length += this_field->field_length; } else if ((id3_tag->ID3v2Tag_MajorVersion == 4 && encoding_val == TE_UTF16LE_WITH_BOM) || encoding_val == TE_UTF16BE_NO_BOM) { APar_ValidateNULLTermination16bit( this_field, encoding_val); // TODO: shouldn't this also exclude // ID3_TEXT_FIELDs? memcpy(dest_buffer + *frame_length, this_field->field_string, this_field->field_length); *frame_length += this_field->field_length; } else { // well, AP didn't set this frame, so just duplicate it. memcpy(dest_buffer + *frame_length, this_field->field_string, this_field->field_length); *frame_length += this_field->field_length; } } // fprintf(stdout, "Field idx %u(%d) is now %" PRIu32 " bytes long\n", // fld_idx, this_field->ID3v2_Field_Type, *frame_length); break; } // these are iso 8859-1 encoded with a single NULL terminator // a 'LINK' url would also come here and be seperately enumerated (because // it has a terminating NULL); but in 3gp assets, external references aren't // allowed an 'OWNE'/'COMR' price field would also be here because of single // byte NULL termination case ID3_OWNER_FIELD: case ID3_MIME_TYPE_FIELD: { if (this_field->field_string == NULL) { *frame_header_len = 0; *frame_length = 0; return; } else { APar_LimitBufferRange(max_alloc, *frame_header_len + *frame_length + 1); //+2 for a possible extra NULLs APar_ValidateNULLTermination8bit(this_field); memcpy(dest_buffer + *frame_length, this_field->field_string, this_field->field_length); *frame_length += this_field->field_length; } // fprintf(stdout, "Field idx %u(%d) is now %" PRIu32 " bytes long\n", // fld_idx, this_field->ID3v2_Field_Type, *frame_length); break; } default: { // fprintf(stdout, "I was unable to determine the field class. I was // provided with %u (i.e. text field: %u, text encoding: %u\n", // this_field->ID3v2_Field_Type, ID3_TEXT_FIELD, ID3_TEXT_ENCODING_FIELD); break; } } // end switch } if (id3v2_frame->ID3v2_FrameType == ID3_TEXT_FRAME && id3v2_frame->textfield_tally > 1 && id3_tag->ID3v2Tag_MajorVersion == 4) { ID3v2Fields *extra_textfield = (id3v2_frame->ID3v2_Frame_Fields + 1)->next_field; while (true) { if (extra_textfield == NULL) break; if (encoding_val == TE_UTF8 || encoding_val == TE_LATIN1) { *frame_length += 1; } else if (encoding_val == TE_UTF16LE_WITH_BOM || encoding_val == TE_UTF16BE_NO_BOM) { *frame_length += 2; } memcpy(dest_buffer + *frame_length, extra_textfield->field_string, extra_textfield->field_length); *frame_length += extra_textfield->field_length; extra_textfield = extra_textfield->next_field; } } return; } uint32_t APar_Render_ID32_Tag(AtomicInfo *id32_atom, uint32_t max_alloc) { bool contains_rendered_frames = false; APar_FrameFilter(id32_atom); UInt16_TO_String2( id32_atom->AtomicLanguage, id32_atom->AtomicData); // parsedAtoms[atom_idx].AtomicLanguage uint64_t tag_offset = 2; // those first 2 bytes will hold the language uint32_t frame_length, frame_header_len; // the length in bytes this frame // consumes in AtomicData as rendered uint64_t frame_length_pos, frame_compressed_length_pos; memcpy(id32_atom->AtomicData + tag_offset, "ID3", 3); tag_offset += 3; id32_atom->AtomicData[tag_offset] = id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion; // should be 4 id32_atom->AtomicData[tag_offset + 1] = id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion; // should be 0 id32_atom->AtomicData[tag_offset + 2] = id32_atom->ID32_TagInfo->ID3v2Tag_Flags; tag_offset += 3; // unknown full length; fill in later if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 3 || id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 4) { tag_offset += 4; if (ID3v2_TestTagFlag( id32_atom->ID32_TagInfo->ID3v2Tag_Flags, ID32_TAGFLAG_EXTENDEDHEADER)) { // currently unimplemented tag_offset += 10; } } else if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 2) { tag_offset += 3; } id32_atom->ID32_TagInfo->ID3v2Tag_Length = tag_offset - 2; ID3v2Frame *thisframe = id32_atom->ID32_TagInfo->ID3v2_FirstFrame; while (thisframe != NULL) { frame_header_len = 0; frame_length_pos = 0; frame_compressed_length_pos = 0; if (thisframe->eliminate_frame == true) { thisframe = thisframe->ID3v2_NextFrame; continue; } contains_rendered_frames = true; // this won't be able to convert from 1 tag version to another because it // doesn't look up the frame id strings for the change if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 3 || id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 4) { memcpy(id32_atom->AtomicData + tag_offset, thisframe->ID3v2_Frame_Namestr, 4); frame_header_len += 4; // the frame length won't be determined until the end of rendering this // frame fully; for now just remember where its supposed to be: frame_length_pos = tag_offset + frame_header_len; frame_header_len += 4; } else if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 2) { memcpy(id32_atom->AtomicData + tag_offset, thisframe->ID3v2_Frame_Namestr, 3); frame_header_len += 3; // the frame length won't be determined until the end of rendering this // frame fully; for now just remember where its supposed to be: frame_length_pos = tag_offset + frame_header_len; frame_header_len += 3; } // render frame flags //TODO: compression & group symbol are the only ones // that can possibly be set here if (id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 3 || id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion == 4) { UInt16_TO_String2(thisframe->ID3v2_Frame_Flags, id32_atom->AtomicData + tag_offset + frame_header_len); frame_header_len += 2; } // grouping flag? 1 byte; technically, its outside the header and before the // fields begin if (ID3v2_TestFrameFlag(thisframe->ID3v2_Frame_Flags, ID32_FRAMEFLAG_GROUPING)) { id32_atom->AtomicData[tag_offset + frame_header_len] = thisframe->ID3v2_Frame_GroupingSymbol; frame_header_len++; } // compression flag? 4bytes; technically, its outside the header and before // the fields begin if (ID3v2_TestFrameFlag(thisframe->ID3v2_Frame_Flags, ID32_FRAMEFLAG_COMPRESSED)) { frame_compressed_length_pos = tag_offset + frame_header_len; // fill in later; remember where it is // supposed to go frame_header_len += 4; } frame_length = 0; APar_RenderFields(id32_atom->AtomicData + tag_offset + frame_header_len, max_alloc - tag_offset, id32_atom->ID32_TagInfo, thisframe, &frame_header_len, &frame_length); #if defined HAVE_ZLIB_H // and now that we have rendered the frame, its time to turn to compression, // if set if (ID3v2_TestFrameFlag(thisframe->ID3v2_Frame_Flags, ID32_FRAMEFLAG_COMPRESSED)) { uint32_t compressed_len = 0; char *compress_buffer = (char *)calloc(1, sizeof(char) * frame_length + 20); compressed_len = APar_zlib_deflate(id32_atom->AtomicData + tag_offset + frame_header_len, frame_length, compress_buffer, frame_length + 20); if (compressed_len > 0) { memcpy(id32_atom->AtomicData + tag_offset + frame_header_len, compress_buffer, compressed_len + 1); convert_to_syncsafe32( frame_length, id32_atom->AtomicData + frame_compressed_length_pos); frame_length = compressed_len; // WriteZlibData(id32_atom->AtomicData + tag_offset+frame_header_len, // compressed_len); } } #endif convert_to_syncsafe32(frame_length, id32_atom->AtomicData + frame_length_pos); tag_offset += frame_header_len + frame_length; // advance id32_atom->ID32_TagInfo->ID3v2Tag_Length += frame_header_len + frame_length; thisframe = thisframe->ID3v2_NextFrame; } convert_to_syncsafe32(id32_atom->ID32_TagInfo->ID3v2Tag_Length - 10, id32_atom->AtomicData + 8); //-10 for a v2.4 tag with no extended header if (!contains_rendered_frames) id32_atom->ID32_TagInfo->ID3v2Tag_Length = 0; return id32_atom->ID32_TagInfo->ID3v2Tag_Length; } /////////////////////////////////////////////////////////////////////////////////////// // id3 initializing functions // /////////////////////////////////////////////////////////////////////////////////////// void APar_FieldInit(ID3v2Frame *aFrame, uint8_t a_field, uint8_t frame_comp_list, const char *frame_payload) { uint32_t byte_allocation = 0; ID3v2Fields *this_field = NULL; int field_type = FrameTypeConstructionList[frame_comp_list].ID3_FieldComponents[a_field]; switch (field_type) { // case ID3_UNKNOWN_FIELD will not be handled // these are all 1 to less than 16 bytes. case ID3_GROUPSYMBOL_FIELD: case ID3_COUNTER_FIELD: case ID3_PIC_TYPE_FIELD: case ID3_LANGUAGE_FIELD: case ID3_IMAGEFORMAT_FIELD: // PIC in v2.2 case ID3_TEXT_ENCODING_FIELD: { byte_allocation = 16; break; } // between 16 & 100 bytes. case ID3_MIME_TYPE_FIELD: { byte_allocation = 100; break; } // these are allocated with 2000 bytes case ID3_FILENAME_FIELD: case ID3_OWNER_FIELD: case ID3_DESCRIPTION_FIELD: case ID3_URL_FIELD: case ID3_TEXT_FIELD: { uint32_t string_len = strlen(frame_payload) + 1; if (string_len * 2 > 2000) { byte_allocation = string_len * 2; } else { byte_allocation = 2000; } break; } case ID3_BINARY_DATA_FIELD: { if (aFrame->ID3v2_Frame_ID == ID3v2_EMBEDDED_PICTURE || aFrame->ID3v2_Frame_ID == ID3v2_EMBEDDED_OBJECT) { // this will be left NULL because it would would probably have to be // realloced, so just do it later to the right size //byte_allocation = // findFileSize(frame_payload) + 1; //this should be limited to // max_sync_safe_uint28_t } else { byte_allocation = 2000; } break; } // default : { // fprintf(stdout, "I am %d\n", // FrameTypeConstructionList[frame_comp_list].ID3_FieldComponents[a_field]); // break; //} } this_field = aFrame->ID3v2_Frame_Fields + a_field; this_field->ID3v2_Field_Type = field_type; if (byte_allocation > 0) { this_field->field_string = (char *)calloc(1, sizeof(char *) * byte_allocation); if (!APar_assert((this_field->field_string != NULL), 11, aFrame->ID3v2_Frame_Namestr)) exit(11); } else { this_field->field_string = NULL; } this_field->field_length = 0; this_field->alloc_length = byte_allocation; this_field->next_field = NULL; // fprintf(stdout, "For %u field, %" PRIu32 " bytes were allocated.\n", // this_field->ID3v2_Field_Type, byte_allocation); return; } void APar_FrameInit(ID3v2Frame *aFrame, const char *frame_str, int frameID, uint8_t frame_comp_list, const char *frame_payload) { aFrame->ID3v2_FieldCount = FrameTypeConstructionList[frame_comp_list].ID3_FieldCount; if (aFrame->ID3v2_FieldCount > 0) { aFrame->ID3v2_Frame_Fields = (ID3v2Fields *)calloc( 1, sizeof(ID3v2Fields) * aFrame->ID3v2_FieldCount); aFrame->ID3v2_Frame_ID = frameID; aFrame->ID3v2_FrameType = FrameTypeConstructionList[frame_comp_list].ID3_FrameType; aFrame->ID3v2_Frame_ExpandedLength = 0; aFrame->ID3v2_Frame_GroupingSymbol = 0; aFrame->ID3v2_Frame_Flags = 0; aFrame->ID3v2_Frame_Length = 0; aFrame->textfield_tally = 0; aFrame->eliminate_frame = false; memcpy(aFrame->ID3v2_Frame_Namestr, frame_str, 5); for (uint8_t fld = 0; fld < aFrame->ID3v2_FieldCount; fld++) { APar_FieldInit(aFrame, fld, frame_comp_list, frame_payload); } // fprintf(stdout, "(%u = %d) Type %d\n", frameID, // KnownFrames[frameID+1].ID3v2_InternalFrameID, aFrame->ID3v2_FrameType); } // fprintf(stdout, "Retrieved frame for '%s': %s (%u fields)\n", frame_str, // KnownFrames[frameID].ID3V2p4_FrameID, aFrame->ID3v2_FieldCount); return; } void APar_ID3Tag_Init(AtomicInfo *id32_atom) { id32_atom->ID32_TagInfo = (ID3v2Tag *)calloc(1, sizeof(ID3v2Tag)); id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion = AtomicParsley_ID3v2Tag_MajorVersion; id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion = AtomicParsley_ID3v2Tag_RevisionVersion; id32_atom->ID32_TagInfo->ID3v2Tag_Flags = AtomicParsley_ID3v2Tag_Flags; id32_atom->ID32_TagInfo->ID3v2Tag_Length = 10; // this would be 9 for v2.2 id32_atom->ID32_TagInfo->ID3v2_Tag_ExtendedHeader_Length = 0; id32_atom->ID32_TagInfo->ID3v2_FrameCount = 0; id32_atom->ID32_TagInfo->modified_tag = false; // this will have to change when a frame is added/modified/removed // because this id3 header won't be written with 0 frames id32_atom->ID32_TagInfo->ID3v2_FirstFrame = NULL; id32_atom->ID32_TagInfo->ID3v2_FrameList = NULL; return; } void APar_realloc_memcpy(ID3v2Fields *thisField, uint32_t new_size) { if (new_size > thisField->alloc_length) { char *new_alloc = (char *)calloc(1, sizeof(char *) * new_size + 1); // memcpy(new_alloc, thisField->field_string, thisField->field_length); thisField->field_length = 0; free(thisField->field_string); thisField->field_string = new_alloc; thisField->alloc_length = new_size; } return; } /////////////////////////////////////////////////////////////////////////////////////// // id3 frame setting/finding functions // /////////////////////////////////////////////////////////////////////////////////////// uint32_t APar_TextFieldDataPut(ID3v2Fields *thisField, const char *this_payload, uint8_t str_encoding, bool multistringtext = false) { uint32_t bytes_used = 0; if (multistringtext == false) { thisField->field_length = 0; } if (str_encoding == TE_UTF8) { bytes_used = strlen( this_payload); // no NULL termination is provided until render time if (bytes_used + thisField->field_length > thisField->alloc_length) { APar_realloc_memcpy(thisField, (bytes_used > 2000 ? bytes_used : 2000)); } memcpy(thisField->field_string + thisField->field_length, this_payload, bytes_used); thisField->field_length += bytes_used; } else if (str_encoding == TE_LATIN1) { int string_length = strlen(this_payload); if (string_length + thisField->field_length > thisField->alloc_length) { APar_realloc_memcpy(thisField, (string_length > 2000 ? string_length : 2000)); } int converted_bytes = UTF8Toisolat1( (unsigned char *)thisField->field_string + thisField->field_length, (int)thisField->alloc_length, (unsigned char *)this_payload, string_length); if (converted_bytes > 0) { thisField->field_length += converted_bytes; bytes_used = converted_bytes; // fprintf(stdout, "string %s, %" PRIu32 "=%" PRIu32 "\n", // thisField->field_string, thisField->field_length, bytes_used); } } else if (str_encoding == TE_UTF16BE_NO_BOM) { int string_length = (int)utf8_length(this_payload, strlen(this_payload)) + 1; if (2 * string_length + thisField->field_length > thisField->alloc_length) { APar_realloc_memcpy(thisField, (2 * string_length + thisField->field_length > 2000 ? 2 * string_length + thisField->field_length : 2000)); } int converted_bytes = UTF8ToUTF16BE( (unsigned char *)thisField->field_string + thisField->field_length, (int)thisField->alloc_length, (unsigned char *)this_payload, string_length); if (converted_bytes > 0) { thisField->field_length += converted_bytes; bytes_used = converted_bytes; } } else if (str_encoding == TE_UTF16LE_WITH_BOM) { int string_length = (int)utf8_length(this_payload, strlen(this_payload)) + 1; uint64_t bom_offset = 0; if (2 * string_length + thisField->field_length > thisField->alloc_length) { // important: realloc before BOM testing!!! APar_realloc_memcpy(thisField, (2 * string_length + thisField->field_length > 2000 ? 2 * string_length + thisField->field_length : 2000)); } if (thisField->field_length == 0 && multistringtext == false) { memcpy(thisField->field_string, "\xFF\xFE", 2); } uint8_t field_encoding = TextField_TestBOM(thisField->field_string); if (field_encoding > 0) { bom_offset = 2; } int converted_bytes = UTF8ToUTF16LE((unsigned char *)thisField->field_string + thisField->field_length + bom_offset, (int)thisField->alloc_length, (unsigned char *)this_payload, string_length); if (converted_bytes > 0) { thisField->field_length += converted_bytes + bom_offset; bytes_used = converted_bytes; } } if (multistringtext != false) { if (str_encoding == TE_UTF16LE_WITH_BOM || str_encoding == TE_UTF16BE_NO_BOM) { bytes_used += 2; } else { bytes_used += 1; } } return bytes_used; } uint32_t APar_BinaryFieldPut(ID3v2Fields *thisField, uint32_t a_number, const char *this_payload, uint32_t payload_len) { if (thisField->ID3v2_Field_Type == ID3_TEXT_ENCODING_FIELD || thisField->ID3v2_Field_Type == ID3_PIC_TYPE_FIELD || thisField->ID3v2_Field_Type == ID3_GROUPSYMBOL_FIELD) { thisField->field_string[0] = (unsigned char)a_number; thisField->field_length = 1; // fprintf(stdout, "My (TE/PT) content is 0x%02X\n", // thisField->field_string[0]); return 1; } else if (thisField->ID3v2_Field_Type == ID3_BINARY_DATA_FIELD && payload_len == 0) { // contents of a file uint64_t file_length = findFileSize(this_payload); thisField->field_string = (char *)calloc(1, sizeof(char *) * file_length + 16); FILE *binfile = APar_OpenFile(this_payload, "rb"); APar_ReadFile(thisField->field_string, binfile, file_length); fclose(binfile); thisField->field_length = file_length; thisField->alloc_length = file_length + 16; thisField->ID3v2_Field_Type = ID3_BINARY_DATA_FIELD; return file_length; } else if (thisField->ID3v2_Field_Type == ID3_BINARY_DATA_FIELD || thisField->ID3v2_Field_Type == ID3_COUNTER_FIELD) { thisField->field_string = (char *)calloc(1, sizeof(char *) * payload_len + 16); memcpy(thisField->field_string, this_payload, payload_len); thisField->field_length = payload_len; thisField->alloc_length = payload_len + 16; thisField->ID3v2_Field_Type = ID3_BINARY_DATA_FIELD; return payload_len; } return 0; } void APar_FrameDataPut(ID3v2Frame *thisFrame, const char *frame_payload, AdjunctArgs *adjunct_payload, uint8_t str_encoding) { if (adjunct_payload->multistringtext == false && !APar_EvalFrame_for_Field(thisFrame->ID3v2_FrameType, ID3_COUNTER_FIELD)) thisFrame->ID3v2_Frame_Length = 0; switch (thisFrame->ID3v2_FrameType) { case ID3_TEXT_FRAME: { if (adjunct_payload->multistringtext && thisFrame->textfield_tally >= 1) { ID3v2Fields *last_textfield = APar_FindLastTextField(thisFrame); if (APar_ExtraTextFieldInit( last_textfield, strlen(frame_payload), (uint8_t)thisFrame->ID3v2_Frame_Fields->field_string[0])) { thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut( last_textfield->next_field, frame_payload, (uint8_t)thisFrame->ID3v2_Frame_Fields->field_string[0], true); } } else { thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut( thisFrame->ID3v2_Frame_Fields, str_encoding, NULL, 1); // encoding thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 1, frame_payload, str_encoding, false); // text field GlobalID3Tag->ID3v2_FrameCount++; } modified_atoms = true; GlobalID3Tag->modified_tag = true; // GlobalID3Tag->ID3v2_FrameCount++; //don't do this for all text frames // because the multiple text field support of id3v2.4; only when the frame // is initially set thisFrame->textfield_tally++; break; } case ID3_TEXT_FRAME_USERDEF: { thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut( thisFrame->ID3v2_Frame_Fields, str_encoding, NULL, 1); // encoding thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 1, adjunct_payload->descripArg, str_encoding); // language thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 2, frame_payload, str_encoding); // text field modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; break; } case ID3_DESCRIBED_TEXT_FRAME: { thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut( thisFrame->ID3v2_Frame_Fields, str_encoding, NULL, 1); // encoding thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 1, adjunct_payload->targetLang, 0); // language thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 2, adjunct_payload->descripArg, str_encoding); // description thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 3, frame_payload, str_encoding, adjunct_payload->multistringtext); // text field modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; break; } case ID3_URL_FRAME: { thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut( thisFrame->ID3v2_Frame_Fields, frame_payload, TE_LATIN1); // url field modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; break; } case ID3_URL_FRAME_USERDEF: { thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut( thisFrame->ID3v2_Frame_Fields, str_encoding, NULL, 1); // encoding thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 1, adjunct_payload->descripArg, str_encoding); // language thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 2, frame_payload, TE_LATIN1); // url field modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; break; } case ID3_UNIQUE_FILE_ID_FRAME: { thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut( thisFrame->ID3v2_Frame_Fields, frame_payload, TE_LATIN1); // owner field if (memcmp(adjunct_payload->dataArg, "randomUUIDstamp", 16) == 0) { char uuid_binary_str[25]; memset(uuid_binary_str, 0, 25); APar_generate_random_uuid(uuid_binary_str); (thisFrame->ID3v2_Frame_Fields + 1)->field_string = (char *)calloc(1, sizeof(char *) * 40); APar_sprintf_uuid((ap_uuid_t *)uuid_binary_str, (thisFrame->ID3v2_Frame_Fields + 1)->field_string); (thisFrame->ID3v2_Frame_Fields + 1)->field_length = 36; (thisFrame->ID3v2_Frame_Fields + 1)->alloc_length = 40; (thisFrame->ID3v2_Frame_Fields + 1)->ID3v2_Field_Type = ID3_BINARY_DATA_FIELD; thisFrame->ID3v2_Frame_Length += 36; } else { uint8_t uniqueIDlen = strlen(adjunct_payload->dataArg); thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut( thisFrame->ID3v2_Frame_Fields, 0, adjunct_payload->dataArg, (uniqueIDlen > 64 ? 64 : uniqueIDlen)); // unique file ID } modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; break; } case ID3_CD_ID_FRAME: { thisFrame->ID3v2_Frame_Fields->field_length = GenerateMCDIfromCD( frame_payload, thisFrame->ID3v2_Frame_Fields->field_string); thisFrame->ID3v2_Frame_Length = thisFrame->ID3v2_Frame_Fields->field_length; if (thisFrame->ID3v2_Frame_Length < 12) { free(thisFrame->ID3v2_Frame_Fields->field_string); thisFrame->ID3v2_Frame_Fields->field_string = NULL; thisFrame->ID3v2_Frame_Fields->alloc_length = 0; thisFrame->ID3v2_Frame_Length = 0; } else { modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; } break; } case ID3_ATTACHED_PICTURE_FRAME: { thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut( thisFrame->ID3v2_Frame_Fields, str_encoding, NULL, 1); // encoding thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 1, adjunct_payload->mimeArg, TE_LATIN1); // mimetype thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields + 2, adjunct_payload->pictype_uint8, NULL, 1); // picturetype thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 3, adjunct_payload->descripArg, str_encoding); // description //(thisFrame->ID3v2_Frame_Fields+4)->ID3v2_Field_Type = // ID3_BINARY_DATA_FIELD; //because it wasn't malloced, this needs to be set // now thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields + 4, 0, frame_payload, 0); // binary file (path) modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; break; } case ID3_ATTACHED_OBJECT_FRAME: { thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut( thisFrame->ID3v2_Frame_Fields, str_encoding, NULL, 1); // encoding thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 1, adjunct_payload->mimeArg, TE_LATIN1); // mimetype if (memcmp(adjunct_payload->filenameArg, "FILENAMESTAMP", 13) == 0) { const char *derived_filename = NULL; #if defined(_WIN32) derived_filename = strrchr(frame_payload, '\\'); #if defined(__CYGWIN__) const char *derived_filename2 = strrchr(frame_payload, '/'); if (derived_filename2 > derived_filename) { derived_filename = derived_filename2; } #endif #else derived_filename = strrchr(frame_payload, '/'); #endif if (derived_filename == NULL) { derived_filename = frame_payload; } else { derived_filename++; // get rid of the preceding slash } thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 2, derived_filename, str_encoding); // filename } else { thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 2, adjunct_payload->filenameArg, str_encoding); // filename } thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields + 3, adjunct_payload->descripArg, str_encoding); // description thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields + 4, 0, frame_payload, 0); // binary file (path) modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; break; } case ID3_GROUP_ID_FRAME: { uint32_t groupdatalen = strlen(adjunct_payload->dataArg); if (adjunct_payload->groupSymbol > 0) { thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields, frame_payload, TE_LATIN1); // owner field thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields + 1, adjunct_payload->groupSymbol, NULL, 1); // group symbol if (groupdatalen > 0) { // not quite binary (unless it were entered as hex // & converted), but it will do thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields + 2, 0, adjunct_payload->dataArg, groupdatalen); // group symbol } modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; } break; } case ID3_SIGNATURE_FRAME: { if (adjunct_payload->groupSymbol > 0) { thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields, adjunct_payload->groupSymbol, NULL, 1); // group symbol thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields + 1, 0, frame_payload, strlen(frame_payload)); // signature modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; } break; } case ID3_PRIVATE_FRAME: { uint32_t datalen = strlen(adjunct_payload->dataArg); // kinda precludes a true "binary" // sense, but whatever... if (datalen > 0) { thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut(thisFrame->ID3v2_Frame_Fields, frame_payload, TE_LATIN1); // owner field thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields + 1, 0, adjunct_payload->dataArg, datalen); // data modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; } break; } case ID3_PLAYCOUNTER_FRAME: { uint64_t playcount = 0; char play_count_syncsafe[16]; memset(play_count_syncsafe, 0, sizeof(play_count_syncsafe)); if (strcmp(frame_payload, "+1") == 0) { if (thisFrame->ID3v2_Frame_Length == 4) { playcount = (uint64_t)syncsafe32_to_UInt32( thisFrame->ID3v2_Frame_Fields->field_string) + 1; } else if (thisFrame->ID3v2_Frame_Length > 4) { playcount = syncsafeXX_to_UInt64(thisFrame->ID3v2_Frame_Fields->field_string, thisFrame->ID3v2_Frame_Fields->field_length) + 1; } else { playcount = 1; } } else { sscanf(frame_payload, "%" SCNu64, &playcount); } if (playcount < 268435455) { convert_to_syncsafe32(playcount, play_count_syncsafe); thisFrame->ID3v2_Frame_Length = APar_BinaryFieldPut( thisFrame->ID3v2_Frame_Fields, 0, play_count_syncsafe, 4); } else { uint8_t conversion_len = convert_to_syncsafeXX(playcount, play_count_syncsafe); thisFrame->ID3v2_Frame_Length = APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields, 0, play_count_syncsafe, conversion_len); } modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; break; } case ID3_POPULAR_FRAME: { unsigned char popm_rating = 0; uint64_t popm_playcount = 0; char popm_play_count_syncsafe[16]; memset(popm_play_count_syncsafe, 0, sizeof(popm_play_count_syncsafe)); if (adjunct_payload->ratingArg != NULL) { popm_rating = strtoul(adjunct_payload->ratingArg, NULL, 10); } thisFrame->ID3v2_Frame_Length += APar_TextFieldDataPut( thisFrame->ID3v2_Frame_Fields, frame_payload, TE_LATIN1); // owner field thisFrame->ID3v2_Frame_Length += APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields + 1, 0, (char *)&popm_rating, 1); // rating if (adjunct_payload->dataArg != NULL) { if (strlen(adjunct_payload->dataArg) > 0) { if (memcmp(adjunct_payload->dataArg, "+1", 3) == 0) { if ((thisFrame->ID3v2_Frame_Fields + 2)->field_length == 4) { popm_playcount = (uint64_t)syncsafe32_to_UInt32( (thisFrame->ID3v2_Frame_Fields + 2)->field_string) + 1; } else if ((thisFrame->ID3v2_Frame_Fields + 2)->field_length > 4) { popm_playcount = syncsafeXX_to_UInt64( (thisFrame->ID3v2_Frame_Fields + 2)->field_string, (thisFrame->ID3v2_Frame_Fields + 2)->field_length) + 1; } else { popm_playcount = 1; } } else { sscanf(adjunct_payload->dataArg, "%" SCNu64, &popm_playcount); } } } if (popm_playcount > 0) { if (popm_playcount < 268435455) { convert_to_syncsafe32(popm_playcount, popm_play_count_syncsafe); thisFrame->ID3v2_Frame_Length = APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields, 0, popm_play_count_syncsafe, 4); // syncsafe32 counter } else { uint8_t conversion_len = convert_to_syncsafeXX(popm_playcount, popm_play_count_syncsafe); thisFrame->ID3v2_Frame_Length = APar_BinaryFieldPut(thisFrame->ID3v2_Frame_Fields, 0, popm_play_count_syncsafe, conversion_len); // BIGsyncsafe counter } } modified_atoms = true; GlobalID3Tag->modified_tag = true; GlobalID3Tag->ID3v2_FrameCount++; break; } } // end switch return; } void APar_EmbeddedFileTests(const char *filepath, int frameType, AdjunctArgs *adjunct_payloads) { if (frameType == ID3_ATTACHED_PICTURE_FRAME) { // get cli imagetype uint8_t total_image_types = (uint8_t)(sizeof(ImageTypeList) / sizeof(*ImageTypeList)); uint8_t img_typlen = strlen(adjunct_payloads->pictypeArg) + 1; const char *img_comparison_str = NULL; for (uint8_t itest = 0; itest < total_image_types; itest++) { if (img_typlen == 5) { img_comparison_str = ImageTypeList[itest].hexstring; } else { img_comparison_str = ImageTypeList[itest].imagetype_str; } if (strcmp(adjunct_payloads->pictypeArg, img_comparison_str) == 0) { adjunct_payloads->pictype_uint8 = ImageTypeList[itest].hexcode; } } if (strlen(filepath) > 0) { // see if file even exists TestFileExistence(filepath, true); char *image_headerbytes = (char *)calloc(1, (sizeof(char) * 25)); FILE *imagefile = APar_OpenFile(filepath, "rb"); APar_ReadFile(image_headerbytes, imagefile, 24); fclose(imagefile); // test mimetype if (strlen(adjunct_payloads->mimeArg) == 0 || memcmp(adjunct_payloads->mimeArg, "-->", 3) == 0) { uint8_t total_image_tests = (uint8_t)(sizeof(ImageList) / sizeof(*ImageList)); for (uint8_t itest = 0; itest < total_image_tests; itest++) { if (ImageList[itest].image_testbytes == 0) { adjunct_payloads->mimeArg = ImageList[itest].image_mimetype; break; } else if (memcmp(image_headerbytes, ImageList[itest].image_binaryheader, ImageList[itest].image_testbytes) == 0) { adjunct_payloads->mimeArg = ImageList[itest].image_mimetype; if (adjunct_payloads->pictype_uint8 == 0x01) { if (memcmp(image_headerbytes + 16, "\x00\x00\x00\x20\x00\x00\x00\x20", 8) != 0 && itest != 2) { adjunct_payloads->pictype_uint8 = 0x02; } } break; } } } free(image_headerbytes); image_headerbytes = NULL; } } else if (frameType == ID3_ATTACHED_OBJECT_FRAME) { if (strlen(filepath) > 0) { TestFileExistence(filepath, true); FILE *embedfile = APar_OpenFile(filepath, "rb"); fclose(embedfile); } } return; } char *APar_ConvertField_to_UTF8(ID3v2Frame *targetframe, int fieldtype) { char *utf8str = NULL; uint8_t targetfield = 0xFF; uint8_t textencoding = 0; for (uint8_t frm_field = 0; frm_field < targetframe->ID3v2_FieldCount; frm_field++) { if ((targetframe->ID3v2_Frame_Fields + frm_field)->ID3v2_Field_Type == fieldtype) { targetfield = frm_field; break; } } if (targetfield != 0xFF) { if (targetframe->ID3v2_Frame_Fields->ID3v2_Field_Type == ID3_TEXT_ENCODING_FIELD) { textencoding = targetframe->ID3v2_Frame_Fields->field_string[0]; } if (textencoding == TE_LATIN1) { utf8str = (char *)calloc( 1, sizeof(char *) * ((targetframe->ID3v2_Frame_Fields + targetfield) ->field_length * 2) + 16); isolat1ToUTF8( (unsigned char *)utf8str, sizeof(char *) * ((targetframe->ID3v2_Frame_Fields + targetfield) ->field_length * 2) + 16, (unsigned char *)((targetframe->ID3v2_Frame_Fields + targetfield) ->field_string), (targetframe->ID3v2_Frame_Fields + targetfield)->field_length); } else if (textencoding == TE_UTF8) { // just so things can be free()'d with // testing; a small price to pay utf8str = (char *)calloc( 1, sizeof(char *) * ((targetframe->ID3v2_Frame_Fields + targetfield) ->field_length) + 16); memcpy(utf8str, (targetframe->ID3v2_Frame_Fields + targetfield)->field_string, (targetframe->ID3v2_Frame_Fields + targetfield)->field_length); } else if (textencoding == TE_UTF16BE_NO_BOM) { utf8str = (char *)calloc( 1, sizeof(char *) * ((targetframe->ID3v2_Frame_Fields + targetfield) ->field_length * 4) + 16); UTF16BEToUTF8( (unsigned char *)utf8str, sizeof(char *) * ((targetframe->ID3v2_Frame_Fields + targetfield) ->field_length * 4) + 16, (unsigned char *)((targetframe->ID3v2_Frame_Fields + targetfield) ->field_string), (targetframe->ID3v2_Frame_Fields + targetfield)->field_length); } else if (textencoding == TE_UTF16LE_WITH_BOM) { utf8str = (char *)calloc( 1, sizeof(char *) * ((targetframe->ID3v2_Frame_Fields + targetfield) ->field_length * 4) + 16); if (memcmp((targetframe->ID3v2_Frame_Fields + targetfield)->field_string, "\xFF\xFE", 2) == 0) { UTF16LEToUTF8( (unsigned char *)utf8str, sizeof(char *) * ((targetframe->ID3v2_Frame_Fields + targetfield) ->field_length * 4) + 16, (unsigned char *)((targetframe->ID3v2_Frame_Fields + targetfield) ->field_string + 2), (targetframe->ID3v2_Frame_Fields + targetfield)->field_length - 2); } else { UTF16BEToUTF8( (unsigned char *)utf8str, sizeof(char *) * ((targetframe->ID3v2_Frame_Fields + targetfield) ->field_length * 4) + 16, (unsigned char *)((targetframe->ID3v2_Frame_Fields + targetfield) ->field_string + 2), (targetframe->ID3v2_Frame_Fields + targetfield)->field_length - 2); } } } return utf8str; } /*---------------------- APar_FindFrame id3v2tag - an already initialized ID3 tag (contained by an ID32 atom) with 0 or more frames as a linked list frame_str - target frame string (like "TIT2") frameID - a known frame in listed in AP_ID3v2_FrameDefinitions & enumerated in AP_ID3v2_Definitions.h frametype - the type of frame (text, described text, picture, object...) to search for adjunct_payloads - holds optional/required args for supplementary matching; example: described text matching on frame name & description; TODO more criteria createframe - create the frame if not found to be existing; this initialzes the frame only - not its fields. this provides 2 functions: actually searching while looping through the frames & creation of a frame at the end of the frame list. ----------------------*/ ID3v2Frame *APar_FindFrame(ID3v2Tag *id3v2tag, const char *frame_str, int frameID, int frametype, AdjunctArgs *adjunct_payloads, bool createframe) { ID3v2Frame *returnframe = NULL; ID3v2Frame *evalframe = id3v2tag->ID3v2_FirstFrame; uint8_t supplemental_matching = 0; if (createframe) { ID3v2Frame *newframe = (ID3v2Frame *)calloc(1, sizeof(ID3v2Frame)); newframe->ID3v2_NextFrame = NULL; if (id3v2tag->ID3v2_FirstFrame == NULL) id3v2tag->ID3v2_FirstFrame = newframe; if (id3v2tag->ID3v2_FrameList != NULL) id3v2tag->ID3v2_FrameList->ID3v2_NextFrame = newframe; id3v2tag->ID3v2_FrameList = newframe; return newframe; } if (APar_EvalFrame_for_Field(frametype, ID3_DESCRIPTION_FIELD)) { supplemental_matching = 0x01; } while (evalframe != NULL) { // if (trametype is a type containing a modifer like description or image // type or symbol or such things if (supplemental_matching != 0) { // match on description + frame name if (supplemental_matching & 0x01 && evalframe->ID3v2_Frame_ID == frameID) { char *utf8_descrip = APar_ConvertField_to_UTF8(evalframe, ID3_DESCRIPTION_FIELD); if (utf8_descrip != NULL) { if (strcmp(adjunct_payloads->descripArg, utf8_descrip) == 0) { returnframe = evalframe; free(utf8_descrip); break; } free(utf8_descrip); } } } else if (evalframe->ID3v2_Frame_ID == ID3_UNKNOWN_FRAME) { if (memcmp(frame_str, evalframe->ID3v2_Frame_Namestr, 4) == 0) { returnframe = evalframe; break; } } else { // fprintf(stdout, "frame is %s; eval frameID is %d ?= %d\n", frame_str, // evalframe->ID3v2_Frame_ID, frameID); if (evalframe->ID3v2_Frame_ID == frameID) { returnframe = evalframe; break; } } evalframe = evalframe->ID3v2_NextFrame; } return returnframe; } /*---------------------- APar_ID3FrameAmmend id32_atom - the ID32 atom targeted to this language; the ID32 atom is already created, the ID3 tag is either created or containing already parsed ID3 frames frame_str - the string for the frame (like TCON) that is desired. This string must be a known frame string in AP_ID3v2_FrameDefinitions.h frame_payload - the major piece of metadata to be set (for APIC its the path, for MCDI its a device...), that can optionally be NULL (for removal of the frame) adjunct_payloads - a structure holding a number of optional/required parameters for the frame (compression...) str_encoding - the encoding to be used in the fields of the target frame when different encodings are allowed lookup what frame_str is supposed to look like in the KnownFrames[] array in AP_ID3v2_FrameDefinitions.h. First see if this frame exists at all - if it does & the frame_str is NULL or blank (""), then mark this frame for elimination. if the frame is of a particular type (like TCON), run some tests on the frame_payload. If all is well after the tests, and the frame does not exists, create it via APar_FindFrame(... true) & initialize the frame to hold data. Send the frame, payload & adjunct payloads onto APar_FrameDataPut to actually place the data onto the frame ----------------------*/ void APar_ID3FrameAmmend(AtomicInfo *id32_atom, const char *frame_str, const char *frame_payload, AdjunctArgs *adjunct_payloads, uint8_t str_encoding) { ID3v2Frame *targetFrame = NULL; if (id32_atom == NULL) return; GlobalID3Tag = id32_atom->ID32_TagInfo; // fprintf(stdout, "frame is %s; payload is %s; %s %s\n", frame_str, // frame_payload, adjunct_payloads->descripArg, adjunct_payloads->targetLang); int frameID = MatchID3FrameIDstr(frame_str, GlobalID3Tag->ID3v2Tag_MajorVersion); int frameType = KnownFrames[frameID + 1].ID3v2_FrameType; uint8_t frameCompositionList = GetFrameCompositionDescription(frameType); if (frameType == ID3_ATTACHED_PICTURE_FRAME || frameType == ID3_ATTACHED_OBJECT_FRAME) { APar_EmbeddedFileTests(frame_payload, frameType, adjunct_payloads); } targetFrame = APar_FindFrame(id32_atom->ID32_TagInfo, frame_str, frameID, frameType, adjunct_payloads, false); if (frame_payload == NULL) { if (targetFrame != NULL) { targetFrame->eliminate_frame = true; modified_atoms = true; id32_atom->ID32_TagInfo->modified_tag = true; } return; } else if (strlen(frame_payload) == 0) { if (targetFrame != NULL) { targetFrame->eliminate_frame = true; // thats right, frames of empty text are removed - so be a doll // and try to convey some info, eh? modified_atoms = true; id32_atom->ID32_TagInfo->modified_tag = true; } return; } else { if (frameType == ID3_UNKNOWN_FRAME) { APar_assert(false, 10, frame_str); return; } // check tags to be set so they conform to the id3v2 informal specification if (frameType == ID3_TEXT_FRAME) { if (targetFrame != NULL) { if (!targetFrame->eliminate_frame) adjunct_payloads->multistringtext = true; // if a frame already exists and isn't marked for // elimination, append a new string } if (frameID == ID3v2_FRAME_COPYRIGHT || frameID == ID3v2_FRAME_PRODNOTICE) { if ((TestCharInRange(frame_payload[0], '0', '9') + TestCharInRange(frame_payload[1], '0', '9') + TestCharInRange(frame_payload[2], '0', '9') + TestCharInRange(frame_payload[3], '0', '9') != 4) || frame_payload[4] != ' ') { fprintf(stderr, "AtomicParsley warning: frame %s was skipped because it did " "not start with a year followed by a space\n", KnownFrames[frameID].ID3V2p4_FrameID); return; } } else if (frameID == ID3v2_FRAME_PART_O_SET || frameID == ID3v2_FRAME_TRACKNUM) { uint8_t pos_len = strlen(frame_payload); for (uint8_t letter_idx = 0; letter_idx < pos_len; letter_idx++) { if (frame_payload[letter_idx] == '/') continue; if (TestCharInRange(frame_payload[letter_idx], '0', '9') != 1) { if (frameID - 1 == ID3v2_FRAME_PART_O_SET) { fprintf(stderr, "AtomicParsley warning: frame %s was skipped because it " "had an extraneous character: %c\n", KnownFrames[frameID].ID3V2p4_FrameID, frame_payload[letter_idx]); return; } else { // okay this is to support the beloved vinyl if (!(TestCharInRange(frame_payload[letter_idx], 'A', 'F') && TestCharInRange(frame_payload[letter_idx + 1], '0', '9'))) { fprintf(stderr, "AtomicParsley warning: frame %s was skipped because " "it had an extraneous character: %c\n", KnownFrames[frameID].ID3V2p4_FrameID, frame_payload[letter_idx]); return; } } } } } else if (frameID == ID3v2_FRAME_ISRC) { uint8_t isrc_len = strlen(frame_payload); if (isrc_len != 12) { fprintf(stderr, "AtomicParsley warning: setting ISRC frame was " "skipped because it was not 12 characters long\n"); return; } for (uint8_t isrc_ltr_idx = 0; isrc_ltr_idx < isrc_len; isrc_ltr_idx++) { if (TestCharInRange(frame_payload[isrc_ltr_idx], '0', '9') + TestCharInRange(frame_payload[isrc_ltr_idx], 'A', 'Z') == 0) { fprintf(stderr, "AtomicParsley warning: ISRC can only consist of A-Z & " "0-9; letter %u was %c; skipping\n", isrc_ltr_idx + 1, frame_payload[isrc_ltr_idx]); return; } } } } if (targetFrame == NULL) { targetFrame = APar_FindFrame(id32_atom->ID32_TagInfo, frame_str, frameID, frameType, adjunct_payloads, true); if (targetFrame == NULL) { fprintf(stdout, "NULL frame\n"); exit(0); } else { APar_FrameInit(targetFrame, frame_str, frameID, frameCompositionList, frame_payload); } } } if (targetFrame != NULL) { if (adjunct_payloads->zlibCompressed) { targetFrame->ID3v2_Frame_Flags |= (ID32_FRAMEFLAG_COMPRESSED + ID32_FRAMEFLAG_LENINDICATED); } if (targetFrame->ID3v2_Frame_ID == ID3v2_FRAME_LANGUAGE) { APar_FrameDataPut(targetFrame, adjunct_payloads->targetLang, adjunct_payloads, str_encoding); } else if (targetFrame->ID3v2_Frame_ID == ID3v2_FRAME_CONTENTTYPE) { uint8_t genre_idx = ID3StringGenreToInt(frame_payload); if (genre_idx != 0xFF) { char buf[4]; snprintf(buf, sizeof(buf), "%u", genre_idx); APar_FrameDataPut(targetFrame, buf, adjunct_payloads, str_encoding); } else { APar_FrameDataPut( targetFrame, frame_payload, adjunct_payloads, str_encoding); } } else { APar_FrameDataPut( targetFrame, frame_payload, adjunct_payloads, str_encoding); } if (adjunct_payloads->zlibCompressed) { targetFrame->ID3v2_Frame_ExpandedLength = targetFrame->ID3v2_Frame_Length; } targetFrame->ID3v2_Frame_GroupingSymbol = adjunct_payloads->groupSymbol; } return; } /////////////////////////////////////////////////////////////////////////////////////// // id3 cleanup function // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_FreeID32Memory free all the little bits of allocated memory. Follow the ID3v2Frame pointers by each frame's ID3v2_NextFrame. Each frame has ID3v2_FieldCount number of field strings (char*) that were malloced. ----------------------*/ void APar_FreeID32Memory(ID3v2Tag *id32tag) { ID3v2Frame *aframe = id32tag->ID3v2_FirstFrame; while (aframe != NULL) { #if defined(DEBUG_V) fprintf(stdout, "freeing frame %s of %u fields\n", aframe->ID3v2_Frame_Namestr, aframe->ID3v2_FieldCount); #endif for (uint8_t id3fld = 0; id3fld < aframe->ID3v2_FieldCount; id3fld++) { #if defined(DEBUG_V) fprintf(stdout, "freeing field %s ; %u of %u fields\n", (aframe->ID3v2_Frame_Fields + id3fld)->field_string, id3fld + 1, aframe->ID3v2_FieldCount); #endif ID3v2Fields *afield = aframe->ID3v2_Frame_Fields + id3fld; ID3v2Fields *freefield = NULL; while (true) { if (afield != NULL && afield->field_string != NULL) { free(afield->field_string); afield->field_string = NULL; } freefield = afield; afield = afield->next_field; if (afield == NULL) break; if (aframe->ID3v2_Frame_Fields + id3fld != freefield) free(freefield); } } free(aframe->ID3v2_Frame_Fields); aframe->ID3v2_Frame_Fields = NULL; free(aframe); aframe = aframe->ID3v2_NextFrame; } return; } atomicparsley-20240608.083822.1ed9031/src/id3v2.h000066400000000000000000000051471463107535600202770ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - id3v2.h AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// struct AdjunctArgs { const char *targetLang; const char *descripArg; const char *mimeArg; const char *pictypeArg; const char *filenameArg; const char *ratingArg; const char *dataArg; // multipurposed: PRIV's binary data, GRID's group data, // UFID's binary data, POPM's counter field uint8_t pictype_uint8; uint8_t groupSymbol; bool zlibCompressed; bool multistringtext; }; uint64_t syncsafeXX_to_UInt64(char *syncsafe_int, uint8_t syncsafe_len); uint32_t syncsafe32_to_UInt32(char *syncsafe_int); bool ID3v2_TestTagFlag(uint8_t TagFlag, uint8_t TagBit); bool ID3v2_TestFrameFlag(uint16_t FrameFlag, uint16_t FrameBit); uint8_t ImageListMembers(); void ListID3FrameIDstrings(); void List_imagtype_strings(); const char *ConvertCLIFrameStr_TO_frameID(const char *frame_str); bool TestCLI_for_FrameParams(int frametype, uint8_t testparam); int MatchID3FrameIDstr(const char *foundFrameID, uint8_t tagVersion); uint8_t GetFrameCompositionDescription(int ID3v2_FrameTypeID); int FrameStr_TO_FrameType(const char *frame_str); void APar_ID32_ScanID3Tag(FILE *source_file, AtomicInfo *id32_atom); uint32_t APar_GetTagSize(AtomicInfo *id32_atom); uint32_t APar_Render_ID32_Tag(AtomicInfo *id32_atom, uint32_t max_alloc); char *APar_ConvertField_to_UTF8(ID3v2Frame *targetframe, int fieldtype); void APar_ID3Tag_Init(AtomicInfo *id32_atom); void APar_ID3FrameAmmend(AtomicInfo *id32_atom, const char *frame_str, const char *frame_payload, AdjunctArgs *adjunct_payloads, uint8_t str_encoding); void APar_FreeID32Memory(ID3v2Tag *id32tag); atomicparsley-20240608.083822.1ed9031/src/id3v2defs.h000066400000000000000000000326241463107535600211410ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - id3v2defs.h AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// ID3FrameDefinition KnownFrames[] = { {"", "", "", "Unknown frame", "", ID3v2_UNKNOWN_FRAME, ID3_UNKNOWN_FRAME}, {"TAL", "TALB", "TALB", "Album/Movie/Show title", "album", ID3v2_FRAME_ALBUM, ID3_TEXT_FRAME}, {"TBP", "TBPM", "TBPM", "BPM (beats per minute)", "bpm", ID3v2_FRAME_BPM, ID3_TEXT_FRAME}, {"TCM", "TCOM", "TCOM", "Composer", "composer", ID3v2_FRAME_COMPOSER, ID3_TEXT_FRAME}, {"TCO", "TCON", "TCON", "Content Type/Genre", "genre", ID3v2_FRAME_CONTENTTYPE, ID3_TEXT_FRAME}, {"TCP", "TCOP", "TCOP", "Copyright message", "copyright", ID3v2_FRAME_COPYRIGHT, ID3_TEXT_FRAME}, {"", "", "TDEN", "Encoding time", "", ID3v2_FRAME_ENCODINGTIME, ID3_TEXT_FRAME}, {"TDY", "TDLY", "TDLY", "Playlist delay", "", ID3v2_FRAME_PLAYLISTDELAY, ID3_TEXT_FRAME}, {"", "", "TDOR", "Original release time", "", ID3v2_FRAME_ORIGRELTIME, ID3_TEXT_FRAME}, {"", "", "TDRC", "Recording time", "date", ID3v2_FRAME_RECORDINGTIME, ID3_TEXT_FRAME}, {"", "", "TDRL", "Release time", "released", ID3v2_FRAME_RELEASETIME, ID3_TEXT_FRAME}, {"", "", "TDTG", "Tagging time", "tagged", ID3v2_FRAME_TAGGINGTIME, ID3_TEXT_FRAME}, {"TEN", "TENC", "TENC", "Encoded by", "encoder", ID3v2_FRAME_ENCODER, ID3_TEXT_FRAME}, {"TXT", "TEXT", "TEXT", "Lyricist/Text writer", "writer", ID3v2_FRAME_LYRICIST, ID3_TEXT_FRAME}, {"TFT", "TFLT", "TFLT", "File type", "", ID3v2_FRAME_FILETYPE, ID3_TEXT_FRAME}, {"", "", "TIPL", "Involved people list", "", ID3v2_FRAME_INVOLVEDPEOPLE, ID3_TEXT_FRAME}, {"TT1", "TIT1", "TIT1", "Content group description", "grouping", ID3v2_FRAME_GROUP_DESC, ID3_TEXT_FRAME}, {"TT2", "TIT2", "TIT2", "Title/songname/content description", "title", ID3v2_FRAME_TITLE, ID3_TEXT_FRAME}, {"TT3", "TIT3", "TIT3", "Subtitle/Description refinement", "subtitle", ID3v2_FRAME_SUBTITLE, ID3_TEXT_FRAME}, {"TKE", "TKEY", "TKEY", "Initial key", "", ID3v2_FRAME_INITIALKEY, ID3_TEXT_FRAME}, {"TLA", "TLAN", "TLAN", "Language(s)", "", ID3v2_FRAME_LANGUAGE, ID3_TEXT_FRAME}, {"TLE", "TLEN", "TLEN", "Length", "", ID3v2_FRAME_TIMELENGTH, ID3_TEXT_FRAME}, {"", "", "TMCL", "Musician credits list", "credits", ID3v2_FRAME_MUSICIANLIST, ID3_TEXT_FRAME}, {"TMT", "TMED", "TMED", "Media type", "media", ID3v2_FRAME_MEDIATYPE, ID3_TEXT_FRAME}, {"", "", "TMOO", "Mood", "mood", ID3v2_FRAME_MOOD, ID3_TEXT_FRAME}, {"TOT", "TOAL", "TOAL", "Original album/movie/show title", "", ID3v2_FRAME_ORIGALBUM, ID3_TEXT_FRAME}, {"TOF", "TOFN", "TOFN", "Original filename", "", ID3v2_FRAME_ORIGFILENAME, ID3_TEXT_FRAME}, {"TOL", "TOLY", "TOLY", "Original lyricist(s)/text writer(s)", "", ID3v2_FRAME_ORIGWRITER, ID3_TEXT_FRAME}, {"TOA", "TOPE", "TOPE", "Original artist(s)/performer(s)", "", ID3v2_FRAME_ORIGARTIST, ID3_TEXT_FRAME}, {"", "TOWN", "TOWN", "File owner/licensee", "", ID3v2_FRAME_FILEOWNER, ID3_TEXT_FRAME}, {"TP1", "TPE1", "TPE1", "Artist/Lead performer(s)/Soloist(s)", "artist", ID3v2_FRAME_ARTIST, ID3_TEXT_FRAME}, {"TP2", "TPE2", "TPE2", "Album artist/Band/orchestra/accompaniment", "album artist", ID3v2_FRAME_ALBUMARTIST, ID3_TEXT_FRAME}, {"TP3", "TPE3", "TPE3", "Conductor/performer refinement", "conductor", ID3v2_FRAME_CONDUCTOR, ID3_TEXT_FRAME}, {"TP4", "TPE4", "TPE4", "Interpreted or remixed by", "remixer", ID3v2_FRAME_REMIXER, ID3_TEXT_FRAME}, {"TPA", "TPOS", "TPOS", "Part of a set", "", ID3v2_FRAME_PART_O_SET, ID3_TEXT_FRAME}, {"", "", "TPRO", "Produced notice", "", ID3v2_FRAME_PRODNOTICE, ID3_TEXT_FRAME}, {"TPB", "TPUB", "TPUB", "Publisher", "publisher", ID3v2_FRAME_PUBLISHER, ID3_TEXT_FRAME}, {"TRK", "TRCK", "TRCK", "Track number/Position in set", "trk#", ID3v2_FRAME_TRACKNUM, ID3_TEXT_FRAME}, {"", "TRSN", "TRSN", "Internet radio station name", "", ID3v2_FRAME_IRADIONAME, ID3_TEXT_FRAME}, {"", "TRSO", "TRSO", "Internet radio station owner", "", ID3v2_FRAME_IRADIOOWNER, ID3_TEXT_FRAME}, {"", "", "TSOA", "Album sort order", "", ID3v2_FRAME_ALBUMSORT, ID3_TEXT_FRAME}, {"", "", "TSOP", "Performer sort order", "", ID3v2_FRAME_PERFORMERSORT, ID3_TEXT_FRAME}, {"", "", "TSOT", "Title sort order", "", ID3v2_FRAME_TITLESORT, ID3_TEXT_FRAME}, {"TRC", "TSRC", "TSRC", "ISRC", "", ID3v2_FRAME_ISRC, ID3_TEXT_FRAME}, {"TSS", "TSSE", "TSSE", "Software/Hardware and settings used for encoding", "", ID3v2_FRAME_ENCODINGSETTINGS, ID3_TEXT_FRAME}, {"", "", "TSST", "Set subtitle", "", ID3v2_FRAME_SETSUBTITLE, ID3_TEXT_FRAME}, {"TDA", "TDAT", "", "Date", "", ID3v2_DATE, ID3_TEXT_FRAME}, {"TIM", "TIME", "", "TIME", "", ID3v2_TIME, ID3_TEXT_FRAME}, {"TOR", "TORY", "", "Original Release Year", "", ID3v2_ORIGRELYEAR, ID3_TEXT_FRAME}, {"TRD", "TRDA", "", "Recording dates", "", ID3v2_RECORDINGDATE, ID3_TEXT_FRAME}, {"TSI", "TSIZ", "", "Size", "", ID3v2_FRAME_SIZE, ID3_TEXT_FRAME}, {"TYE", "TYER", "", "YEAR", "", ID3v2_FRAME_YEAR, ID3_TEXT_FRAME}, {"TXX", "TXXX", "TXXX", "User defined text information frame", "", ID3v2_FRAME_USERDEF_TEXT, ID3_TEXT_FRAME_USERDEF}, // some of these (like WCOM, WOAF) allow for muliple frames - but (sigh) // alas, such is not the case in AP. {"WCM", "WCOM", "WCOM", "Commercial information", "", ID3v2_FRAME_URLCOMMINFO, ID3_URL_FRAME}, {"WCP", "WCOP", "WCOP", "Copyright/Legal information", "", ID3v2_FRAME_URLCOPYRIGHT, ID3_URL_FRAME}, {"WAF", "WOAF", "WOAF", "Official audio file webpage", "", ID3v2_FRAME_URLAUDIOFILE, ID3_URL_FRAME}, {"WAR", "WOAR", "WOAR", "Official artist/performer webpage", "", ID3v2_FRAME_URLARTIST, ID3_URL_FRAME}, {"WAS", "WOAS", "WOAS", "Official audio source webpage", "", ID3v2_FRAME_URLAUDIOSOURCE, ID3_URL_FRAME}, {"", "WORS", "WORS", "Official Internet radio station homepage", "", ID3v2_FRAME_URLIRADIO, ID3_URL_FRAME}, {"", "WPAY", "WPAY", "Payment", "", ID3v2_FRAME_URLPAYMENT, ID3_URL_FRAME}, {"WPB", "WPUB", "WPUB", "Publishers official webpage", "", ID3v2_FRAME_URLPUBLISHER, ID3_URL_FRAME}, {"WXX", "WXXX", "WXXX", "User defined URL link frame", "", ID3v2_FRAME_USERDEF_URL, ID3_URL_FRAME_USERDEF}, {"UFI", "UFID", "UFID", "Unique file identifier", "", ID3v2_FRAME_UFID, ID3_UNIQUE_FILE_ID_FRAME}, {"MCI", "MCID", "MCDI", "Music CD Identifier", "", ID3v2_FRAME_MUSIC_CD_ID, ID3_CD_ID_FRAME}, {"COM", "COMM", "COMM", "Comment", "comment", ID3v2_FRAME_COMMENT, ID3_DESCRIBED_TEXT_FRAME}, {"ULT", "USLT", "USLT", "Unsynchronised lyrics", "lyrics", ID3v2_FRAME_UNSYNCLYRICS, ID3_DESCRIBED_TEXT_FRAME}, {"", "APIC", "APIC", "Attached picture", "", ID3v2_EMBEDDED_PICTURE, ID3_ATTACHED_PICTURE_FRAME}, {"PIC", "", "", "Attached picture", "", ID3v2_EMBEDDED_PICTURE_V2P2, ID3_OLD_V2P2_PICTURE_FRAME}, {"GEO", "GEOB", "GEOB", "Attached object", "", ID3v2_EMBEDDED_OBJECT, ID3_ATTACHED_OBJECT_FRAME}, {"", "GRID", "GRID", "Group ID registration", "", ID3v2_FRAME_GRID, ID3_GROUP_ID_FRAME}, {"", "", "SIGN", "Signature", "", ID3v2_FRAME_SIGNATURE, ID3_SIGNATURE_FRAME}, {"", "PRIV", "PRIV", "Private frame", "", ID3v2_FRAME_PRIVATE, ID3_PRIVATE_FRAME}, {"CNT", "PCNT", "PCNT", "Play counter", "", ID3v2_FRAME_PLAYCOUNTER, ID3_PLAYCOUNTER_FRAME}, {"POP", "POPM", "POPM", "Popularimeter", "", ID3v2_FRAME_POPULARITY, ID3_POPULAR_FRAME} }; // the field listing array is mostly used for mental clarification instead of // internal use - frames are parsed/rendered hardcoded irrespective of ordering // here ID3v2FieldDefinition FrameTypeConstructionList[] = { {ID3_UNKNOWN_FRAME, 1, {ID3_UNKNOWN_FIELD}}, {ID3_TEXT_FRAME, 2, {ID3_TEXT_ENCODING_FIELD, ID3_TEXT_FIELD}}, {ID3_TEXT_FRAME_USERDEF, 3, {ID3_TEXT_ENCODING_FIELD, ID3_DESCRIPTION_FIELD, ID3_TEXT_FIELD}}, {ID3_URL_FRAME, 1, {ID3_URL_FIELD}}, {ID3_URL_FRAME_USERDEF, 3, {ID3_TEXT_ENCODING_FIELD, ID3_DESCRIPTION_FIELD, ID3_URL_FIELD}}, {ID3_UNIQUE_FILE_ID_FRAME, 2, {ID3_OWNER_FIELD, ID3_BINARY_DATA_FIELD}}, {ID3_CD_ID_FRAME, 1, {ID3_BINARY_DATA_FIELD}}, {ID3_DESCRIBED_TEXT_FRAME, 4, {ID3_TEXT_ENCODING_FIELD, ID3_LANGUAGE_FIELD, ID3_DESCRIPTION_FIELD, ID3_TEXT_FIELD}}, {ID3_ATTACHED_PICTURE_FRAME, 5, {ID3_TEXT_ENCODING_FIELD, ID3_MIME_TYPE_FIELD, ID3_PIC_TYPE_FIELD, ID3_DESCRIPTION_FIELD, ID3_BINARY_DATA_FIELD}}, {ID3_ATTACHED_OBJECT_FRAME, 5, {ID3_TEXT_ENCODING_FIELD, ID3_MIME_TYPE_FIELD, ID3_FILENAME_FIELD, ID3_DESCRIPTION_FIELD, ID3_BINARY_DATA_FIELD}}, {ID3_GROUP_ID_FRAME, 3, {ID3_OWNER_FIELD, ID3_GROUPSYMBOL_FIELD, ID3_BINARY_DATA_FIELD}}, {ID3_SIGNATURE_FRAME, 2, {ID3_GROUPSYMBOL_FIELD, ID3_BINARY_DATA_FIELD}}, {ID3_PRIVATE_FRAME, 2, {ID3_OWNER_FIELD, ID3_BINARY_DATA_FIELD}}, {ID3_PLAYCOUNTER_FRAME, 1, {ID3_COUNTER_FIELD}}, {ID3_POPULAR_FRAME, 3, {ID3_OWNER_FIELD, ID3_BINARY_DATA_FIELD, ID3_COUNTER_FIELD}}, {ID3_OLD_V2P2_PICTURE_FRAME, 5, {ID3_TEXT_ENCODING_FIELD, ID3_IMAGEFORMAT_FIELD, ID3_PIC_TYPE_FIELD, ID3_DESCRIPTION_FIELD, ID3_BINARY_DATA_FIELD}}}; // used to determine mimetype for APIC image writing ImageFileFormatDefinition ImageList[] = { {"image/jpeg", ".jpg", 3, "\xFF\xD8\xFF"}, {"image/png", ".png", 8, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"}, {"image/pdf", ".pdf", 7, "%PDF-1."}, {"image/jp2", ".jp2", 12, "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A\x00\x00\x00\x14\x66\x74" "\x79\x70\x6A\x70\x32\x20"}, {"image/gif", ".gif", 6, "GIF89a"}, {"image/tiff", ".tiff", 4, "\x4D\x4D\x00\x2A"}, {"image/tiff", ".tiff", 4, "\x49\x49\x2A\x00"}, {"image/bmp", ".bmp", 2, "\x42\x4D"}, {"image/bmp", ".bmp", 2, "\x42\x41"}, {"image/photoshop", ".psd", 4, "8BPS"}, {"image/other", ".img", 0, ""}}; ID3ImageType ImageTypeList[] = { {0x00, "0x00", "Other"}, {0x01, "0x01", "32x32 pixels 'file icon' (PNG only)"}, {0x02, "0x02", "Other file icon"}, {0x03, "0x03", "Cover (front)"}, {0x04, "0x04", "Cover (back)"}, {0x05, "0x05", "Leaflet page"}, {0x06, "0x06", "Media (e.g. label side of CD)"}, {0x07, "0x07", "Lead artist/lead performer/soloist"}, {0x08, "0x08", "Artist/performer"}, {0x09, "0x09", "Conductor"}, {0x0A, "0x0A", "Band/Orchestra"}, {0x0B, "0x0B", "Composer"}, {0x0C, "0x0C", "Lyricist/text writer"}, {0x0D, "0x0D", "Recording Location"}, {0x0E, "0x0E", "During recording"}, {0x0F, "0x0F", "During performance"}, {0x10, "0x10", "Movie/video screen capture"}, {0x11, "0x11", "A bright coloured fish"}, {0x12, "0x12", "Illustration"}, {0x13, "0x13", "Band/artist logotype"}, {0x14, "0x14", "Publisher/Studio logotype"}}; atomicparsley-20240608.083822.1ed9031/src/id3v2types.h000066400000000000000000000211121463107535600213520ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - id3v2types.h AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// enum ID3_FieldTypes { ID3_UNKNOWN_FIELD = -1, ID3_TEXT_FIELD, ID3_TEXT_ENCODING_FIELD, ID3_OWNER_FIELD, // UFID,PRIV ID3_DESCRIPTION_FIELD, // TXXX, WXXX ID3_URL_FIELD, ID3_LANGUAGE_FIELD, // USLT ID3_MIME_TYPE_FIELD, // APIC ID3_PIC_TYPE_FIELD, // APIC ID3_BINARY_DATA_FIELD, // APIC,GEOB ID3_FILENAME_FIELD, // GEOB ID3_GROUPSYMBOL_FIELD, ID3_COUNTER_FIELD, ID3_IMAGEFORMAT_FIELD // PIC in v2.2 }; // the order of these frame types must exactly match the order listed in the // FrameTypeConstructionList[] array!!! enum ID3v2FrameType { ID3_UNKNOWN_FRAME = -1, ID3_TEXT_FRAME, ID3_TEXT_FRAME_USERDEF, ID3_URL_FRAME, ID3_URL_FRAME_USERDEF, ID3_UNIQUE_FILE_ID_FRAME, ID3_CD_ID_FRAME, ID3_DESCRIBED_TEXT_FRAME, // oy... these frames (COMM, USLT) can differ by // description ID3_ATTACHED_PICTURE_FRAME, ID3_ATTACHED_OBJECT_FRAME, ID3_GROUP_ID_FRAME, ID3_SIGNATURE_FRAME, ID3_PRIVATE_FRAME, ID3_PLAYCOUNTER_FRAME, ID3_POPULAR_FRAME, ID3_OLD_V2P2_PICTURE_FRAME }; // the order of these frames must exactly match the order listed in the // KnownFrames[] array!!! enum ID3v2FrameIDs { ID3v2_UNKNOWN_FRAME = -1, ID3v2_FRAME_ALBUM, ID3v2_FRAME_BPM, ID3v2_FRAME_COMPOSER, ID3v2_FRAME_CONTENTTYPE, ID3v2_FRAME_COPYRIGHT, ID3v2_FRAME_ENCODINGTIME, ID3v2_FRAME_PLAYLISTDELAY, ID3v2_FRAME_ORIGRELTIME, ID3v2_FRAME_RECORDINGTIME, ID3v2_FRAME_RELEASETIME, ID3v2_FRAME_TAGGINGTIME, ID3v2_FRAME_ENCODER, ID3v2_FRAME_LYRICIST, ID3v2_FRAME_FILETYPE, ID3v2_FRAME_INVOLVEDPEOPLE, ID3v2_FRAME_GROUP_DESC, ID3v2_FRAME_TITLE, ID3v2_FRAME_SUBTITLE, ID3v2_FRAME_INITIALKEY, ID3v2_FRAME_LANGUAGE, ID3v2_FRAME_TIMELENGTH, ID3v2_FRAME_MUSICIANLIST, ID3v2_FRAME_MEDIATYPE, ID3v2_FRAME_MOOD, ID3v2_FRAME_ORIGALBUM, ID3v2_FRAME_ORIGFILENAME, ID3v2_FRAME_ORIGWRITER, ID3v2_FRAME_ORIGARTIST, ID3v2_FRAME_FILEOWNER, ID3v2_FRAME_ARTIST, ID3v2_FRAME_ALBUMARTIST, ID3v2_FRAME_CONDUCTOR, ID3v2_FRAME_REMIXER, ID3v2_FRAME_PART_O_SET, ID3v2_FRAME_PRODNOTICE, ID3v2_FRAME_PUBLISHER, ID3v2_FRAME_TRACKNUM, ID3v2_FRAME_IRADIONAME, ID3v2_FRAME_IRADIOOWNER, ID3v2_FRAME_ALBUMSORT, ID3v2_FRAME_PERFORMERSORT, ID3v2_FRAME_TITLESORT, ID3v2_FRAME_ISRC, ID3v2_FRAME_ENCODINGSETTINGS, ID3v2_FRAME_SETSUBTITLE, ID3v2_DATE, ID3v2_TIME, ID3v2_ORIGRELYEAR, ID3v2_RECORDINGDATE, ID3v2_FRAME_SIZE, ID3v2_FRAME_YEAR, ID3v2_FRAME_USERDEF_TEXT, ID3v2_FRAME_URLCOMMINFO, ID3v2_FRAME_URLCOPYRIGHT, ID3v2_FRAME_URLAUDIOFILE, ID3v2_FRAME_URLARTIST, ID3v2_FRAME_URLAUDIOSOURCE, ID3v2_FRAME_URLIRADIO, ID3v2_FRAME_URLPAYMENT, ID3v2_FRAME_URLPUBLISHER, ID3v2_FRAME_USERDEF_URL, ID3v2_FRAME_UFID, ID3v2_FRAME_MUSIC_CD_ID, ID3v2_FRAME_COMMENT, ID3v2_FRAME_UNSYNCLYRICS, ID3v2_EMBEDDED_PICTURE, ID3v2_EMBEDDED_PICTURE_V2P2, ID3v2_EMBEDDED_OBJECT, ID3v2_FRAME_GRID, ID3v2_FRAME_SIGNATURE, ID3v2_FRAME_PRIVATE, ID3v2_FRAME_PLAYCOUNTER, ID3v2_FRAME_POPULARITY }; enum ID3v2_TagFlags { ID32_TAGFLAG_BIT0 = 0x01, ID32_TAGFLAG_BIT1 = 0x02, ID32_TAGFLAG_BIT2 = 0x04, ID32_TAGFLAG_BIT3 = 0x08, ID32_TAGFLAG_FOOTER = 0x10, ID32_TAGFLAG_EXPERIMENTAL = 0x20, ID32_TAGFLAG_EXTENDEDHEADER = 0x40, ID32_TAGFLAG_UNSYNCRONIZATION = 0x80 }; enum ID3v2_FrameFlags { ID32_FRAMEFLAG_STATUS = 0x4000, ID32_FRAMEFLAG_PRESERVE = 0x2000, ID32_FRAMEFLAG_READONLY = 0x1000, ID32_FRAMEFLAG_GROUPING = 0x0040, ID32_FRAMEFLAG_COMPRESSED = 0x0008, ID32_FRAMEFLAG_ENCRYPTED = 0x0004, ID32_FRAMEFLAG_UNSYNCED = 0x0002, ID32_FRAMEFLAG_LENINDICATED = 0x0001 }; // the wording of the ID3 (v2.4 in this case) 'informal standard' is not always // replete with clarity. text encodings are worded as having a NULL terminator // (8or16bit), even for the body of text frames with that in hand, then a // description field from COMM should look much like a utf8 text field and yet // for TXXX, description is expressely worded as: // // "The frame body consists of a description of the string, represented as a // terminated string, followed by the actual string." // // Description $00 (00) // Value // // Note how description is expressly *worded* as having a NULL terminator, but // the text field is not. GEOB text clarifies things better: "The first two // strings [mime & filename] may be omitted, leaving only their terminations. // // MIME type $00 // Filename $00 (00) // // so these trailing $00 (00) are the terminators for the strings - not // separators between n-length string fields. If the string is devoid of // content (not NULLed out, but *devoid* of info), then the only thing that // should exist is for a utf16 BOM to exist on text encoding 0x01. The // (required) terminator for mime & filename are specifically enumerated in the // frame format, which matches the wording of the frame description. ...and so // AP does not terminate text fields // // Further sealing the case is the reference implementation for id3v2.3 // (id3lib) doesn't terminate text fields: // // http://sourceforge.net/project/showfiles.php?group_id=979&package_id=4679 enum text_encodings { TE_LATIN1 = 0, TE_UTF16LE_WITH_BOM = 1, TE_UTF16BE_NO_BOM = 2, TE_UTF8 = 3 }; // Structure that defines the (subset) known ID3 frames defined by id3 informal // specification. typedef struct { const char *ID3V2p2_FrameID; const char *ID3V2p3_FrameID; const char *ID3V2p4_FrameID; const char *ID3V2_FrameDescription; const char *CLI_frameIDpreset; int ID3v2_InternalFrameID; int ID3v2_FrameType; } ID3FrameDefinition; typedef struct { const char *image_mimetype; const char *image_fileextn; uint8_t image_testbytes; const char *image_binaryheader; } ImageFileFormatDefinition; typedef struct { uint8_t hexcode; const char *hexstring; const char *imagetype_str; } ID3ImageType; // Structure that defines how any ID3v2FrameType is constructed, listing an // array of its constituent ID3_FieldTypes typedef struct { ID3v2FrameType ID3_FrameType; uint8_t ID3_FieldCount; ID3_FieldTypes ID3_FieldComponents[5]; // max known to be tested } ID3v2FieldDefinition; struct ID3v2Fields { int ID3v2_Field_Type; uint32_t field_length; uint32_t alloc_length; char *field_string; ID3v2Fields *next_field; }; struct ID3v2Frame { char ID3v2_Frame_Namestr[5]; uint32_t ID3v2_Frame_Length; // this is the real length, not a syncsafe int; // note: does not include frame ID (like 'TIT2', // 'TCO' - 3or4 bytes) or frame flags (2bytes) uint16_t ID3v2_Frame_Flags; // these next 2 values can be potentially be stored based on bitsetting in // frame flags; uint8_t ID3v2_Frame_GroupingSymbol; uint32_t ID3v2_Frame_ExpandedLength; int ID3v2_Frame_ID; int ID3v2_FrameType; uint8_t ID3v2_FieldCount; uint8_t textfield_tally; ID3v2Fields *ID3v2_Frame_Fields; // malloc ID3v2Frame *ID3v2_NextFrame; bool eliminate_frame; }; struct ID3v2Tag { uint8_t ID3v2Tag_MajorVersion; uint8_t ID3v2Tag_RevisionVersion; uint8_t ID3v2Tag_Flags; uint32_t ID3v2Tag_Length; // this is a bonafide uint_32_t length, not a // syncsafe int // this extended header section depends on a bitsetting in ID3v2Tag_Flags uint32_t ID3v2_Tag_ExtendedHeader_Length; // the entire extended header section is // unimplemented flags & flag frames ID3v2Frame *ID3v2_FirstFrame; ID3v2Frame *ID3v2_FrameList; uint16_t ID3v2_FrameCount; bool modified_tag; }; atomicparsley-20240608.083822.1ed9031/src/main.cpp000066400000000000000000005250701463107535600206310ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - main.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (c) 2005-2007 puck_lock with contributions from others; see the CREDITS file ---------------------- Code Contributions by: * Mike Brancato - Debian patches & build support * Brian Story - porting getopt & native Win32 patches */ //==================================================================// #include "AtomicParsley.h" // define one-letter cli options for #define OPT_HELP 'h' #define OPT_TEST 'T' #define OPT_ShowTextData 't' #define OPT_ExtractPix 'E' #define OPT_ExtractPixToPath 'e' #define Meta_artist 'a' #define Meta_artDirector 0xC6 #define Meta_arranger 0xC8 #define Meta_author 0xC9 #define Meta_conductor 0xD0 #define Meta_director 0xD1 #define Meta_originalArtist 0xD2 #define Meta_producer 0xD3 //#define Meta_performer 0xD4 #define Meta_soundEngineer 0xD5 #define Meta_soloist 0xD6 #define Meta_executiveProducer 0xD7 #define Meta_songtitle 's' #define Meta_subtitle 0xC5 #define Meta_album 'b' #define Meta_tracknum 'k' #define Meta_disknum 'd' #define Meta_genre 'g' #define Meta_comment 'c' #define Meta_year 'y' #define Meta_lyrics 'l' #define Meta_lyrics_file 0xC7 #define Meta_composer 'w' #define Meta_copyright 'x' #define Meta_grouping 'G' #define Meta_album_artist 'A' #define Meta_compilation 'C' #define Meta_hdvideo 'O' #define Meta_BPM 'B' #define Meta_artwork 'r' #define Meta_advisory 'V' #define Meta_stik 'S' #define Meta_description 'p' #define Meta_Rating 0xCB #define Meta_longdescription 'j' #define Meta_TV_Network 'n' #define Meta_TV_ShowName 'H' #define Meta_TV_EpisodeNumber 'N' #define Meta_TV_SeasonNumber 'U' #define Meta_TV_Episode 'I' #define Meta_podcastFlag 'f' #define Meta_category 'q' #define Meta_keyword 'K' #define Meta_podcast_URL 'L' #define Meta_podcast_GUID 'J' #define Meta_PurchaseDate 'D' #define Meta_apID 'Y' #define Meta_cnID 0xC0 #define Meta_geID 0xC2 #define Meta_xID 0xC3 #define Meta_storedescription 0xC4 #define Meta_EncodingTool 0xB7 #define Meta_EncodedBy 0xC1 #define Meta_PlayGapless 0xBA #define Meta_SortOrder 0xBF #define Meta_ReverseDNS_Form 'M' #define Meta_rDNS_rating 0xBB #define Meta_StandardDate 'Z' #define Meta_URL 'u' #define Meta_Information 'i' #define Meta_uuid 'z' #define Opt_Extract_all_uuids 0xB8 #define Opt_Extract_a_uuid 0xB9 #define Opt_Ipod_AVC_uuid 0xBE #define Metadata_Purge 'P' #define UserData_Purge 'X' #define foobar_purge '.' #define Meta_dump 'Q' #define Manual_atom_removal 'R' #define Opt_FreeFree 'F' #define OPT_OutputFile 'o' #define OPT_NoOptimize 0xBD #define OPT_OverWrite 'W' #if defined(_WIN32) #define OPT_PreserveTimeStamps 0xCA #endif #define ISO_Copyright 0xAA #define _3GP_Title 0xAB #define _3GP_Author 0xAC #define _3GP_Performer 0xAD #define _3GP_Genre 0xAE #define _3GP_Description 0xAF #define _3GP_Copyright 0xB0 #define _3GP_Album 0xB1 #define _3GP_Year 0xB2 #define _3GP_Rating 0xB3 #define _3GP_Classification 0xB4 #define _3GP_Keyword 0xB5 #define _3GP_Location 0xB6 #define Meta_ID3v2Tag 0xBC #define Meta_movementCount 0xE0 #define Meta_movementName 0xE1 #define Meta_movementNumber 0xE2 #define Meta_showWorkMovement 0xE3 #define Meta_work 0xE4 char *output_file; int total_args; static void kill_signal(int sig); static void kill_signal(int sig) { exit(0); } // less than 80 (max 78) char wide, giving a general (concise) overview static const char *shortHelp_text = "\n" "AtomicParsley sets metadata into MPEG-4 files & derivatives supporting 3 " "tag\n" " schemes: iTunes-style, 3GPP assets & ISO defined copyright " "notifications.\n" "\n" "AtomicParsley quick help for setting iTunes-style metadata into MPEG-4 " "files.\n" "\n" "General usage examples:\n" " AtomicParsley /path/to.mp4 -T 1\n" " AtomicParsley /path/to.mp4 -t +\n" " AtomicParsley /path/to.mp4 --artist \"Me\" --artwork /path/to/art.jpg\n" " Atomicparsley /path/to.mp4 --albumArtist \"You\" --podcastFlag true\n" " Atomicparsley /path/to.mp4 --stik \"TV Show\" --advisory explicit\n" "\n" "Getting information about the file & tags:\n" " -T --test Test file for mpeg4-ishness & print atom tree\n" " -t --textdata Prints tags embedded within the file\n" " -E --extractPix Extracts pix to the same folder as the mpeg-4 file\n" "\n" "Setting iTunes-style metadata tags\n" " --artist (string) Set the artist tag\n" " --title (string) Set the title tag\n" " --album (string) Set the album tag\n" " --genre (string) Genre tag (see --longhelp for more info)\n" " --tracknum (num)[/tot] Track number (or track number/total " "tracks)\n" " --disk (num)[/tot] Disk number (or disk number/total disks)\n" " --comment (string) Set the comment tag\n" " --year (num|UTC) Year tag (see --longhelp for \"Release " "Date\")\n" " --lyrics (string) Set lyrics (not subject to 256 byte limit)\n" " --lyricsFile (/path) Set lyrics to the content of a file\n" " --composer (string) Set the composer tag\n" " --copyright (string) Set the copyright tag\n" " --grouping (string) Set the grouping tag\n" " --artwork (/path) Set a piece of artwork (jpeg or png only)\n" " --bpm (number) Set the tempo/bpm\n" " --albumArtist (string) Set the album artist tag\n" " --compilation (boolean) Set the compilation flag (true or false)\n" " --hdvideo (number) Set the hdvideo flag to one of:\n" " false or 0 for standard definition\n" " true or 1 for 720p\n" " 2 for 1080p\n" " --advisory (string*) Content advisory (*values: 'clean', " "'explicit')\n" " --stik (string*) Sets the iTunes \"stik\" atom (see " "--longhelp)\n" " --description (string) Set the description tag\n" " --longdesc (string) Set the long description tag\n" " --storedesc (string) Set the store description tag\n" " --TVNetwork (string) Set the TV Network name\n" " --TVShowName (string) Set the TV Show name\n" " --TVEpisode (string) Set the TV episode/production code\n" " --TVSeasonNum (number) Set the TV Season number\n" " --TVEpisodeNum (number) Set the TV Episode number\n" " --podcastFlag (boolean) Set the podcast flag (true or false)\n" " --category (string) Sets the podcast category\n" " --keyword (string) Sets the podcast keyword\n" " --podcastURL (URL) Set the podcast feed URL\n" " --podcastGUID (URL) Set the episode's URL tag\n" " --purchaseDate (UTC) Set time of purchase\n" " --encodingTool (string) Set the name of the encoder\n" " --encodedBy (string) Set the name of the Person/company who " "encoded the file\n" " --apID (string) Set the Account Name\n" " --cnID (number) Set the iTunes Catalog ID (see --longhelp)\n" " --geID (number) Set the iTunes Genre ID (see --longhelp)\n" " --xID (string) Set the vendor-supplied iTunes xID (see " "--longhelp)\n" " --gapless (boolean) Set the gapless playback flag\n" " --contentRating (string*) Set tv/mpaa rating (see -rDNS-help)\n" "\n" "Deleting tags\n" " Set the value to \"\": --artist \"\" --stik \"\" --bpm \"\"\n" " To delete (all) artwork: --artwork REMOVE_ALL\n" " manually removal: --manualAtomRemove " "\"moov.udta.meta.ilst.ATOM\"\n" "\n" "More detailed iTunes help is available with AtomicParsley --longhelp\n" "Setting reverse DNS forms for iTunes files: see --reverseDNS-help\n" "Setting 3gp assets into 3GPP & derivative files: see --3gp-help\n" "Setting copyright notices for all files: see --ISO-help\n" "For file-level options & padding info: see --file-help\n" "Setting custom private tag extensions: see --uuid-help\n" "Setting ID3 tags onto mpeg-4 files: see --ID3-help\n" "\n" "----------------------------------------------------------------------"; // an expansive, verbose, unconstrained (about 112 char wide) detailing of // options static const char *longHelp_text = "AtomicParsley help page for setting iTunes-style metadata into MPEG-4 " "files. \n" " (3gp help available with AtomicParsley --3gp-help)\n" " (ISO copyright help available with AtomicParsley --ISO-help)\n" " (reverse DNS form help available with AtomicParsley " "--reverseDNS-help)\n" "Usage: AtomicParsley [mp4FILE]... [OPTION]... [ARGUMENT]... [ " "[OPTION2]...[ARGUMENT2]...] \n" "\n" "example: AtomicParsley /path/to.mp4 -e ~/Desktop/pix\n" "example: AtomicParsley /path/to.mp4 --podcastURL \"http://www.url.net\" " "--tracknum 45/356\n" "example: AtomicParsley /path/to.mp4 --copyright \"\342\204\227 \302\251 " "2006\"\n" "example: AtomicParsley /path/to.mp4 --year \"2006-07-27T14:00:43Z\" " "--purchaseDate timestamp\n" "example: AtomicParsley /path/to.mp4 --sortOrder artist \"Mighty Dub Cats, " "The\n" "--------------------------------------------------------------------------" "----------------------\n" " Extract any pictures in user data \"covr\" atoms to separate files. \n" " --extractPix , -E Extract to same folder " "(basename derived from file).\n" " --extractPixToPath , -e (/path/basename) Extract to specific path " "(numbers added to basename).\n" " example: --e " "~/Desktop/SomeText\n" " gives: " "SomeText_artwork_1.jpg SomeText_artwork_2.png\n" " Note: extension comes from " "embedded image file format\n" "--------------------------------------------------------------------------" "----------------------\n" " Tag setting options:\n" "\n" " --artist , -a (str) Set the artist tag: " "\"moov.udta.meta.ilst.\302\251ART.data\"\n" " --title , -s (str) Set the title tag: " "\"moov.udta.meta.ilst.\302\251nam.data\"\n" " --album , -b (str) Set the album tag: " "\"moov.udta.meta.ilst.\302\251alb.data\"\n" " --genre , -g (str) Set the genre tag: \"\302\251gen\" " "(custom) or \"gnre\" (standard).\n" " see the standard list with " "\"AtomicParsley --genre-list\"\n" " --tracknum , -k (num)[/tot] Set the track number (or track " "number & total tracks).\n" " --disk , -d (num)[/tot] Set the disk number (or disk " "number & total disks).\n" " --comment , -c (str) Set the comment tag: " "\"moov.udta.meta.ilst.\302\251cmt.data\"\n" " --year , -y (num|UTC) Set the year tag: " "\"moov.udta.meta.ilst.\302\251day.data\"\n" " set with UTC " "\"2006-09-11T09:00:00Z\" for Release Date\n" " --lyrics , -l (str) Set the lyrics tag: " "\"moov.udta.meta.ilst.\302\251lyr.data\"\n" " --lyricsFile , (/path) Set the lyrics tag to the content " "of a file\n" " --composer , -w (str) Set the composer tag: " "\"moov.udta.meta.ilst.\302\251wrt.data\"\n" " --copyright , -x (str) Set the copyright tag: " "\"moov.udta.meta.ilst.cprt.data\"\n" " --grouping , -G (str) Set the grouping tag: " "\"moov.udta.meta.ilst.\302\251grp.data\"\n" " --artwork , -A (/path) Set a piece of artwork (jpeg or " "png) on \"covr.data\"\n" " Note: multiple pieces are " "allowed with more --artwork args\n" " --bpm , -B (num) Set the tempo/bpm tag: " "\"moov.udta.meta.ilst.tmpo.data\"\n" " --albumArtist , -A (str) Set the album artist tag: " "\"moov.udta.meta.ilst.aART.data\"\n" " --compilation , -C (bool) Sets the \"cpil\" atom (true or " "false to delete the atom)\n" " --hdvideo , -V (bool) Sets the \"hdvd\" atom (true or " "false to delete the atom)\n" " --advisory , -y (1of3) Sets the iTunes lyrics advisory " "('remove', 'clean', 'explicit') \n" " --stik , -S (1of7) Sets the iTunes \"stik\" atom " "(--stik \"remove\" to delete) \n" " \"Movie\", \"Normal\", \"TV " "Show\" .... others: \n" " see the full list with " "\"AtomicParsley --stik-list\"\n" " or set in an integer value " "with --stik value=(num)\n" " Note: --stik Audiobook will change " "file extension to '.m4b'\n" " --description , -p (str) Sets the description on the " "\"desc\" atom\n" " --Rating , (str) Sets the Rating on the \"rate\" " "atom\n" " --longdesc , -j (str) Sets the long description on the " "\"ldes\" atom\n" " --storedesc , (str) Sets the iTunes store description " "on the \"sdes\" atom\n" " --TVNetwork , -n (str) Sets the TV Network name on the " "\"tvnn\" atom\n" " --TVShowName , -H (str) Sets the TV Show name on the " "\"tvsh\" atom\n" " --TVEpisode , -I (str) Sets the TV Episode on " "\"tven\":\"209\", but it is a string: \"209 Part 1\"\n" " --TVSeasonNum , -U (num) Sets the TV Season number on the " "\"tvsn\" atom\n" " --TVEpisodeNum , -N (num) Sets the TV Episode number on the " "\"tves\" atom\n" " --podcastFlag , -f (bool) Sets the podcast flag (values are " "\"true\" or \"false\")\n" " --category , -q (str) Sets the podcast category; " "typically a duplicate of its genre\n" " --keyword , -K (str) Sets the podcast keyword; invisible " "to MacOSX Spotlight\n" " --podcastURL , -L (URL) Set the podcast feed URL on the " "\"purl\" atom\n" " --podcastGUID , -J (URL) Set the episode's URL tag on the " "\"egid\" atom\n" " --purchaseDate , -D (UTC) Set Universal Coordinated Time of " "purchase on a \"purd\" atom\n" " (use \"timestamp\" to set UTC to " "now; can be akin to id3v2 TDTG tag)\n" " --encodingTool , (str) Set the name of the encoder on the " "\"\302\251too\" atom\n" " --encodedBy , (str) Set the name of the Person/company " "who encoded the file on the \"\302\251enc\" atom\n" " --apID , -Y (str) Set the name of the Account Name on " "the \"apID\" atom\n" " --cnID , (num) Set iTunes Catalog ID, used for " "combining SD and HD encodes in iTunes on the \"cnID\" atom\n" "\n" " To combine you must set \"hdvd\" " "atom on one file and must have same \"stik\" on both file\n" " Must not use \"stik\" of value Home " "Video(0), use Movie(9)\n" "\n" " iTunes Catalog numbers can be " "obtained by finding the item in the iTunes Store. Once item\n" " is found in the iTunes Store right " "click on picture of item and select copy link. Paste this link\n" " into a document or web browser to " "display the catalog number ID.\n" "\n" " An example link for the video " "Street Kings is:\n" " " "http://itunes.apple.com/WebObjects/MZStore.woa/wa/" "viewMovie?id=278743714&s=143441\n" " Here you can see the cnID is " "278743714\n" "\n" " Alternatively you can use iMDB " "numbers, however these will not match the iTunes catalog.\n" "\n" " --geID , (num) Set iTunes Genre ID. This does not " "necessarily have to match genre.\n" " See --genre-movie-id-list and " "--genre-tv-id-list\n" "\n" " --xID , (str) Set iTunes vendor-supplied xID, " "used to allow iTunes LPs and iTunes Extras to interact \n" " with other content in your " "iTunes Library\n" " --gapless , (bool) Sets the gapless playback flag for " "a track in a gapless album\n" " --sortOrder (type) (str) Sets the sort order string for that " "type of tag.\n" " (available types are: \"name\", " "\"artist\", \"albumartist\",\n" " \"album\", \"composer\", " "\"show\")\n" "\n" "NOTE: Except for artwork, only 1 of each tag is allowed; artwork allows " "multiple pieces.\n" "NOTE: Tags that carry text(str) have a limit of 255 utf8 characters;\n" "however lyrics and long descriptions have no limit.\n" "--------------------------------------------------------------------------" "----------------------\n" " To delete a single atom, set the tag to null (except artwork):\n" " --artist \"\" --lyrics \"\"\n" " --artwork REMOVE_ALL \n" " --metaEnema , -P Douches away every atom under " "\"moov.udta.meta.ilst\" \n" " --foobar2000Enema , -2 Eliminates foobar2000's " "non-compliant so-out-o-spec tagging scheme\n" " --manualAtomRemove \"some.atom.path\" where some.atom.path can be:\n" " keys to using manualAtomRemove:\n" " ilst.ATOM.data or ilst.ATOM target an iTunes-style metadata tag\n" " ATOM:lang=foo target an atom with this language " "setting; like 3gp assets\n" " ATOM.----.name:[foo] target a reverseDNS metadata tag; " "like iTunNORM\n" " Note: these atoms show up with 'AP " "-t' as: Atom \"----\" [foo]\n" " 'foo' is actually carried on the " "'name' atom\n" " ATOM[x] target an atom with an index other " "than 1; like trak[2]\n" " ATOM.uuid=hex-hex-hex-hex targt a uuid atom with the uuid of " "hex string representation\n" " examples:\n" " moov.udta.meta.ilst.----.name:[iTunNORM] " "moov.trak[3].cprt:lang=urd\n" " moov.trak[2].uuid=55534d54-21d2-4fce-bb88-695cfac9c740\n" "--------------------------------------------------------------------------" "----------------------\n" #if defined(__APPLE__) " Environmental Variables (affecting picture placement)\n" "\n" " set PIC_OPTIONS in your shell to set these flags; preferences are " "separated by colons (:)\n" "\n" " MaxDimensions=num (default: 0; unlimited); sets maximum pixel " "dimensions\n" " DPI=num (default: 72); sets dpi\n" " MaxKBytes=num (default: 0; unlimited); maximum kilobytes for file " "(jpeg only)\n" " AddBothPix=bool (default: false); add original & converted pic (for " "archival purposes)\n" " AllPixJPEG | AllPixPNG =bool (default: false); force conversion to a " "specific picture format\n" " SquareUp (include to square images to largest dimension, allows " "an [ugly] 160x1200->1200x1200)\n" " removeTempPix (include to delete temp pic files created when " "resizing images after tagging)\n" " ForceHeight=num (must also specify width, below) force image pixel " "height\n" " ForceWidth=num (must also specify height, above) force image pixel " "width\n" "\n" " Examples: (bash-style)\n" " export " "PIC_OPTIONS=\"MaxDimensions=400:DPI=72:MaxKBytes=100:AddBothPix=true:" "AllPixJPEG=true\"\n" " export PIC_OPTIONS=\"SquareUp:removeTempPix\"\n" " export PIC_OPTIONS=\"ForceHeight=999:ForceWidth=333:removeTempPix\"\n" "--------------------------------------------------------------------------" "----------------------\n" #endif ; static const char *fileLevelHelp_text = "AtomicParsley help page for general & file level options.\n" #if defined(_WIN32) #ifndef __CYGWIN__ " Note: you can change the input/output behavior to raw 8-bit utf8 if the " "program name\n" " is appended with \"-utf8\". AtomicParsley-utf8.exe will have " "problems with files/\n" " folders with unicode characters in given paths.\n" #else " Note: you can change the input/output behavior for MCDI functions to " "raw 8-bit utf8\n" " if the program name is appended with \"-utf8\".\n" #endif "\n" #endif "--------------------------------------------------------------------------" "----------------------\n" " Atom reading services:\n" "\n" " --test , -T Tests file to see if it is a valid " "MPEG-4 file.\n" " Prints out the hierarchical atom " "tree.\n" " -T 1 Supplemental track level info with " "\"-T 1\"\n" " -T +dates Track level with creation/modified " "dates\n" "\n" " --textdata , -t print user data text metadata relevant to " "brand (inc. # of any pics).\n" " -t + show supplemental info like free space, " "available padding, user data\n" " length & media data length\n" " -t 1 show all textual metadata (disregards " "brands, shows track copyright)\n" "\n" " --brands show the major & minor brands for the " "file & available tagging schemes\n" "\n" "--------------------------------------------------------------------------" "----------------------\n" " File services:\n" "\n" " --freefree [num] , Remove \"free\" atoms which only " "act as filler in the file\n" " ?(num)? - optional integer argument " "to delete 'free's to desired level\n" "\n" " NOTE 1: levels begin at level 1 aka " "file level.\n" " NOTE 2: Level 0 (which doesn't " "exist) deletes level 1 atoms that pre-\n" " cede 'moov' & don't serve " "as padding. Typically, such atoms\n" " are created by libmp4ff or " "libmp4v2 as a byproduct of tagging.\n" " NOTE 3: When padding falls below " "MIN_PAD (typically zero), a default\n" " amount of padding " "(typically 2048 bytes) will be added. To\n" " achieve absolutely 0 bytes " "'free' space with --freefree, set\n" " DEFAULT_PAD to 0 via the " "AP_PADDING mechanism (see below).\n" "\n" " --preventOptimizing Prevents reorganizing the file to " "have file metadata before media data.\n" " iTunes/Quicktime have so far " "*always* placed metadata first; many 3rd\n" " party utilities do not (preventing " "streaming to the web, AirTunes, iTV).\n" " Used in conjunction with " "--overWrite, files with metadata at the end\n" " (most ffmpeg produced files) can " "have their tags rapidly updated without\n" " requiring a full rewrite. Note: " "this does not un-optimize a file.\n" " Note: this option will be canceled " "out if used with the --freefree option\n" "\n" " --metaDump Dumps out 'moov.udta' metadata out " "to a new file next to original\n" " (for diagnostic purposes, " "please remove artwork before sending)\n" " --output , -o (/path) Specify the filename of tempfile " "(voids overWrite)\n" " --overWrite , -W Writes to temp file; deletes " "original, renames temp to original\n" " If possible, padding will be used " "to update without a full rewrite.\n" "\n" #if defined(_WIN32) " --preserveTime Will overwrite the original file in " "place (--overWrite forced),\n" " but will also keep the original " "file's timestamps intact.\n" "\n" #endif " --DeepScan Parse areas of the file that are " "normally skipped (must be the 3rd arg)\n" " --iPod-uuid (num) Place the ipod-required uuid for " "higher resolution avc video files\n" " Currently, the only number used is " "1200 - the maximum number of macro-\n" " blocks allowed by the higher " "resolution iPod setting.\n" " NOTE: this requires the " "\"--DeepScan\" option as the 3rd cli argument\n" " NOTE2: only works on the first avc " "video track, not all avc tracks\n" "\n" "Examples: \n" " --freefree 0 (deletes all top-level non-padding atoms preceding " "'mooov') \n" " --freefree 1 (deletes all non-padding atoms at the top most " "level) \n" " --output ~/Desktop/newfile.mp4\n" " AP /path/to/file.m4v --DeepScan --iPod-uuid 1200\n" "--------------------------------------------------------------------------" "----------------------\n" " Padding & 'free' atoms:\n" "\n" " A special type of atom called a 'free' atom is used for padding (all " "'free' atoms contain NULL space).\n" " When changes need to occur, these 'free' atom are used. They grows or " "shink, but the relative locations\n" " of certain other atoms (stco/mdat) remain the same. If there is no " "'free' space, a full rewrite will occur.\n" " The locations of 'free' atom(s) that AP can use as padding must be " "follow 'moov.udta' & come before 'mdat'.\n" " A 'free' preceding 'moov' or following 'mdat' won't be used as padding " "for example. \n" "\n" " Set the shell variable AP_PADDING with these values, separated by " "colons to alter padding behavior:\n" "\n" " DEFAULT_PADDING= - the amount of padding added if the minimum padding " "is non-existant in the file\n" " default = 2048\n" " MIN_PAD= - the minimum padding present before more padding " "will be added\n" " default = 0\n" " MAX_PAD= - the maximum allowable padding; excess padding will " "be eliminated\n" " default = 5000\n" "\n" " If you use --freefree to eliminate 'free' atoms from the file, the " "DEFAULT_PADDING amount will still be\n" " added to any newly written files. Set DEFAULT_PADDING=0 to prevent any " "'free' padding added at rewrite.\n" " You can set MIN_PAD to be assured that at least that amount of padding " "will be present - similarly,\n" " MAX_PAD limits any excessive amount of padding. All 3 options will in " "all likelyhood produce a full\n" " rewrite of the original file. Another case where a full rewrite will " "occur is when the original file\n" " is not optimized and has 'mdat' preceding 'moov'.\n" "\n" #if defined(_WIN32) && !defined(__CYGWIN__) "Examples:\n" " c:> SET AP_PADDING=\"DEFAULT_PAD=0\" or c:> SET " "AP_PADDING=\"DEFAULT_PAD=3128\"\n" " c:> SET AP_PADDING=\"DEFAULT_PAD=5128:MIN_PAD=200:MAX_PAD=6049\"\n" #else "Examples (bash style):\n" " $ export AP_PADDING=\"DEFAULT_PAD=0\" or $ export " "AP_PADDING=\"DEFAULT_PAD=3128\"\n" " $ export AP_PADDING=\"DEFAULT_PAD=5128:MIN_PAD=200:MAX_PAD=6049\"\n" #endif "\n" "Note: while AtomicParsley is still in the beta stage, the original file " "will always remain untouched - \n" " unless given the --overWrite flag when if possible, utilizing " "available padding to update tags\n" " will be tried (falling back to a full rewrite if changes are " "greater than the found padding).\n" "--------------------------------------------------------------------------" "--------------------------\n" " iTunes 7 & Gapless playback:\n" "\n" " iTunes 7 adds NULL space at the ends of files (filled with zeroes). It " "is possble this is how iTunes\n" " implements gapless playback - perhaps not. In any event, with " "AtomicParsley you can choose to preserve\n" " that NULL space, or you can eliminate its presence (typically around " "2,000 bytes). The default behavior\n" " is to preserve it - if it is present at all. You can choose to eliminate " "it by setting the environ-\n" " mental preference for AP_PADDING to have DEFAULT_PAD=0\n" "\n" #if defined(_WIN32) && !defined(__CYGWIN__) "Example:\n" " c:> SET AP_PADDING=\"DEFAULT_PAD=0\"\n" #else "Example (bash style):\n" " $ export AP_PADDING=\"DEFAULT_PAD=0\"\n" #endif "--------------------------------------------------------------------------" "--------------------------\n"; // detailed options for 3gp branded files static const char *_3gpHelp_text = "AtomicParsley 3gp help page for setting 3GPP-style metadata.\n" "--------------------------------------------------------------------------" "--------------------------\n" " 3GPP text tags can be encoded in either UTF-8 (default input encoding) " "or UTF-16 (converted from UTF-8)\n" " Many 3GPP text tags can be set for a desired language by a " "3-letter-lowercase code (default is \"eng\").\n" " For tags that support the language attribute (all except year), more " "than one tag of the same name\n" " (3 titles for example) differing in the language code is supported.\n" "\n" " iTunes-style metadata is not supported by the 3GPP TS 26.244 version " "6.4.0 Release 6 specification.\n" " 3GPP asset tags can be set at movie level or track level & are set in a " "different hierarchy: moov.udta \n" " if at movie level (versus iTunes moov.udta.meta.ilst). Other 3rd party " "utilities may allow setting\n" " iTunes-style metadata in 3gp files. When a 3gp file is detected (file " "extension doesn't matter), only\n" " 3gp spec-compliant metadata will be read & written.\n" "\n" " Note1: there are a number of different 'brands' that 3GPP files come " "marked as. Some will not be \n" " supported by AtomicParsley due simply to them being unknown and " "untested. You can compile your\n" " own AtomicParsley to evaluate it by adding the hex code into the " "source of APar_IdentifyBrand.\n" "\n" " Note2: There are slight accuracy discrepancies in location's fixed " "point decimals set and retrieved.\n" "\n" " Note3: QuickTime Player can see a limited subset of these tags, but " "only in 1 language & there seems to\n" " be an issue with not all unicode text displaying properly. This " "is an issue withing QuickTime -\n" " the exact same text (in utf8) displays properly in an MPEG-4 " "file. Some languages can also display\n" " more glyphs than others.\n" "\n" "--------------------------------------------------------------------------" "--------------------------\n" " Tag setting options (default user data area is movie level; default lang " "is 'eng'; default encoding is UTF8):\n" " required arguments are in (parentheses); optional arguments are in " "[brackets]\n" "\n" " --3gp-title (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp media title tag\n" " --3gp-author (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp author of the media tag\n" " --3gp-performer (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp performer or artist tag\n" " --3gp-genre (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp genre asset tag\n" " --3gp-description (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp description or caption tag\n" " --3gp-copyright (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp copyright notice tag*\n" "\n" " --3gp-album (str) [lang=3str] [UTF16] [trknum=int] [area] " "Set a 3gp album tag (& opt. tracknum)\n" " --3gp-year (int) [area] ........................... Set a " "3gp recording year tag (4 digit only)\n" "\n" " --3gp-rating (str) [entity=4str] [criteria=4str] " "[lang=3str] [UTF16] [area] Rating tag\n" " --3gp-classification (str) [entity=4str] [index=int] " "[lang=3str] [UTF16] [area] Classification\n" "\n" " --3gp-keyword (str) [lang=3str] [UTF16] [area] Format of " "str: 'keywords=word1,word2,word3,word4'\n" "\n" " --3gp-location (str) [lang=3str] [UTF16] [area] Set a 3gp " "location tag (default: Central Park)\n" " [longitude=fxd.pt] [latitude=fxd.pt] " "[altitude=fxd.pt]\n" " [role=str] [body=str] [notes=str]\n" " fxd.pt values are decimal coordinates " "(55.01209, 179.25W, 63)\n" " 'role=' values: 'shooting location', " "'real location', 'fictional location'\n" " a negative value in coordinates " "will be seen as a cli flag\n" " append 'S', 'W' or 'B': lat=55S, " "long=90.23W, alt=90.25B\n" "\n" " [area] can be \"movie\", \"track\" or \"track=num\" where 'num' is the " "number of the track. If not specificied,\n" " assets will be placed at movie level. The \"track\" option sets the " "asset across all available tracks\n" "\n" "Note1: '4str' = a 4 letter string like \"PG13\"; 3str is a 3 letter " "string like \"eng\"; int is an integer\n" "Note2: List all languages for '3str' with \"AtomicParsley --language-list " "(unknown languages become \"und\")\n" "*Note3: The 3gp copyright asset can potentially be altered by using the " "--ISO-copyright setting.\n" "--------------------------------------------------------------------------" "--------------------------\n" "Usage: AtomicParsley [3gpFILE] --option [argument] [optional_arguments] " "[ --option2 [argument2]...] \n" "\n" "example: AtomicParsley /path/to.3gp -t \n" "example: AtomicParsley /path/to.3gp -T 1 \n" "example: Atomicparsley /path/to.3gp --3gp-performer \"Enjoy Yourself\" " "lang=pol UTF16\n" "example: Atomicparsley /path/to.3gp --3gp-year 2006 --3gp-album \"White " "Label\" track=8 lang=fra\n" "example: Atomicparsley /path/to.3gp --3gp-album \"Cow Cod Soup For " "Everyone\" track=10 lang=car\n" "\n" "example: Atomicparsley /path/to.3gp --3gp-classification \"Poor Sport\" " "entity=\"PTA \" index=12 UTF16\n" "example: Atomicparsley /path/to.3gp --3gp-keyword " "keywords=\"foo1,foo2,foo 3\" UTF16 --3gp-keyword \"\"\n" "example: Atomicparsley /path/to.3gp --3gp-location 'Bethesda Terrace' " "latitude=40.77 longitude=73.98W \n" " altitude=4.3B " "role='real' body=Earth notes='Underground'\n" "\n" "example: Atomicparsley /path/to.3gp --3gp-title \"I see London.\" " "--3gp-title \"Veo Madrid.\" lang=spa \n" " --3gp-title \"Widze Warsawa.\" " "lang=pol\n" "\n"; static const char *ISOHelp_text = "AtomicParsley help page for setting ISO copyright notices at movie & " "track level.\n" "--------------------------------------------------------------------------" "--------------------------\n" " The ISO specification allows for setting copyright in a number of " "places. This copyright atom is\n" " independant of the iTunes-style --copyright tag that can be set. This " "ISO tag is identical to the\n" " 3GP-style copyright. In fact, using --ISO-copyright can potentially " "overwrite the 3gp copyright\n" " asset if set at movie level & given the same language to set the " "copyright on. This copyright\n" " notice is the only metadata tag defined by the reference ISO 14496-12 " "specification.\n" "\n" " ISO copyright notices can be set at movie level, track level for a " "single track, or for all tracks.\n" " Multiple copyright notices are allowed, but they must differ in the " "language setting. To see avail-\n" " able languages use \"AtomicParsley --language-list\". Notices can be " "set in utf8 or utf16.\n" "\n" " --ISO-copyright (str) [movie|track|track=#] [lang=3str] [UTF16] " "Set a copyright notice\n" " # in 'track=#' " "denotes the target track\n" " 3str is the 3 " "letter ISO-639-2 language.\n" " Brackets [] " "show optional parameters.\n" " Defaults are: " "movie level, 'eng' in utf8.\n" "\n" "example: AtomicParsley /path/file.mp4 -t 1 Note: the only way to see " "all contents is with -t 1 \n" "example: AtomicParsley /path/file.mp4 --ISO-copyright \"Sample\"\n" "example: AtomicParsley /path/file.mp4 --ISO-copyright \"Sample\" movie\n" "example: AtomicParsley /path/file.mp4 --ISO-copyright \"Sample\" track=2 " "lang=urd\n" "example: AtomicParsley /path/file.mp4 --ISO-copyright \"Sample\" track " "UTF16\n" "example: AP --ISO-copyright \"Example\" track --ISO-copyright \"Por " "Exemplo\" track=2 lang=spa UTF16\n" "\n" "Note: to remove the copyright, set the string to \"\" - the track and " "language must match the target.\n" "example: --ISO-copyright \"\" track --ISO-copyright \"\" track=2 " "lang=spa\n" "\n" "Note: (foo) denotes required arguments; [foo] denotes optional parameters " "& may have defaults.\n"; static const char *uuidHelp_text = "AtomicParsley help page for setting uuid user extension metadata tags.\n" "--------------------------------------------------------------------------" "--------------------------\n" " Setting a user-defined 'uuid' private extention tags will appear in " "\"moov.udta.meta\"). These will\n" " only be read by AtomicParsley & can be set irrespective of file " "branding. The form of uuid that AP\n" " is a v5 uuid generated from a sha1 hash of an atom name in an " "'AtomicParsley.sf.net' namespace.\n" "\n" " The uuid form is in some Sony & Compressor files, but of version 4 " "(random/pseudo-random). An example\n" " uuid of 'cprt' in the 'AtomicParsley.sf.net' namespace is: " "\"4bd39a57-e2c8-5655-a4fb-7a19620ef151\".\n" " 'cprt' in the same namespace will always create that uuid; uuid atoms " "will only print out if the\n" " uuid generated is the same as discovered. Sony uuids don't for example " "show up with AP -t.\n" "\n" " --information , -i (str) Set an information tag on uuid atom " "name\"\251inf\"\n" " --url , -u (URL) Set a URL tag on uuid atom name " "\"\302\251url\"\n" " --tagtime , timestamp Set the Coordinated Univeral Time " "of tagging on \"tdtg\"\n" "\n" " Define & set an arbitrary atom with a text data or embed a file:\n" " --meta-uuid There are two forms: 1 for text & 1 for file " "operations\n" " setting text form:\n" " --meta-uuid (atom) \"text\" (str) \"atom\" = 4 " "character atom name of your choice\n" " str is whatever text " "you want to set\n" " file embedding form:\n" " --meta-uuid (atom) \"file\" (/path) [description=\"foo\"] " "[mime-type=\"foo/moof\"]\n" " \"atom\" = 4 " "character atom name of your choice\n" " /path = path to the " "file that will be embedded*\n" " description = " "optional description of the file\n" " default is " "\"[none]\"\n" " mime-type = optional " "mime type for the file\n" " default is " "\"none\"\n" " Note: no " "auto-disocevery of mime type\n" " if " "you know/want it: supply it.\n" " *Note: a file extension " "(/path/file.ext) is required\n" "\n" "Note: (foo) denotes required arguments; [foo] denotes optional arguments " "& may have defaults.\n" "\n" "Examples: \n" " --tagtime timestamp --information \"[psst]I see metadata\" --url " "http://www.bumperdumper.com\n" " --meta-uuid tagr text \"Johnny Appleseed\" --meta-uuid \302\251sft text " "\"OpenShiiva encoded.\"\n" " --meta-uuid scan file /usr/pix/scans.zip\n" " --meta-uuid 1040 file ../../2006_taxes.pdf description=\"Fooled 'The " "Man' yet again.\"\n" "can be removed with:\n" " --tagtime \"\" --information \"\" --url \" \" --meta-uuid scan file " "\n" " --manualAtomRemove " "\"moov.udta.meta.uuid=672c98cd-f11f-51fd-adec-b0ee7b4d215f\" \\\n" " --manualAtomRemove " "\"moov.udta.meta.uuid=1fed6656-d911-5385-9cb2-cb2c100f06e7\"\n" "Remove the Sony uuid atoms with:\n" " --manualAtomRemove " "moov.trak[1].uuid=55534d54-21d2-4fce-bb88-695cfac9c740 \\\n" " --manualAtomRemove " "moov.trak[2].uuid=55534d54-21d2-4fce-bb88-695cfac9c740 \\\n" " --manualAtomRemove uuid=50524f46-21d2-4fce-bb88-695cfac9c740\n" "\n" "Viewing the contents of uuid atoms:\n" " -t or --textdata Shows the uuid atoms (both text & file) that " "AP sets:\n" " Example output:\n" " Atom uuid=ec0f...d7 (AP uuid for \"scan\") contains: FILE.zip; " "description=[none]\n" " Atom uuid=672c...5f (AP uuid for \"tagr\") contains: Johnny " "Appleseed\n" "\n" "Extracting an embedded file in a uuid atom:\n" " --extract1uuid (atom) Extract file embedded within " "uuid=atom into same folder\n" " (file will be named with suffix " "shown in --textdata)\n" " --extract-uuids [/path] Extract all files in uuid atoms " "under the moov.udta.meta\n" " hierarchy. If no /path is given, " "files will be extracted\n" " to the same folder as the " "originating file.\n" "\n" " Examples:\n" " --extract1uuid scan\n" " ... Extracted uuid=scan attachment to file: " "/some/path/FILE_scan_uuid.zip\n" " --extract-uuids ~/Desktop/plops\n" " ... Extracted uuid=pass attachment to file: " "/Users/me/Desktop/plops_pass_uuid.pdf\n" " ... Extracted uuid=site attachment to file: " "/Users/me/Desktop/plops_site_uuid.html\n" "\n" "--------------------------------------------------------------------------" "----------------------\n"; static const char *rDNSHelp_text = "AtomicParsley help page for setting reverse domain '----' metadata " "atoms.\n" "--------------------------------------------------------------------------" "--------------------------\n" " Please note that the reverse DNS format supported here is not " "feature complete.\n" "\n" " Another style of metadata that iTunes uses is called the reverse DNS " "format. For all known tags,\n" " iTunes offers no user-accessible exposure to these tags or their " "contents. This reverse DNS form has\n" " a differnt form than other iTunes tags that have a atom name that " "describes the content of 'data'\n" " atom it contains. In the reverseDNS format, the parent to the structure " "called the '----' atom, with\n" " children atoms that describe & contain the metadata carried. The 'mean' " "child contains the reverse\n" " domain itself ('com.apple.iTunes') & the 'name' child contains the " "descriptor ('iTunNORM'). A 'data'\n" " atom follows that actually contains the contents of the tag.\n" "\n" " --contentRating (rating) Set the US TV/motion picture media " "content rating\n" " for available ratings use " "\"AtomicParsley --ratings-list\n" " --rDNSatom (str) name=(name_str) domain=(reverse_domain) " "Manually set a reverseDNS atom.\n" "\n" " To set the form manually, 3 things are required: a domain, a name, and " "the desired text.\n" " Note: multiple 'data' atoms are supported, but not in the " "com.apple.iTunes domain\n" " Examples:\n" " --contentRating \"NC-17\" --contentRating \"TV-Y7\"\n" " --rDNSatom \"mpaa|PG-13|300|\" name=iTunEXTC domain=com.apple.iTunes\n" " --contentRating \"\"\n" " --rDNSatom \"\" name=iTunEXTC domain=com.apple.iTunes\n" " --rDNSatom \"try1\" name=EVAL domain=org.fsf --rDNSatom \"try 2\" " "name=EVAL domain=org.fsf\n" " --rDNSatom \"\" name=EVAL domain=org.fsf\n" "--------------------------------------------------------------------------" "--------------------------\n"; static const char *ID3Help_text = "AtomicParsley help page for ID32 atoms with ID3 tags.\n" "--------------------------------------------------------------------------" "--------------------------\n" " ** Please note: ID3 tag support is not feature complete & is in an " "alpha state. **\n" "--------------------------------------------------------------------------" "--------------------------\n" " ID3 tags are the tagging scheme used by mp3 files (where they are found " "typically at the start of the\n" " file). In mpeg-4 files, ID3 version 2 tags are located in specific " "hierarchies at certain levels, at\n" " file/movie/track level. The way that ID3 tags are carried on mpeg-4 " "files (carried by 'ID32' atoms)\n" " was added in early 2006, but the ID3 tagging 'informal standard' was " "last updated (to v2.4) in 2000.\n" " With few exceptions, ID3 tags in mpeg-4 files exist identically to their " "mp3 counterparts.\n" "\n" " The ID3 parlance, a frame contains an piece of metadata. A frame (like " "COMM for comment, or TIT1 for\n" " title) contains the information, while the tag contains all the frames " "collectively. The 'informal\n" " standard' for ID3 allows multiple langauges for frames like COMM " "(comment) & USLT (lyrics). In mpeg-4\n" " this language setting is removed from the ID3 domain and exists in the " "mpeg-4 domain. That means that\n" " when an english and a spanish comment are set, 2 separate ID32 atoms are " "created, each with a tag & 1\n" " frame as in this example:\n" " --ID3Tag COMM \"Primary\" --desc=AAA --ID3Tag COMM \"El Segundo\" " "UTF16LE lang=spa --desc=AAA\n" " See available frames with \"AtomicParsley --ID3frames-list\"\n" " See avilable imagetypes with \"AtomicParsley --imagetype-list\"\n" "\n" " AtomicParsley writes ID3 version 2.4.0 tags *only*. There is no " "up-converting from older versions.\n" " Defaults are:\n" " default to movie level (moov.meta.ID32); other options are [ \"root\", " "\"track=(num)\" ] (see WARNING)\n" " UTF-8 text encoding when optional; other options are [ \"LATIN1\", " "\"UTF16BE\", \"UTF16LE\" ]\n" " frames that require descriptions have a default of \"\"\n" " for frames requiring a language setting, the ID32 language is used " "(currently defaulting to 'eng')\n" " frames that require descriptions have a default of \"\"\n" " image type defaults to 0x00 or Other; for image type 0x01, 32x32 png " "is enforced (switching to 0x02)\n" " setting the image mimetype is generally not required as the file is " "tested, but can be overridden\n" " zlib compression off\n" "\n" " WARNING:\n" " Quicktime Player (up to v7.1.3 at least) will freeze opeing a file " "with ID32 tags at movie level.\n" " Specifically, the parent atom, 'meta' is the source of the issue. " "You can set the tags at file or\n" " track level which avoids the problem, but the default is movie " "level. iTunes is unaffected.\n" "--------------------------------------------------------------------------" "--------------------------\n" " Current limitations:\n" " - syncsafe integers are used as indicated by the id3 \"informal " "standard\". usage/reading of\n" " nonstandard ordinary unsigned integers (uint32_t) is not/will not be " "implemented.\n" " - externally referenced images (using mimetype '-->') are prohibited " "by the ID32 specification.\n" " - the ID32 atom is only supported in a non-referenced context\n" " - probably a raft of other limitations that my brain lost along the " "way...\n" "--------------------------------------------------------------------------" "--------------------------\n" " Usage:\n" " --ID3Tag (frameID or alias) (str) [desc=(str)] [mimetype=(str)] " "[imagetype=(str or hex)] [...]\n" "\n" " ... represents other arguments:\n" " [compressed] zlib compress the frame\n" " [UTF16BE, UTF16LE, LATIN1] alternative text encodings for frames that " "support different encodings\n" "\n" "Note: (foo) denotes required arguments; [foo] denotes optional " "parameters\n" "\n" " Examples:\n" " --ID3Tag APIC /path/to/img.ext\n" " --ID3Tag APIC /path/to/img.ext desc=\"something to say\" imagetype=0x08 " "UTF16LE compressed\n" " --ID3Tag composer \"I, Claudius\" --ID3Tag TPUB \"Seneca the Roman\" " "--ID3Tag TMOO Imperial\n" " --ID3Tag UFID look@me.org uniqueID=randomUUIDstamp\n" "\n" " Extracting embedded images in APIC frames:\n" " --ID3Tag APIC extract\n" " images are extracted into the same directory as the source mpeg-4 " "file\n" "\n" #if defined(__APPLE__) " Setting MCDI (Music CD Identifier):\n" " --ID3Tag MCDI disk4\n" " Information to create this frame is taken directly off an Audio " "CD's TOC. If the target\n" " disk is not found or is not an audio CD, a scan of all devices " "will occur. If an Audio CD\n" " is present, the scan should yield what should be entered after " "'MCDI':\n" " % AP file --ID3Tag MCDI disk3\n" " % No cd present in drive at disk3\n" " % Device 'disk4' contains cd media\n" " % Good news, device 'disk4' is an Audio CD and can be used for " "'MCDI' setting\n" #elif defined(__linux__) " Setting MCDI (Music CD Identifier):\n" " --ID3Tag MCDI /dev/hdc\n" " Information to create this frame is taken directly off an Audio " "CD's TOC. An Audio CD\n" " must be mounted & present.\n" #elif defined(_WIN32) " Setting MCDI (Music CD Identifier):\n" " --ID3Tag MCDI D\n" " Information to create this frame is taken directly off an Audio " "CD's TOC. The letter after\n" " \"MCDI\" is the letter of the drive where the CD is present.\n" #endif ; void ExtractPaddingPrefs(char *env_padding_prefs) { pad_prefs.default_padding_size = DEFAULT_PADDING_LENGTH; pad_prefs.minimum_required_padding_size = MINIMUM_REQUIRED_PADDING_LENGTH; pad_prefs.maximum_present_padding_size = MAXIMUM_REQUIRED_PADDING_LENGTH; if (env_padding_prefs != NULL) { if (env_padding_prefs[0] == 0x22 || env_padding_prefs[0] == 0x27) env_padding_prefs++; } char *env_pad_prefs_ptr = env_padding_prefs; while (env_pad_prefs_ptr != NULL) { env_pad_prefs_ptr = strsep(&env_padding_prefs, ":"); if (env_pad_prefs_ptr == NULL) break; if (strncmp(env_pad_prefs_ptr, "DEFAULT_PAD=", 12) == 0) { strsep(&env_pad_prefs_ptr, "="); sscanf(env_pad_prefs_ptr, "%" SCNu32, &pad_prefs.default_padding_size); } if (strncmp(env_pad_prefs_ptr, "MIN_PAD=", 8) == 0) { strsep(&env_pad_prefs_ptr, "="); sscanf(env_pad_prefs_ptr, "%" SCNu32, &pad_prefs.minimum_required_padding_size); } if (strncmp(env_pad_prefs_ptr, "MAX_PAD=", 8) == 0) { strsep(&env_pad_prefs_ptr, "="); sscanf(env_pad_prefs_ptr, "%" SCNu32, &pad_prefs.maximum_present_padding_size); } } // fprintf(stdout, "Def %" PRIu32 "; Min %" PRIu32 "; Max %" PRIu32 "\n", // pad_prefs.default_padding_size, pad_prefs.minimum_required_padding_size, // pad_prefs.maximum_present_padding_size); return; } void GetBasePath(const char *filepath, char *&basepath) { // with a myriad of m4a, m4p, mp4, whatever else comes up... it might just be // easiest to strip off the end. int split_here = 0; for (int i = strlen(filepath); i >= 0; i--) { const char *this_char = &filepath[i]; if (*this_char == '.') { split_here = i; break; } } memcpy(basepath, filepath, (size_t)split_here); return; } void find_optional_args(char *argv[], int start_optindargs, uint16_t &packed_lang, bool &asUTF16, uint8_t &udta_container, uint8_t &trk_idx, int max_optargs) { asUTF16 = false; packed_lang = 5575; // und = 0x55C4 = 21956, but QTPlayer doesn't like und // //eng = 0x15C7 = 5575 for (int i = 0; i <= max_optargs - 1; i++) { if (argv[start_optindargs + i] && start_optindargs + i <= total_args) { if (strncmp(argv[start_optindargs + i], "lang=", 5) == 0) { if (!MatchLanguageCode(argv[start_optindargs + i] + 5)) { packed_lang = PackLanguage("und", 0); } else { packed_lang = PackLanguage(argv[start_optindargs + i], 5); } } else if (strcmp(argv[start_optindargs + i], "UTF16") == 0) { asUTF16 = true; } else if (strcmp(argv[optind + i], "movie") == 0) { udta_container = MOVIE_LEVEL_ATOM; } else if (strncmp(argv[optind + i], "track=", 6) == 0) { char *track_index_str = argv[optind + i]; strsep(&track_index_str, "="); trk_idx = strtoul(track_index_str, NULL, 10); udta_container = SINGLE_TRACK_ATOM; } else if (strcmp(argv[optind + i], "track") == 0) { udta_container = ALL_TRACKS_ATOM; } if (*argv[start_optindargs + i] == '-') { break; // we've hit another cli argument } } } return; } void scan_ID3_optargs(char *argv[], int start_optargs, const char *&target_lang, uint16_t &packed_lang, uint8_t &char_encoding, char *meta_container, bool &multistring) { packed_lang = 5575; // default ID32 lang is 'eng' uint16_t i = 0; while (argv[start_optargs + i] != NULL) { if (argv[start_optargs + i] && start_optargs + i <= total_args) { if (strncmp(argv[start_optargs + i], "lang=", 5) == 0) { if (!MatchLanguageCode(argv[start_optargs + i] + 5)) { packed_lang = PackLanguage("und", 0); target_lang = "und"; } else { packed_lang = PackLanguage(argv[start_optargs + i], 5); target_lang = argv[start_optargs + i] + 5; } } else if (strcmp(argv[start_optargs + i], "UTF16LE") == 0) { char_encoding = TE_UTF16LE_WITH_BOM; } else if (strcmp(argv[start_optargs + i], "UTF16BE") == 0) { char_encoding = TE_UTF16BE_NO_BOM; } else if (strcmp(argv[start_optargs + i], "LATIN1") == 0) { char_encoding = TE_LATIN1; } else if (strcmp(argv[optind + i], "root") == 0) { *meta_container = 0 - FILE_LEVEL_ATOM; } else if (strncmp(argv[optind + i], "track=", 6) == 0) { char *track_index_str = argv[optind + i]; strsep(&track_index_str, "="); *meta_container = strtoul(track_index_str, NULL, 10); } } if (*argv[start_optargs + i] == '-') { break; // we've hit another cli argument or deleting some frame } i++; } return; } const char * find_ID3_optarg(char *argv[], int start_optargs, const char *arg_string) { const char *ret_val = ""; uint16_t i = 0; uint8_t arg_prefix_len = strlen(arg_string); while (argv[start_optargs + i] != NULL) { if (argv[start_optargs + i] && start_optargs + i <= total_args) { if (strcmp(arg_string, "compressed") == 0 && strcmp(argv[start_optargs + i], "compressed") == 0) { return "1"; } // if (strcmp(arg_string, "text++") == 0 && strcmp(argv[start_optargs + // i], "text++") == 0) { // return "1"; //} if (strncmp(argv[start_optargs + i], arg_string, arg_prefix_len) == 0) { ret_val = argv[start_optargs + i] + arg_prefix_len; break; } } if (*argv[start_optargs + i] == '-') { break; // we've hit another cli argument or deleting some frame } i++; } return ret_val; } //*********************************************** static void show_short_help(void) { printf("%s\n", shortHelp_text); ShowVersionInfo(); printf("\nSubmit bug fixes to https://github.com/wez/atomicparsley\n"); } int real_main(int argc, char *argv[]) { if (argc == 1) { show_short_help(); exit(0); } else if (argc == 2 && ((strcmp(argv[1], "-v") == 0) || (strcmp(argv[1], "-version") == 0) || (strcmp(argv[1], "--version") == 0))) { ShowVersionInfo(); exit(0); } else if (argc == 2) { if ((strcmp(argv[1], "-help") == 0) || (strcmp(argv[1], "--help") == 0) || (strcmp(argv[1], "-h") == 0)) { show_short_help(); exit(0); } else if ((strcmp(argv[1], "--longhelp") == 0) || (strcmp(argv[1], "-longhelp") == 0) || (strcmp(argv[1], "-Lh") == 0)) { #if defined(_WIN32) && !defined(__CYGWIN__) if (UnicodeOutputStatus == WIN32_UTF16) { // convert the helptext to utf16 // to preserve \251 characters int help_len = strlen(longHelp_text) + 1; wchar_t *Lhelp_text = (wchar_t *)malloc(sizeof(wchar_t) * help_len); wmemset(Lhelp_text, 0, help_len); UTF8ToUTF16LE((unsigned char *)Lhelp_text, 2 * help_len, (unsigned char *)longHelp_text, help_len); APar_unicode_win32Printout(Lhelp_text, (char *)longHelp_text); free(Lhelp_text); } else #endif { fprintf(stdout, "%s", longHelp_text); } exit(0); } else if ((strcmp(argv[1], "--3gp-help") == 0) || (strcmp(argv[1], "-3gp-help") == 0) || (strcmp(argv[1], "--3gp-h") == 0)) { fprintf(stdout, "%s\n", _3gpHelp_text); exit(0); } else if ((strcmp(argv[1], "--ISO-help") == 0) || (strcmp(argv[1], "--iso-help") == 0) || (strcmp(argv[1], "-Ih") == 0)) { fprintf(stdout, "%s\n", ISOHelp_text); exit(0); } else if ((strcmp(argv[1], "--file-help") == 0) || (strcmp(argv[1], "-file-help") == 0) || (strcmp(argv[1], "-fh") == 0)) { fprintf(stdout, "%s\n", fileLevelHelp_text); exit(0); } else if ((strcmp(argv[1], "--uuid-help") == 0) || (strcmp(argv[1], "-uuid-help") == 0) || (strcmp(argv[1], "-uh") == 0)) { fprintf(stdout, "%s\n", uuidHelp_text); exit(0); } else if ((strcmp(argv[1], "--reverseDNS-help") == 0) || (strcmp(argv[1], "-rDNS-help") == 0) || (strcmp(argv[1], "-rh") == 0)) { fprintf(stdout, "%s\n", rDNSHelp_text); exit(0); } else if ((strcmp(argv[1], "--ID3-help") == 0) || (strcmp(argv[1], "-ID3-help") == 0) || (strcmp(argv[1], "-ID3h") == 0)) { fprintf(stdout, "%s\n", ID3Help_text); exit(0); } else if (strcmp(argv[1], "--genre-list") == 0) { ListGenresValues(); exit(0); } else if (strcmp(argv[1], "--stik-list") == 0) { ListStikValues(); exit(0); } else if (strcmp(argv[1], "--language-list") == 0 || strcmp(argv[1], "--languages-list") == 0 || strcmp(argv[1], "--list-language") == 0 || strcmp(argv[1], "--list-languages") == 0 || strcmp(argv[1], "-ll") == 0) { ListLanguageCodes(); exit(0); } else if (strcmp(argv[1], "--ratings-list") == 0) { ListMediaRatings(); exit(0); } else if (strcmp(argv[1], "--genre-movie-id-list") == 0) { ListMovieGenreIDValues(); exit(0); } else if (strcmp(argv[1], "--genre-tv-id-list") == 0) { ListTVGenreIDValues(); exit(0); } else if (strcmp(argv[1], "--ID3frames-list") == 0) { ListID3FrameIDstrings(); exit(0); } else if (strcmp(argv[1], "--imagetype-list") == 0) { List_imagtype_strings(); exit(0); } } if (argc == 3 && (strcmp(argv[2], "--brands") == 0 || strcmp(argv[2], "-brands") == 0)) { APar_ExtractBrands(argv[1]); exit(0); } int extr = 99; total_args = argc; char *ISObasemediafile = argv[1]; TestFileExistence(ISObasemediafile, true); char *padding_options = getenv("AP_PADDING"); ExtractPaddingPrefs(padding_options); // it would probably be better to test output_file if provided & if // --overWrite was provided.... probably only of use on Windows - and I'm not // on it. if (strlen(ISObasemediafile) + 11 > MAXPATHLEN) { fprintf(stderr, "%c %s", '\a', "AtomicParsley error: filename/filepath was too long.\n"); exit(1); } if (argc > 3 && strcmp(argv[2], "--DeepScan") == 0) { deep_atom_scan = true; APar_ScanAtoms(ISObasemediafile, true); } while (1) { static struct option long_options[] = { {"help", 0, NULL, OPT_HELP}, {"test", optional_argument, NULL, OPT_TEST}, {"textdata", optional_argument, NULL, OPT_ShowTextData}, {"extractPix", 0, NULL, OPT_ExtractPix}, {"extractPixToPath", required_argument, NULL, OPT_ExtractPixToPath}, {"artist", required_argument, NULL, Meta_artist}, {"artDirector", required_argument, NULL, Meta_artDirector}, {"arranger", required_argument, NULL, Meta_arranger}, {"author", required_argument, NULL, Meta_author}, {"conductor", required_argument, NULL, Meta_conductor}, {"director", required_argument, NULL, Meta_director}, {"originalArtist", required_argument, NULL, Meta_originalArtist}, {"producer", required_argument, NULL, Meta_producer}, // { "performer", required_argument, NULL, Meta_performer // }, {"soundEngineer", required_argument, NULL, Meta_soundEngineer}, {"soloist", required_argument, NULL, Meta_soloist}, {"executiveProducer", required_argument, NULL, Meta_executiveProducer}, {"title", required_argument, NULL, Meta_songtitle}, {"subtitle", required_argument, NULL, Meta_subtitle}, {"album", required_argument, NULL, Meta_album}, {"genre", required_argument, NULL, Meta_genre}, {"tracknum", required_argument, NULL, Meta_tracknum}, {"disknum", required_argument, NULL, Meta_disknum}, {"comment", required_argument, NULL, Meta_comment}, {"year", required_argument, NULL, Meta_year}, {"lyrics", required_argument, NULL, Meta_lyrics}, {"lyricsFile", required_argument, NULL, Meta_lyrics_file}, {"composer", required_argument, NULL, Meta_composer}, {"copyright", required_argument, NULL, Meta_copyright}, {"grouping", required_argument, NULL, Meta_grouping}, {"albumArtist", required_argument, NULL, Meta_album_artist}, {"compilation", required_argument, NULL, Meta_compilation}, {"hdvideo", required_argument, NULL, Meta_hdvideo}, {"advisory", required_argument, NULL, Meta_advisory}, {"bpm", required_argument, NULL, Meta_BPM}, {"artwork", required_argument, NULL, Meta_artwork}, {"stik", required_argument, NULL, Meta_stik}, {"description", required_argument, NULL, Meta_description}, {"longdesc", required_argument, NULL, Meta_longdescription}, {"storedesc", required_argument, NULL, Meta_storedescription}, {"Rating", required_argument, NULL, Meta_Rating}, {"TVNetwork", required_argument, NULL, Meta_TV_Network}, {"TVShowName", required_argument, NULL, Meta_TV_ShowName}, {"TVEpisode", required_argument, NULL, Meta_TV_Episode}, {"TVEpisodeNum", required_argument, NULL, Meta_TV_EpisodeNumber}, {"TVSeasonNum", required_argument, NULL, Meta_TV_SeasonNumber}, {"podcastFlag", required_argument, NULL, Meta_podcastFlag}, {"keyword", required_argument, NULL, Meta_keyword}, {"category", required_argument, NULL, Meta_category}, {"podcastURL", required_argument, NULL, Meta_podcast_URL}, {"podcastGUID", required_argument, NULL, Meta_podcast_GUID}, {"purchaseDate", required_argument, NULL, Meta_PurchaseDate}, {"encodingTool", required_argument, NULL, Meta_EncodingTool}, {"encodedBy", required_argument, NULL, Meta_EncodedBy}, {"apID", required_argument, NULL, Meta_apID}, {"cnID", required_argument, NULL, Meta_cnID}, {"geID", required_argument, NULL, Meta_geID}, {"xID", required_argument, NULL, Meta_xID}, {"gapless", required_argument, NULL, Meta_PlayGapless}, {"sortOrder", required_argument, NULL, Meta_SortOrder}, {"rDNSatom", required_argument, NULL, Meta_ReverseDNS_Form}, {"contentRating", required_argument, NULL, Meta_rDNS_rating}, {"tagtime", optional_argument, NULL, Meta_StandardDate}, {"information", required_argument, NULL, Meta_Information}, {"url", required_argument, NULL, Meta_URL}, {"meta-uuid", required_argument, NULL, Meta_uuid}, {"extract-uuids", optional_argument, NULL, Opt_Extract_all_uuids}, {"extract1uuid", required_argument, NULL, Opt_Extract_a_uuid}, {"iPod-uuid", required_argument, NULL, Opt_Ipod_AVC_uuid}, {"freefree", optional_argument, NULL, Opt_FreeFree}, {"metaEnema", 0, NULL, Metadata_Purge}, {"manualAtomRemove", required_argument, NULL, Manual_atom_removal}, {"udtaEnema", 0, NULL, UserData_Purge}, {"foobar2000Enema", 0, NULL, foobar_purge}, {"metaDump", 0, NULL, Meta_dump}, {"output", required_argument, NULL, OPT_OutputFile}, {"preventOptimizing", 0, NULL, OPT_NoOptimize}, {"overWrite", 0, NULL, OPT_OverWrite}, #if defined(_WIN32) {"preserveTime", 0, NULL, OPT_PreserveTimeStamps}, #endif {"ISO-copyright", required_argument, NULL, ISO_Copyright}, {"3gp-title", required_argument, NULL, _3GP_Title}, {"3gp-author", required_argument, NULL, _3GP_Author}, {"3gp-performer", required_argument, NULL, _3GP_Performer}, {"3gp-genre", required_argument, NULL, _3GP_Genre}, {"3gp-description", required_argument, NULL, _3GP_Description}, {"3gp-copyright", required_argument, NULL, _3GP_Copyright}, {"3gp-album", required_argument, NULL, _3GP_Album}, {"3gp-year", required_argument, NULL, _3GP_Year}, {"3gp-rating", required_argument, NULL, _3GP_Rating}, {"3gp-classification", required_argument, NULL, _3GP_Classification}, {"3gp-keyword", required_argument, NULL, _3GP_Keyword}, {"3gp-location", required_argument, NULL, _3GP_Location}, {"ID3Tag", required_argument, NULL, Meta_ID3v2Tag}, {"DeepScan", 0, &extr, 1}, {"movementCount", required_argument, NULL, Meta_movementCount}, {"movementName", required_argument, NULL, Meta_movementName}, {"movementNumber", required_argument, NULL, Meta_movementNumber}, {"showWorkMovement", required_argument, NULL, Meta_showWorkMovement}, {"work", required_argument, NULL, Meta_work}, {0, 0, 0, 0} }; int c = -1; int option_index = 0; c = getopt_long(argc, argv, "hTtEe:a:b:c:d:f:g:i:k:l:n:o:p:q::u:w:x:y:z:A:B:C:D:F:G:H:" "I:J:K:L:MN:QR:S:U:WXV:ZP", long_options, &option_index); if (c == -1) { if (argc < 3 && argc > 2) { APar_ScanAtoms(ISObasemediafile, true); APar_PrintAtomicTree(); } break; } signal(SIGTERM, kill_signal); signal(SIGINT, kill_signal); switch (c) { // "optind" represents the count of arguments up to and including its // optional flag: case '?': return 1; case OPT_HELP: { fprintf(stdout, "%s", longHelp_text); return 0; } case OPT_TEST: { deep_atom_scan = true; APar_ScanAtoms(ISObasemediafile, true); APar_PrintAtomicTree(); if (argv[optind]) { if (strcmp(argv[optind], "+dates") == 0) { APar_ExtractDetails(APar_OpenISOBaseMediaFile(ISObasemediafile, true), SHOW_TRACK_INFO + SHOW_DATE_INFO); } else { APar_ExtractDetails(APar_OpenISOBaseMediaFile(ISObasemediafile, true), SHOW_TRACK_INFO); } } APar_OpenISOBaseMediaFile(ISObasemediafile, false); break; } case OPT_ShowTextData: { if (argv[optind]) { // for utilities that write iTunes-style metadata into // 3gp branded files APar_ExtractBrands(ISObasemediafile); deep_atom_scan = true; APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); if (strcmp(argv[optind], "+") == 0) { APar_Print_iTunesData(ISObasemediafile, NULL, PRINT_FREE_SPACE + PRINT_PADDING_SPACE + PRINT_USER_DATA_SPACE + PRINT_MEDIA_SPACE, PRINT_DATA); } else { fprintf(stdout, "---------------------------\n"); APar_Print_ISO_UserData_per_track(); AtomicInfo *iTuneslistAtom = APar_FindAtom("moov.udta.meta.ilst", false, SIMPLE_ATOM, 0); if (iTuneslistAtom != NULL) { fprintf( stdout, "---------------------------\n iTunes-style metadata tags:\n"); APar_Print_iTunesData(ISObasemediafile, NULL, PRINT_FREE_SPACE + PRINT_PADDING_SPACE + PRINT_USER_DATA_SPACE + PRINT_MEDIA_SPACE, PRINT_DATA, iTuneslistAtom); } fprintf(stdout, "---------------------------\n"); } } else { deep_atom_scan = true; APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); if (metadata_style >= THIRD_GEN_PARTNER) { APar_PrintUserDataAssests(); } else if (metadata_style == ITUNES_STYLE) { APar_Print_iTunesData(ISObasemediafile, NULL, 0, PRINT_DATA); // false, don't try to extractPix APar_Print_APuuid_atoms(ISObasemediafile, NULL, PRINT_DATA); } } APar_OpenISOBaseMediaFile(ISObasemediafile, false); break; } case OPT_ExtractPix: { char *base_path = (char *)malloc(sizeof(char) * MAXPATHLEN + 1); memset(base_path, 0, MAXPATHLEN + 1); GetBasePath(ISObasemediafile, base_path); APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_Print_iTunesData( ISObasemediafile, base_path, 0, EXTRACT_ARTWORK); // exportPix to stripped ISObasemediafile path APar_OpenISOBaseMediaFile(ISObasemediafile, false); free(base_path); base_path = NULL; break; } case OPT_ExtractPixToPath: { APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_Print_iTunesData(ISObasemediafile, optarg, 0, EXTRACT_ARTWORK); // exportPix to a different path APar_OpenISOBaseMediaFile(ISObasemediafile, false); break; } case Meta_artist: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "artist")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251ART.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_artDirector: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "artDirector")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251ard.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_arranger: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "arranger")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251arg.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_author: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "author")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251aut.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_conductor: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "conductor")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251con.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_director: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "director")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251dir.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_originalArtist: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "originalArtist")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251ope.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_producer: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "producer")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251prd.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } /* case Meta_performer : { APar_ScanAtoms(ISObasemediafile); if ( !APar_assert(metadata_style == ITUNES_STYLE, 1, "performer") ) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo* artistData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.\251prf.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put(artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } */ case Meta_soundEngineer: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "soundEngineer")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251sne.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_soloist: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "soloist")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251sol.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_executiveProducer: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert( metadata_style == ITUNES_STYLE, 1, "executiveProducer")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251xpd.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_songtitle: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "title")) { break; } AtomicInfo *titleData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251nam.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( titleData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_subtitle: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "subtitle")) { break; } AtomicInfo *titleData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251st3.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( titleData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_album: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "album")) { break; } AtomicInfo *albumData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251alb.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( albumData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_genre: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "genre")) { break; } APar_MetaData_atomGenre_Set(optarg); break; } case Meta_tracknum: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "track number")) { break; } uint16_t pos_in_total = 0; uint16_t the_total = 0; if (strrchr(optarg, '/') != NULL) { char *duplicate_info = optarg; char *item_stat = strsep(&duplicate_info, "/"); sscanf(item_stat, "%" SCNu16, &pos_in_total); item_stat = strsep(&duplicate_info, "/"); sscanf(item_stat, "%" SCNu16, &the_total); } else { sscanf(optarg, "%" SCNu16, &pos_in_total); } AtomicInfo *tracknumData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.trkn.data", optarg, AtomFlags_Data_Binary); // tracknum: [0, 0, 0, 0, 0, 0, 0, pos_in_total, 0, the_total, 0, 0]; // BUT that first uint32_t is already accounted for in // APar_MetaData_atom_Init APar_Unified_atom_Put( tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); APar_Unified_atom_Put(tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, pos_in_total, 16); APar_Unified_atom_Put(tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, the_total, 16); APar_Unified_atom_Put( tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); break; } case Meta_disknum: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "disc number")) { break; } uint16_t pos_in_total = 0; uint16_t the_total = 0; if (strrchr(optarg, '/') != NULL) { char *duplicate_info = optarg; char *item_stat = strsep(&duplicate_info, "/"); sscanf(item_stat, "%" SCNu16, &pos_in_total); item_stat = strsep(&duplicate_info, "/"); sscanf(item_stat, "%" SCNu16, &the_total); } else { sscanf(optarg, "%" SCNu16, &pos_in_total); } AtomicInfo *disknumData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.disk.data", optarg, AtomFlags_Data_Binary); // disknum: [0, 0, 0, 0, 0, 0, 0, pos_in_total, 0, the_total]; BUT that // first uint32_t is already accounted for in APar_MetaData_atom_Init APar_Unified_atom_Put( disknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); APar_Unified_atom_Put(disknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, pos_in_total, 16); APar_Unified_atom_Put(disknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, the_total, 16); break; } case Meta_comment: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "comment")) { break; } AtomicInfo *commentData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251cmt.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( commentData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_year: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "year")) { break; } AtomicInfo *yearData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251day.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( yearData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_lyrics: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "lyrics")) { break; } AtomicInfo *lyricsData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251lyr.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( lyricsData_atom, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); break; } case Meta_lyrics_file: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "lyrics")) { break; } APar_MetaData_atomLyrics_Set(optarg); break; } case Meta_composer: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "composer")) { break; } AtomicInfo *composerData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251wrt.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( composerData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_copyright: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "copyright")) { break; } AtomicInfo *copyrightData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.cprt.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( copyrightData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_grouping: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "grouping")) { break; } AtomicInfo *groupingData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251grp.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( groupingData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_compilation: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "compilation")) { break; } if (strcmp(optarg, "false") == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.cpil.data", VERSIONED_ATOM, 0); } else { // compilation: [0, 0, 0, 0, boolean_value]; BUT that first uint32_t // is already accounted for in APar_MetaData_atom_Init AtomicInfo *compilationData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.cpil.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(compilationData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 1, 8); // a hard coded uint8_t of: 1 is compilation } break; } case Meta_hdvideo: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "hdvideo")) { break; } if (strcmp(optarg, "false") == 0 || strlen(optarg) == 0 || strcmp(optarg, "0") == 0) { APar_RemoveAtom("moov.udta.meta.ilst.hdvd.data", VERSIONED_ATOM, 0); } else { // compilation: [0, 0, 0, 0, boolean_value]; BUT that first uint32_t // is already accounted for in APar_MetaData_atom_Init AtomicInfo *hdvideoData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.hdvd.data", optarg, AtomFlags_Data_UInt); uint8_t hdvideo_value = 0; if (strcmp(optarg, "true") == 0) { hdvideo_value = 1; } else { sscanf(optarg, "%" SCNu8, &hdvideo_value); } APar_Unified_atom_Put(hdvideoData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, hdvideo_value, 8); } break; } case Meta_BPM: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "BPM")) { break; } if (strcmp(optarg, "0") == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.tmpo.data", VERSIONED_ATOM, 0); } else { uint16_t bpm_value = 0; sscanf(optarg, "%" SCNu16, &bpm_value); // bpm is [0, 0, 0, 0, 0, bpm_value]; BUT that first uint32_t is // already accounted for in APar_MetaData_atom_Init AtomicInfo *bpmData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tmpo.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(bpmData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, bpm_value, 16); } break; } case Meta_advisory: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "content advisory")) { break; } if (strcmp(optarg, "remove") == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.rtng.data", VERSIONED_ATOM, 0); } else { uint8_t rating_value = 0; if (strcmp(optarg, "clean") == 0) { rating_value = 2; // only \02 is clean } else if (strcmp(optarg, "explicit") == 0) { rating_value = 4; // most non \00, \02 numbers are allowed } // rating is [0, 0, 0, 0, rating_value]; BUT that first uint32_t is // already accounted for in APar_MetaData_atom_Init AtomicInfo *advisoryData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.rtng.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(advisoryData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, rating_value, 8); } break; } case Meta_artwork: { // handled differently: there can be multiple // "moov.udta.meta.ilst.covr.data" atoms char *env_PicOptions = getenv("PIC_OPTIONS"); APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "coverart")) { break; } APar_MetaData_atomArtwork_Set(optarg, env_PicOptions); break; } case Meta_stik: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "'stik'")) { break; } if (strcmp(optarg, "remove") == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.stik.data", VERSIONED_ATOM, 0); } else { uint8_t stik_value = 0; if (strncmp(optarg, "value=", 6) == 0) { char *stik_val_str_ptr = optarg; strsep(&stik_val_str_ptr, "="); stik_value = strtoul(stik_val_str_ptr, NULL, 10); } else { stiks *return_stik = MatchStikString(optarg); if (return_stik != NULL) { stik_value = return_stik->stik_number; if (strcmp(optarg, "Audiobook") == 0) { forced_suffix_type = FORCE_M4B_TYPE; } } } // stik is [0, 0, 0, 0, stik_value]; BUT that first uint32_t is // already accounted for in APar_MetaData_atom_Init AtomicInfo *stikData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.stik.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(stikData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, stik_value, 8); } break; } case Meta_EncodingTool: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "encoding tool")) { break; } AtomicInfo *encodingtoolData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251too.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put(encodingtoolData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_EncodedBy: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "encoded by")) { break; } AtomicInfo *encodedbyData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251enc.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( encodedbyData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_apID: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "Account Name")) { break; } AtomicInfo *apIDData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.apID.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( apIDData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_description: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "description")) { break; } AtomicInfo *descriptionData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.desc.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( descriptionData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_longdescription: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "longdesc")) { break; } AtomicInfo *descriptionData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.ldes.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( descriptionData_atom, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); break; } case Meta_storedescription: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "storedesc")) { break; } AtomicInfo *descriptionData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.sdes.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( descriptionData_atom, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); break; } case Meta_Rating: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "Rating")) { break; } AtomicInfo *ratingData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.rate.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( ratingData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_TV_Network: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "TV Network")) { break; } AtomicInfo *tvnetworkData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tvnn.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( tvnetworkData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_TV_ShowName: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "TV Show name")) { break; } AtomicInfo *tvshownameData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tvsh.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( tvshownameData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_TV_Episode: { // if the show "ABC Lost 209", its "209" APar_ScanAtoms(ISObasemediafile); if (!APar_assert( metadata_style == ITUNES_STYLE, 1, "TV Episode string")) { break; } AtomicInfo *tvepisodeData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tven.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( tvepisodeData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_TV_SeasonNumber: { // if the show "ABC Lost 209", its 2; integer 2 // not char "2" APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "TV Season")) { break; } uint16_t data_value = 0; sscanf(optarg, "%" SCNu16, &data_value); AtomicInfo *tvseasonData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tvsn.data", optarg, AtomFlags_Data_UInt); // season is [0, 0, 0, 0, 0, 0, 0, data_value]; BUT that first uint32_t // is already accounted for in APar_MetaData_atom_Init APar_Unified_atom_Put( tvseasonData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); APar_Unified_atom_Put(tvseasonData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 16); break; } case Meta_TV_EpisodeNumber: { // if the show "ABC Lost 209", its 9; integer // 9 (0x09) not char "9"(0x39) APar_ScanAtoms(ISObasemediafile); if (!APar_assert( metadata_style == ITUNES_STYLE, 1, "TV Episode number")) { break; } uint16_t data_value = 0; sscanf(optarg, "%" SCNu16, &data_value); AtomicInfo *tvepisodenumData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tves.data", optarg, AtomFlags_Data_UInt); // episodenumber is [0, 0, 0, 0, 0, 0, 0, data_value]; BUT that first // uint32_t is already accounted for in APar_MetaData_atom_Init APar_Unified_atom_Put( tvepisodenumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); APar_Unified_atom_Put(tvepisodenumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 16); break; } case Meta_cnID: { // the iTunes Catalog ID APar_ScanAtoms(ISObasemediafile); if (!APar_assert( metadata_style == ITUNES_STYLE, 1, "iTunes Catalog ID")) { break; } uint32_t data_value = 0; sscanf(optarg, "%" SCNu32, &data_value); AtomicInfo *cnIDData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.cnID.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(cnIDData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 32); break; } case Meta_geID: { // the iTunes Genre ID APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "iTunes Genre ID")) { break; } uint32_t data_value = 0; sscanf(optarg, "%" SCNu32, &data_value); AtomicInfo *geIDData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.geID.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(geIDData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 32); break; } case Meta_xID: { // the vendor-supplied iTunes xID APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "iTunes xID")) { break; } AtomicInfo *xIDData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.xid .data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( xIDData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_album_artist: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "album artist")) { break; } AtomicInfo *albumartistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.aART.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( albumartistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_podcastFlag: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "podcast flag")) { break; } if (strcmp(optarg, "false") == 0) { APar_RemoveAtom("moov.udta.meta.ilst.pcst.data", VERSIONED_ATOM, 0); } else { // podcastflag: [0, 0, 0, 0, boolean_value]; BUT that first uint32_t // is already accounted for in APar_MetaData_atom_Init AtomicInfo *podcastFlagData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.pcst.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put( podcastFlagData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 1, 8); // a hard coded uint8_t of: 1 denotes podcast flag } break; } case Meta_keyword: { // TODO to the end of iTunes-style metadata & uuid // atoms APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "keyword")) { break; } AtomicInfo *keywordData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.keyw.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( keywordData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_category: { // see // http://www.apple.com/itunes/podcasts/techspecs.html // for available categories APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "category")) { break; } AtomicInfo *categoryData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.catg.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( categoryData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_podcast_URL: { // usually a read-only value, but useful for // getting videos into the 'podcast' menu APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "podcast URL")) { break; } AtomicInfo *podcasturlData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.purl.data", optarg, AtomFlags_Data_Binary); APar_Unified_atom_Put( podcasturlData_atom, optarg, UTF8_iTunesStyle_Binary, 0, 0); break; } case Meta_podcast_GUID: { // Global Unique IDentifier; it is *highly* // doubtful that this would be useful... APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "podcast GUID")) { break; } AtomicInfo *globalidData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.egid.data", optarg, AtomFlags_Data_Binary); APar_Unified_atom_Put( globalidData_atom, optarg, UTF8_iTunesStyle_Binary, 0, 0); break; } case Meta_PurchaseDate: { // might be useful to *remove* this, but adding // it... although it could function like id3v2 // tdtg... APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "purchase date")) { break; } char *purd_time; bool free_memory = false; if (optarg != NULL) { if (strcmp(optarg, "timestamp") == 0) { purd_time = (char *)malloc(sizeof(char) * 255); free_memory = true; APar_StandardTime(purd_time); } else { purd_time = optarg; } } else { purd_time = optarg; } AtomicInfo *globalIDData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.purd.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( globalIDData_atom, purd_time, UTF8_iTunesStyle_256glyphLimited, 0, 0); if (free_memory) { free(purd_time); purd_time = NULL; } break; } case Meta_PlayGapless: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "gapless playback")) { break; } if (strcmp(optarg, "false") == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.pgap.data", VERSIONED_ATOM, 0); } else { // gapless playback: [0, 0, 0, 0, boolean_value]; BUT that first // uint32_t is already accounted for in APar_MetaData_atom_Init AtomicInfo *gaplessData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.pgap.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put( gaplessData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 1, 8); } break; } case Meta_SortOrder: { AtomicInfo *sortOrder_atom = NULL; APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "sort order tags")) { break; } if (argv[optind] == NULL) { fprintf(stdout, "AP warning, skipping setting the sort order %s tag\n", optarg); break; } if (strcmp(optarg, "name") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.sonm.data", argv[optind], AtomFlags_Data_Text); } else if (strcmp(optarg, "artist") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.soar.data", argv[optind], AtomFlags_Data_Text); } else if (strcmp(optarg, "albumartist") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.soaa.data", argv[optind], AtomFlags_Data_Text); } else if (strcmp(optarg, "album") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.soal.data", argv[optind], AtomFlags_Data_Text); } else if (strcmp(optarg, "composer") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.soco.data", argv[optind], AtomFlags_Data_Text); } else if (strcmp(optarg, "show") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.sosn.data", argv[optind], AtomFlags_Data_Text); } APar_Unified_atom_Put( sortOrder_atom, argv[optind], UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } // uuid atoms case Meta_StandardDate: { APar_ScanAtoms(ISObasemediafile); char *formed_time = (char *)malloc(sizeof(char) * 110); if (argv[optind]) { if (strlen(argv[optind]) > 0) { APar_StandardTime(formed_time); } } AtomicInfo *tdtgUUID = APar_uuid_atom_Init("moov.udta.meta.uuid=%s", "tdtg", AtomFlags_Data_Text, formed_time, false); APar_Unified_atom_Put( tdtgUUID, formed_time, UTF8_iTunesStyle_Unlimited, 0, 0); free(formed_time); break; } case Meta_URL: { APar_ScanAtoms(ISObasemediafile); AtomicInfo *urlUUID = APar_uuid_atom_Init("moov.udta.meta.uuid=%s", "\251url", AtomFlags_Data_Text, optarg, false); APar_Unified_atom_Put(urlUUID, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); break; } case Meta_Information: { APar_ScanAtoms(ISObasemediafile); AtomicInfo *infoUUID = APar_uuid_atom_Init("moov.udta.meta.uuid=%s", "\251inf", AtomFlags_Data_Text, optarg, false); APar_Unified_atom_Put(infoUUID, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); break; } case Meta_uuid: { APar_ScanAtoms(ISObasemediafile); uint32_t uuid_dataType = 0; uint32_t desc_len = 0; uint8_t mime_len = 0; char *uuid_file_path = NULL; char *uuid_file_description = NULL; char *uuid_file_extn = NULL; char *uuid_file_mimetype = NULL; // char* uuid_file_filename = NULL; // a uuid in AP is a version 5 uuid created by getting a sha1 hash // of a string (the atom name) in a namespace ('AP.sf.net'). This // is guaranteed to be reproducible, so later it can be verified // that this uuid (which could come from anywhere), is in fact made // by AtomicParsley. This is achieved by storing the atom name // string right after the uuid, and is read back later and a new // uuid is created to see if it matches the discovered uuid. If // they match, it will print out or extract to a file; if not, only // its name will be displayed in the tree. // // --meta-uuid "\251foo" 1 'http://www.url.org' --meta-uuid "pdf1" // file /some/path/pic.pdf description="My Booty, Your Booty, // Djbouti" if (strcmp(argv[optind], "text") == 0 || strcmp(argv[optind], "1") == 0) uuid_dataType = AtomFlags_Data_Text; if (strcmp(argv[optind], "file") == 0) { uuid_dataType = AtomFlags_Data_uuid_binary; if (optind + 1 < argc) { if (true) { // TODO: test the file to see if it exists uuid_file_path = argv[optind + 1]; // get the file extension/suffix of the file to embed uuid_file_extn = strrchr(uuid_file_path, '.'); //'.' inclusive; say goodbye to AP-0.8.8.tar.bz2 //#ifdef _WIN32 //#define path_delim '\\' //#else //#define path_delim '/' //#endif // uuid_file_filename = strrchr(uuid_file_path, // path_delim)+1; //includes whatever // extensions } if (uuid_file_extn == NULL) { fprintf(stdout, "AP warning: embedding a file onto a uuid atom " "requires a file extension. Skipping.\n"); continue; } // copy a pointer to description int more_optional_args = 2; while (optind + more_optional_args < argc) { if (strncmp( argv[optind + more_optional_args], "description=", 12) == 0 && argv[optind + more_optional_args][12]) { uuid_file_description = argv[optind + more_optional_args] + 12; desc_len = strlen(uuid_file_description) + 1; //+1 for the trailing 1 byte NULL terminator } if (strncmp(argv[optind + more_optional_args], "mime-type=", 10) == 0 && argv[optind + more_optional_args][10]) { uuid_file_mimetype = argv[optind + more_optional_args] + 10; mime_len = strlen(uuid_file_mimetype) + 1; //+1 for the trailing 1 byte NULL terminator } if (strncmp(argv[optind + more_optional_args], "--", 2) == 0) { break; // we've hit another cli argument } more_optional_args++; } } } AtomicInfo *genericUUID = APar_uuid_atom_Init("moov.udta.meta.uuid=%s", optarg, uuid_dataType, argv[optind + 1], true); if (uuid_dataType == AtomFlags_Data_uuid_binary && genericUUID != NULL) { TestFileExistence(uuid_file_path, true); // format for a uuid atom set by AP: // 4 bytes - atom length as uin32_t // 4 bytes - atom name as iso 8859-1 atom name as a 4byte string set // to 'uuid' 16 bytes - the uuid; here a version 5 sha1-based hash // derived from a name in a namespace of 'AtomicParsley.sf.net' 4 bytes // - the name of the desired atom to create a uuid for (this "name" of // the uuid is the only cli accessible means of crafting the uuid) 4 // bytes - atom version & flags (currently 1 for 'text' or '88' for // binary attachment) 4 bytes - NULL space /////////////text or 1 for version/flags // X bytes - utf8 string, no null termination /////////////binary attachment or 88 for version/flags // 4 bytes - length of utf8 string describing the attachment // X bytes - utf8 string describing the attachment, null terminated // 1 byte - length of the file suffix (including the period) of the // originating file X bytes - utf8 string of the file suffix, null // terminated 1 byte - length of the MIME-type X bytes - utf8 string // holding the MIME-type, null terminated 4 bytes - length of the // attached binary data/file length X bytes - binary data uint32_t extn_len = strlen(uuid_file_extn) + 1; //+1 for the trailing 1 byte NULL terminator uint64_t file_len = findFileSize(uuid_file_path); APar_MetaData_atom_QuickInit(genericUUID->AtomicNumber, uuid_dataType, 20, extn_len + desc_len + file_len + 100); genericUUID->AtomicClassification = EXTENDED_ATOM; // it gets reset in QuickInit Above; force its proper // setting if (uuid_file_description == NULL || desc_len == 0) { APar_Unified_atom_Put( genericUUID, "[none]", UTF8_3GP_Style, 7, 32); // sets 4bytes desc_len, then 7bytes description (forced to // "[none]"=6 + 1 byte NULL =7) } else { APar_Unified_atom_Put(genericUUID, uuid_file_description, UTF8_3GP_Style, desc_len, 32); // sets 4bytes desc_len, then Xbytes // description (in that order) } APar_Unified_atom_Put(genericUUID, uuid_file_extn, UTF8_3GP_Style, extn_len, 8); // sets 1bytes desc_len, then Xbytes file // extension string (in that order) if (uuid_file_mimetype == NULL || mime_len == 0) { APar_Unified_atom_Put( genericUUID, "none", UTF8_3GP_Style, 5, 8); // sets 4bytes desc_len, then 5bytes description (forced to // "none" + 1byte null) } else { APar_Unified_atom_Put( genericUUID, uuid_file_mimetype, UTF8_3GP_Style, mime_len, 8); // sets 1 byte mime len, then Xbytes mime type } FILE *uuid_binfile = APar_OpenFile(uuid_file_path, "rb"); APar_Unified_atom_Put(genericUUID, NULL, UTF8_3GP_Style, file_len, 32); // store the data directly on the atom in AtomicData uint32_t bin_bytes_read = APar_ReadFile( genericUUID->AtomicData + (genericUUID->AtomicLength - 32), uuid_binfile, file_len); genericUUID->AtomicLength += bin_bytes_read; fclose(uuid_binfile); } else { // text type APar_Unified_atom_Put( genericUUID, argv[optind + 1], UTF8_iTunesStyle_Unlimited, 0, 0); } break; } case Opt_Extract_all_uuids: { APar_ScanAtoms(ISObasemediafile); char *output_path = NULL; if (optind + 1 == argc) { output_path = argv[optind]; } APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_Print_APuuid_atoms( ISObasemediafile, output_path, EXTRACT_ALL_UUID_BINARYS); APar_OpenISOBaseMediaFile(ISObasemediafile, false); exit(0); // never gets here break; } case Opt_Extract_a_uuid: { APar_ScanAtoms(ISObasemediafile); char *uuid_path = (char *)calloc(1, sizeof(char) * 256 + 1); char *uuid_binary_str = (char *)calloc(1, sizeof(char) * 20 + 1); char uuid_4char_name[16]; memset(uuid_4char_name, 0, 16); AtomicInfo *extractionAtom = NULL; UTF8Toisolat1((unsigned char *)&uuid_4char_name, 4, (unsigned char *)optarg, strlen(optarg)); APar_generate_uuid_from_atomname(uuid_4char_name, uuid_binary_str); // this will only append (and knock off) %s (anything) at the end of a // string uint16_t path_len = strlen("moov.udta.meta.uuid=%s"); memcpy(uuid_path, "moov.udta.meta.uuid=%s", path_len - 2); memcpy(uuid_path + (path_len - 2), uuid_binary_str, 16); extractionAtom = APar_FindAtom(uuid_path, false, EXTENDED_ATOM, 0, true); if (extractionAtom != NULL) { APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_Extract_uuid_binary_file(extractionAtom, ISObasemediafile, NULL); APar_OpenISOBaseMediaFile(ISObasemediafile, false); } free(uuid_path); uuid_path = NULL; free(uuid_binary_str); uuid_binary_str = NULL; exit(0); break; // never gets here } case Opt_Ipod_AVC_uuid: { if (deep_atom_scan == true) { if (strcmp(optarg, "1200") != 0) { fprintf(stdout, "the ipod-uuid has a single preset of '1200' which " "is required\n"); // 1200 might not be the only max // macroblock setting down the pike break; } uint8_t total_tracks = 0; uint8_t a_track = 0; // unused char atom_path[100]; AtomicInfo *video_desc_atom = NULL; memset(atom_path, 0, 100); APar_FindAtomInTrack( total_tracks, a_track, NULL); // With track_num set to 0, it will return the total trak // atom into total_tracks here. while (a_track < total_tracks) { a_track++; sprintf(atom_path, "moov.trak[%u].mdia.minf.stbl.stsd.avc1", a_track); video_desc_atom = APar_FindAtom(atom_path, false, VERSIONED_ATOM, 0, false); if (video_desc_atom != NULL) { uint16_t mb_t = APar_TestVideoDescription( video_desc_atom, APar_OpenFile(ISObasemediafile, "rb")); if (mb_t > 0 && mb_t <= 1200) { sprintf(atom_path, "moov.trak[%u].mdia.minf.stbl.stsd.avc1.uuid=", a_track); uint8_t uuid_baselen = (uint8_t)strlen(atom_path); APar_uuid_scanf(atom_path + uuid_baselen, "6b6840f2-5f24-4fc5-ba39-a51bcf0323f3"); APar_endian_uuid_bin_str_conversion(atom_path + uuid_baselen); APar_Generate_iPod_uuid(atom_path); } } } } else { fprintf( stdout, "the --DeepScan option is required for this operation. Skipping\n"); } break; } case Manual_atom_removal: { APar_ScanAtoms(ISObasemediafile); char *compliant_name = (char *)malloc(sizeof(char) * strlen(optarg) + 1); memset(compliant_name, 0, strlen(optarg) + 1); UTF8Toisolat1((unsigned char *)compliant_name, strlen(optarg), (unsigned char *)optarg, strlen(optarg)); if (strstr(optarg, "uuid=") != NULL) { uint16_t uuid_path_pos = 0; uint16_t uuid_path_len = strlen(optarg); while (compliant_name + uuid_path_pos < compliant_name + uuid_path_len) { if (strncmp(compliant_name + uuid_path_pos, "uuid=", 5) == 0) { uuid_path_pos += 5; break; } uuid_path_pos++; } if (strlen(compliant_name + uuid_path_pos) > 4) { // if =4 then it would be the deprecated form (or it should be, // if not it just won't find anything; no harm done) uint8_t uuid_len = APar_uuid_scanf(compliant_name + uuid_path_pos, optarg + uuid_path_pos); compliant_name[uuid_path_pos + uuid_len] = 0; } APar_RemoveAtom(compliant_name, EXTENDED_ATOM, 0); } else if (strcmp(compliant_name + (strlen(compliant_name) - 4), "data") == 0) { APar_RemoveAtom(compliant_name, VERSIONED_ATOM, 0); } else { size_t string_len = strlen(compliant_name); // reverseDNS atom path if (strstr(optarg, ":[") != NULL && compliant_name[string_len - 1] == ']') { APar_RemoveAtom(compliant_name, VERSIONED_ATOM, 0); // packed language asset } else if (strncmp(compliant_name + string_len - 9, ":lang=", 6) == 0) { uint16_t packed_lang = PackLanguage(compliant_name + string_len - 3, 0); memset(compliant_name + string_len - 9, 0, 1); APar_RemoveAtom(compliant_name, PACKED_LANG_ATOM, packed_lang); } else { APar_RemoveAtom(compliant_name, UNKNOWN_ATOM, 0); } } free(compliant_name); compliant_name = NULL; break; } // 3gp tags /* First, scan the file to get at atom tree (only happens once). Then take the cli args and look for optional arguments. All arguments begin with -- or -; other args are optional and are determined by directly testing arguments. Optional arguments common to the 3gp asset group (language, unicode, track/movie userdata) are extracted in find_optional_args. Setting assets in all tracks requires getting the number of tracks. Loop through either once (for movie & single track) or as many tracks there are for all tracks. Each pass through the loop, set the individual pieces of metadata. */ case _3GP_Title: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "title")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; // otherwise, APar_UserData_atom_Init will shift // to non-existing track 0 } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *title_asset = APar_UserData_atom_Init( "titl", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( title_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Author: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "author")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *author_asset = APar_UserData_atom_Init( "auth", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( author_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Performer: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "performer")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *performer_asset = APar_UserData_atom_Init( "perf", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( performer_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Genre: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "genre")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *genre_asset = APar_UserData_atom_Init( "gnre", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( genre_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Description: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "description")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *description_asset = APar_UserData_atom_Init( "dscp", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( description_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case ISO_Copyright: // ISO copyright atom common to all files that are // derivatives of the base media file format, identical // to.... case _3GP_Copyright: { // the 3gp copyright asset; this gets a test for // major branding (but only with the cli arg // --3gp-copyright). APar_ScanAtoms(ISObasemediafile); if (c == _3GP_Copyright) { if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "copyright")) { break; } } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *copyright_notice = APar_UserData_atom_Init( "cprt", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( copyright_notice, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Album: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "album")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; uint8_t tracknum = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 4); // cygle through the remaining independant arguments (before the next // --cli_flag) and figure out if any are useful to us; already have lang & // utf16 for (int i = 0; i <= 4; i++) { // 3 possible arguments for this tag (the first - which // doesn't count - is the data for the tag itself) if (argv[optind + i] && optind + i <= total_args) { if (strncmp(argv[optind + i], "trknum=", 7) == 0) { char *track_num = argv[optind + i]; strsep(&track_num, "="); tracknum = strtoul(track_num, NULL, 10); } if (*argv[optind + i] == '-') break; } } if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *album_asset = APar_UserData_atom_Init( "albm", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( album_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); if (tracknum != 0) { APar_Unified_atom_Put(album_asset, NULL, UTF8_3GP_Style, tracknum, 8); } } break; } case _3GP_Year: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "year")) { break; } uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; uint16_t year_tag = 0; if (argv[optind] && optind <= total_args) { if (strcmp(argv[optind], "movie") == 0) { userdata_area = MOVIE_LEVEL_ATOM; } if (strncmp(argv[optind], "track=", 6) == 0) { char *trak_idx = argv[optind]; strsep(&trak_idx, "="); selected_track = strtoul(trak_idx, NULL, 10); userdata_area = SINGLE_TRACK_ATOM; } else if (strcmp(argv[optind], "track") == 0) { userdata_area = ALL_TRACKS_ATOM; } } sscanf(optarg, "%" SCNu16, &year_tag); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *recordingyear_asset = APar_UserData_atom_Init( "yrrc", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, 0); APar_Unified_atom_Put( recordingyear_asset, NULL, UTF8_3GP_Style, year_tag, 16); } break; } case _3GP_Rating: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "rating")) { break; } char rating_entity[5] = { 'N', 'O', 'N', 'E', 0}; //'NONE' - thats what it will be if not provided char rating_criteria[5] = {'N', 'O', 'N', 'E', 0}; bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 5); for (int i = 0; i < 5; i++) { // 3 possible arguments for this tag (the first - which // doesn't count - is the data for the tag itself) if (argv[optind + i] && optind + i <= total_args) { if (strncmp(argv[optind + i], "entity=", 7) == 0) { char *entity = argv[optind + i]; strsep(&entity, "="); memcpy(&rating_entity, entity, 4); } if (strncmp(argv[optind + i], "criteria=", 9) == 0) { char *criteria = argv[optind + i]; strsep(&criteria, "="); memcpy(&rating_criteria, criteria, 4); } if (*argv[optind + i] == '-') break; // we've hit another cli argument } } if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *rating_asset = APar_UserData_atom_Init( "rtng", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put(rating_asset, NULL, UTF8_3GP_Style, UInt32FromBigEndian(rating_entity), 32); APar_Unified_atom_Put(rating_asset, NULL, UTF8_3GP_Style, UInt32FromBigEndian(rating_criteria), 32); APar_Unified_atom_Put( rating_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Classification: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "classification")) { break; } char classification_entity[5] = { 'N', 'O', 'N', 'E', 0}; //'NONE' - thats what it will be if not provided uint16_t classification_index = 0; bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 5); for (int i = 0; i < 4; i++) { // 3 possible arguments for this tag (the first - which // doesn't count - is the data for the tag itself) if (argv[optind + i] && optind + i <= total_args) { if (strncmp(argv[optind + i], "entity=", 7) == 0) { char *cls_entity = argv[optind + i]; strsep(&cls_entity, "="); memcpy(&classification_entity, cls_entity, 4); } if (strncmp(argv[optind + i], "index=", 6) == 0) { char *cls_idx = argv[optind + i]; strsep(&cls_idx, "="); sscanf(cls_idx, "%" SCNu16, &classification_index); } if (*argv[optind + i] == '-') break; // we've hit another cli argument } } if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *classification_asset = APar_UserData_atom_Init( "clsf", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put(classification_asset, NULL, UTF8_3GP_Style, UInt32FromBigEndian(classification_entity), 32); APar_Unified_atom_Put(classification_asset, NULL, UTF8_3GP_Style, classification_index, 16); APar_Unified_atom_Put( classification_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Keyword: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "keyword")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; char *formed_keyword_struct = NULL; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 4); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } if (strrchr(optarg, '=') != NULL) { // must be in the format of: keywords=foo1,foo2,foo3,foo4 char *arg_keywords = optarg; char *keywords_globbed = strsep(&arg_keywords, "="); // separate out 'keyword=' keywords_globbed = strsep(&arg_keywords, "="); // this is what we want to work on: just the keywords char *keyword_ptr = keywords_globbed; uint32_t keyword_strlen = strlen(keywords_globbed); uint8_t keyword_count = 0; uint32_t key_index = 0; if (keyword_strlen > 0) { // if there is anything past the = then it counts as a keyword keyword_count++; } while (true) { // count occurrences of comma here if (*keyword_ptr == ',') { keyword_count++; } keyword_ptr++; key_index++; if (keyword_strlen == key_index) { break; } } formed_keyword_struct = (char *)calloc( 1, sizeof(char) * (set_UTF16_text ? (keyword_strlen * 4) : (keyword_strlen * 2))); // *4 should carry utf16's BOM & TERM uint32_t keyword_struct_bytes = APar_3GP_Keyword_atom_Format(keywords_globbed, keyword_count, set_UTF16_text, formed_keyword_struct); for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *keyword_asset = APar_UserData_atom_Init( "kywd", keyword_strlen ? "temporary" : "", userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); // just a "temporary" valid string to satisfy a test // there if (keyword_strlen > 0) { APar_Unified_atom_Put( keyword_asset, NULL, UTF8_3GP_Style, packed_lang, 16); APar_Unified_atom_Put( keyword_asset, NULL, UTF8_3GP_Style, keyword_count, 8); APar_atom_Binary_Put( keyword_asset, formed_keyword_struct, keyword_struct_bytes, 3); } } if (formed_keyword_struct != NULL) { free(formed_keyword_struct); formed_keyword_struct = NULL; } } else { for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { APar_UserData_atom_Init("kywd", "", userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); } } break; } case _3GP_Location: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "location")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; double longitude = -73.98; // if you don't provide a place, you WILL be put right into // Central Park. Welcome to New York City... now go away. double latitude = 40.77; double altitude = 4.3; uint8_t role = 0; const char *astronomical_body = "Earth"; const char *additional_notes = "no notes"; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 10); for (int i = 0; i <= 10; i++) { // 9 possible arguments for this tag (the first - which // doesn't count - is the data for the tag itself) if (argv[optind + i] && optind + i <= total_args) { if (strncmp(argv[optind + i], "longitude=", 10) == 0) { char *_long = argv[optind + i]; strsep(&_long, "="); sscanf(_long, "%lf", &longitude); if (_long[strlen(_long) - 1] == 'W') { longitude *= -1; } } if (strncmp(argv[optind + i], "latitude=", 9) == 0) { char *_latt = argv[optind + i]; strsep(&_latt, "="); sscanf(_latt, "%lf", &latitude); if (_latt[strlen(_latt) - 1] == 'S') { latitude *= -1; } } if (strncmp(argv[optind + i], "altitude=", 9) == 0) { char *_alti = argv[optind + i]; strsep(&_alti, "="); sscanf(_alti, "%lf", &altitude); if (_alti[strlen(_alti) - 1] == 'B') { altitude *= -1; } } if (strncmp(argv[optind + i], "role=", 5) == 0) { char *_role = argv[optind + i]; strsep(&_role, "="); if (strcmp(_role, "shooting location") == 0 || strcmp(_role, "shooting") == 0) { role = 0; } else if (strcmp(_role, "real location") == 0 || strcmp(_role, "real") == 0) { role = 1; } else if (strcmp(_role, "fictional location") == 0 || strcmp(_role, "fictional") == 0) { role = 2; } } if (strncmp(argv[optind + i], "body=", 5) == 0) { char *_astrobody = argv[optind + i]; strsep(&_astrobody, "="); astronomical_body = _astrobody; } if (strncmp(argv[optind + i], "notes=", 6) == 0) { char *_add_notes = argv[optind + i]; strsep(&_add_notes, "="); additional_notes = _add_notes; } if (*argv[optind + i] == '-') break; // we've hit another cli argument } } // fprintf(stdout, "long, lat, alt = %lf %lf %lf\n", longitude, latitude, // altitude); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } if (longitude < -180.0 || longitude > 180.0 || latitude < -90.0 || latitude > 90.0) { fprintf(stdout, "AtomicParsley warning: longitude or latitude was " "invalid; skipping setting location\n"); } else { for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { // short location_3GP_atom = APar_UserData_atom_Init("moov.udta.loci", // optarg, packed_lang); AtomicInfo *location_asset = APar_UserData_atom_Init( "loci", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( location_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); APar_Unified_atom_Put(location_asset, NULL, false, role, 8); APar_Unified_atom_Put(location_asset, NULL, false, float_to_16x16bit_fixed_point(longitude), 32); APar_Unified_atom_Put(location_asset, NULL, false, float_to_16x16bit_fixed_point(latitude), 32); APar_Unified_atom_Put(location_asset, NULL, false, float_to_16x16bit_fixed_point(altitude), 32); APar_Unified_atom_Put( location_asset, astronomical_body, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), 0, 0); APar_Unified_atom_Put( location_asset, additional_notes, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), 0, 0); } } break; } case Meta_ReverseDNS_Form: { //--rDNSatom "mv-ma" name=iTuneEXTC // domain=com.apple.iTunes char *reverseDNS_atomname = NULL; char *reverseDNS_atomdomain = NULL; uint32_t rdns_atom_flags = AtomFlags_Data_Text; APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "reverse DNS form")) { break; } for (int i = 0; i <= 5 - 1; i++) { if (argv[optind + i] && optind + i <= argc) { if (strncmp(argv[optind + i], "name=", 5) == 0) { reverseDNS_atomname = argv[optind + i] + 5; } else if (strncmp(argv[optind + i], "domain=", 7) == 0) { reverseDNS_atomdomain = argv[optind + i] + 7; } else if (strncmp(argv[optind + i], "datatype=", 9) == 0) { sscanf(argv[optind + i] + 9, "%" SCNu32, &rdns_atom_flags); } if (*argv[optind + i] == '-') { break; // we've hit another cli argument } } } if (reverseDNS_atomname == NULL) { fprintf(stdout, "AtomicParsley warning: no name for the reverseDNS " "form was found. Skipping.\n"); } else if ((int)strlen(reverseDNS_atomname) != test_conforming_alpha_string(reverseDNS_atomname)) { fprintf(stdout, "AtomicParsley warning: Some part of the reverseDNS " "atom name was non-conforming. Skipping.\n"); } else if (reverseDNS_atomdomain == NULL) { fprintf(stdout, "AtomicParsley warning: no domain for the reverseDNS " "form was found. Skipping.\n"); } else if (rdns_atom_flags != AtomFlags_Data_Text) { fprintf(stdout, "AtomicParsley warning: currently, only the strings " "are supported in reverseDNS atoms. Skipping.\n"); } else { AtomicInfo *rDNS_data_atom = APar_reverseDNS_atom_Init(reverseDNS_atomname, optarg, &rdns_atom_flags, reverseDNS_atomdomain); APar_Unified_atom_Put( rDNS_data_atom, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); } break; } case Meta_rDNS_rating: { const char *media_rating = Expand_cli_mediastring(optarg); uint32_t rDNS_data_flags = AtomFlags_Data_Text; APar_ScanAtoms(ISObasemediafile); if (media_rating != NULL || strlen(optarg) == 0) { AtomicInfo *rDNS_rating_data_atom = APar_reverseDNS_atom_Init( "iTunEXTC", media_rating, &rDNS_data_flags, "com.apple.iTunes"); APar_Unified_atom_Put(rDNS_rating_data_atom, media_rating, UTF8_iTunesStyle_Unlimited, 0, 0); } break; } case Meta_ID3v2Tag: { const char *target_frame_ID = NULL; uint16_t packed_lang = 0; uint8_t char_encoding = TE_UTF8; // utf8 is the default encoding char meta_container = 0 - MOVIE_LEVEL_ATOM; bool multistring = false; APar_ScanAtoms(ISObasemediafile); // limit the files that can be tagged with meta.ID32 atoms. The file has // to conform to the ISO BMFFv2 in order for a 'meta' atom. This should // exclude files branded as 3gp5 for example, except it doesn't always. // The test is for a compatible brand (of a v2 ISO MBFF). Quicktime writes // some 3GPP files as 3gp5 with a compatible brand of mp42, so tagging // works on these files. Not when you use timed text though. if (!APar_assert(parsedAtoms[0].ancillary_data != 0 || (metadata_style >= THIRD_GEN_PARTNER_VER1_REL7 && metadata_style < MOTIONJPEG2000), 3, NULL)) { break; } AdjunctArgs *id3args = (AdjunctArgs *)malloc(sizeof(AdjunctArgs)); id3args->targetLang = NULL; // it will default later to "eng" id3args->descripArg = NULL; id3args->mimeArg = NULL; id3args->pictypeArg = NULL; id3args->ratingArg = NULL; id3args->dataArg = NULL; id3args->pictype_uint8 = 0; id3args->groupSymbol = 0; id3args->zlibCompressed = false; id3args->multistringtext = false; target_frame_ID = ConvertCLIFrameStr_TO_frameID(optarg); if (target_frame_ID == NULL) { target_frame_ID = optarg; } int frameType = FrameStr_TO_FrameType(target_frame_ID); if (frameType >= 0) { if (TestCLI_for_FrameParams(frameType, 0)) { id3args->descripArg = find_ID3_optarg(argv, optind, "desc="); } if (TestCLI_for_FrameParams(frameType, 1)) { id3args->mimeArg = find_ID3_optarg(argv, optind, "mimetype="); } if (TestCLI_for_FrameParams(frameType, 2)) { id3args->pictypeArg = find_ID3_optarg(argv, optind, "imagetype="); } if (TestCLI_for_FrameParams(frameType, 3)) { id3args->dataArg = find_ID3_optarg(argv, optind, "uniqueID="); } if (TestCLI_for_FrameParams(frameType, 4)) { id3args->filenameArg = find_ID3_optarg(argv, optind, "filename="); } if (TestCLI_for_FrameParams(frameType, 5)) { id3args->ratingArg = find_ID3_optarg(argv, optind, "rating="); } if (TestCLI_for_FrameParams(frameType, 6)) { id3args->dataArg = find_ID3_optarg(argv, optind, "counter="); } if (TestCLI_for_FrameParams(frameType, 7)) { id3args->dataArg = find_ID3_optarg(argv, optind, "data="); } if (TestCLI_for_FrameParams(frameType, 8)) { id3args->dataArg = find_ID3_optarg(argv, optind, "data="); } if (*find_ID3_optarg(argv, optind, "compressed") == '1') { id3args->zlibCompressed = true; } const char *groupsymbol = find_ID3_optarg(argv, optind, "groupsymbol="); if (groupsymbol[0] == '0' && groupsymbol[1] == 'x') { id3args->groupSymbol = strtoul(groupsymbol, NULL, 16); if (id3args->groupSymbol < 0x80 || id3args->groupSymbol > 0xF0) id3args->groupSymbol = 0; } } scan_ID3_optargs(argv, optind, id3args->targetLang, packed_lang, char_encoding, &meta_container, multistring); if (id3args->targetLang == NULL) id3args->targetLang = "eng"; APar_OpenISOBaseMediaFile( ISObasemediafile, true); // if not already scanned, the whole tag for // *this* ID32 atom needs to be read from file AtomicInfo *id32_atom = APar_ID32_atom_Init( target_frame_ID, meta_container, id3args->targetLang, packed_lang); if (strcmp(argv[optind + 0], "extract") == 0 && (strcmp(target_frame_ID, "APIC") == 0 || strcmp(target_frame_ID, "GEOB") == 0)) { if (id32_atom != NULL) { APar_Extract_ID3v2_file( id32_atom, target_frame_ID, ISObasemediafile, NULL, id3args); APar_OpenISOBaseMediaFile(ISObasemediafile, false); } exit(0); } APar_OpenISOBaseMediaFile(ISObasemediafile, false); APar_ID3FrameAmmend( id32_atom, target_frame_ID, argv[optind + 0], id3args, char_encoding); free(id3args); id3args = NULL; break; } // utility functions case Metadata_Purge: { APar_ScanAtoms(ISObasemediafile); APar_RemoveAtom("moov.udta.meta.ilst", SIMPLE_ATOM, 0); break; } case UserData_Purge: { APar_ScanAtoms(ISObasemediafile); APar_RemoveAtom("moov.udta", SIMPLE_ATOM, 0); break; } case foobar_purge: { APar_ScanAtoms(ISObasemediafile); APar_RemoveAtom("moov.udta.tags", UNKNOWN_ATOM, 0); break; } case Opt_FreeFree: { APar_ScanAtoms(ISObasemediafile); int free_level = -1; if (argv[optind]) { sscanf(argv[optind], "%i", &free_level); } APar_freefree(free_level); break; } case OPT_OverWrite: { alter_original = true; break; } #if defined(_WIN32) case OPT_PreserveTimeStamps: { alter_original = true; preserve_timestamps = true; break; } #endif case Meta_dump: { APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_MetadataFileDump(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, false); APar_FreeMemory(); exit(0); } case OPT_NoOptimize: { force_existing_hierarchy = true; break; } case OPT_OutputFile: { output_file = optarg; break; } case Meta_movementCount: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "movementCount")) { break; } uint8_t data_value = 0; sscanf(optarg, "%" SCNu8, &data_value); AtomicInfo *atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251mvc.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put( atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 8); break; } case Meta_movementName: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "movementName")) { break; } AtomicInfo *atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251mvn.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_movementNumber: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "movementNumber")) { break; } uint8_t data_value = 0; sscanf(optarg, "%" SCNu8, &data_value); AtomicInfo *atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251mvi.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put( atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 8); break; } case Meta_showWorkMovement: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "showWorkMovement")) { break; } AtomicInfo *atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.shwm.data", optarg, AtomFlags_Data_Binary); APar_Unified_atom_Put( atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_work: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "work")) { break; } AtomicInfo *atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251wrk.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } } /* end switch */ } /* end while */ // after all the modifications are enacted on the tree in memory, THEN // write out the changes if (modified_atoms) { #if defined(_WIN32) HANDLE hFile, hFileOut; FILETIME createTime, accessTime, writeTime; if (preserve_timestamps == true) { hFile = APar_OpenFileWin32(ISObasemediafile, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); if (hFile != INVALID_HANDLE_VALUE) { GetFileTime(hFile, &createTime, &accessTime, &writeTime); CloseHandle(hFile); } else { fprintf(stdout, "\n Invalid HANDLE!"); } } #endif APar_DetermineAtomLengths(); APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_WriteFile(ISObasemediafile, output_file, alter_original); #if defined(_WIN32) if (preserve_timestamps == true) { hFileOut = APar_OpenFileWin32(ISObasemediafile, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); if (hFileOut != INVALID_HANDLE_VALUE) { SetFileTime(hFileOut, &createTime, &accessTime, &writeTime); CloseHandle(hFileOut); } } #endif if (!alter_original) { // The file was opened orignally as read-only; when it came time to // writeback into the original file, that FILE was closed, and a // new one opened with write abilities, so to close a FILE that no // longer exists would.... be retarded. APar_OpenISOBaseMediaFile(ISObasemediafile, false); } } else { if (ISObasemediafile != NULL && argc > 3 && !deep_atom_scan) { fprintf(stdout, "No changes.\n"); } } APar_FreeMemory(); return 0; } #if defined(_WIN32) #if !defined(__CYGWIN__) int wmain(int argc, wchar_t *arguments[]) { int return_val = 0; uint16_t name_len = wcslen(arguments[0]); if (name_len >= 9 && _wcsicmp(arguments[0] + (name_len - 9), L"-utf8.exe") == 0) { UnicodeOutputStatus = UNIVERSAL_UTF8; } else { UnicodeOutputStatus = WIN32_UTF16; } char **argv = (char **)calloc(argc + 1, sizeof(char *)); // for native Win32 & full unicode support (well, cli arguments anyway), // take whatever 16bit unicode windows gives (utf16le), and convert // EVERYTHING that is sends to utf8; use those utf8 strings (mercifully // subject to familiar standby's like strlen) to pass around the program // like getopt_long to get cli options; convert from utf8 filenames as // required for unicode filename support on Windows using wide file // functions. for (int z = 0; z < argc; z++) { uint32_t wchar_length = wcslen(arguments[z]) + 1; argv[z] = (char *)malloc(sizeof(char) * 8 * wchar_length); memset(argv[z], 0, 8 * wchar_length); if (UnicodeOutputStatus == WIN32_UTF16) { UTF16LEToUTF8((unsigned char *)argv[z], 8 * wchar_length, (unsigned char *)arguments[z], wchar_length * 2); } else { strip_bogusUTF16toRawUTF8((unsigned char *)argv[z], 8 * wchar_length, arguments[z], wchar_length); } } argv[argc] = NULL; return_val = real_main(argc, argv); for (int free_cnt = 0; free_cnt < argc; free_cnt++) { free(argv[free_cnt]); argv[free_cnt] = NULL; } free(argv); argv = NULL; return return_val; } #ifdef __MINGW32__ int main() { int argc; wchar_t **argv = CommandLineToArgvW(GetCommandLineW(), &argc); return wmain(argc, argv); } #endif #else // defined __CYGWIN__ int main(int argc, char *argv[]) { size_t name_len = strlen(argv[0]); if (name_len >= 5 && (strcmp(argv[0] + (name_len - 5), "-utf8") == 0 || strcmp(argv[0] + (name_len - 5), "-UTF8") == 0)) { UnicodeOutputStatus = UNIVERSAL_UTF8; } else { UnicodeOutputStatus = WIN32_UTF16; } return real_main(argc, argv); } #endif #else int main(int argc, char *argv[]) { return real_main(argc, argv); } #endif /* vim:ts=4:sw=4:et: */ atomicparsley-20240608.083822.1ed9031/src/metalist.cpp000066400000000000000000002344421463107535600215270ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - metalist.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C) 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #include "AtomicParsley.h" bool BOM_printed = false; uint32_t APar_ProvideTallyForAtom(const char *atom_name) { uint32_t tally_for_atom = 0; short iter = parsedAtoms[0].NextAtomNumber; while (true) { if (memcmp(parsedAtoms[iter].AtomicName, atom_name, 4) == 0) { if (parsedAtoms[iter].AtomicLength == 0) { tally_for_atom += file_size - parsedAtoms[iter].AtomicStart; } else if (parsedAtoms[iter].AtomicLength == 1) { tally_for_atom += parsedAtoms[iter].AtomicLengthExtended; } else { tally_for_atom += parsedAtoms[iter].AtomicLength; } } if (iter == 0) { break; } else { iter = parsedAtoms[iter].NextAtomNumber; } } return tally_for_atom; } void printBOM() { if (BOM_printed) return; #if defined(_WIN32) && !defined(__CYGWIN__) if (UnicodeOutputStatus == WIN32_UTF16) { APar_unicode_win32Printout(L"\xEF\xBB\xBF", "\xEF\xBB\xBF"); } #else fprintf(stdout, "\xEF\xBB\xBF"); // Default to output of a UTF-8 BOM #endif BOM_printed = true; return; } #if defined(_WIN32) && !defined(__CYGWIN__) void APar_unicode_win32Printout( wchar_t *unicode_out, char * utf8_out) { // based on // http://blogs.msdn.com/junfeng/archive/2004/02/25/79621.aspx // its possible that this isn't even available on windows95 DWORD dwBytesWritten; DWORD fdwMode; HANDLE outHandle = GetStdHandle(STD_OUTPUT_HANDLE); // ThreadLocale adjustment, resource loading, etc. is skipped if ((GetFileType(outHandle) & FILE_TYPE_CHAR) && GetConsoleMode(outHandle, &fdwMode)) { if (wcsncmp(unicode_out, L"\xEF\xBB\xBF", 3) != 0) { // skip BOM when writing directly to the console WriteConsoleW( outHandle, unicode_out, wcslen(unicode_out), &dwBytesWritten, 0); } } else { // writing out to a file. Everything will be written out in utf8 to the // file. fprintf(stdout, "%s", utf8_out); } return; } #endif void APar_fprintf_UTF8_data(const char *utf8_encoded_data) { #if defined(_WIN32) && !defined(__CYGWIN__) if (GetVersion() & 0x80000000 || UnicodeOutputStatus == UNIVERSAL_UTF8) { fprintf(stdout, "%s", utf8_encoded_data); // just printout the raw utf8 bytes (not // characters) under pre-NT windows } else { wchar_t *utf16_data = Convert_multibyteUTF8_to_wchar(utf8_encoded_data); fflush(stdout); APar_unicode_win32Printout(utf16_data, (char *)utf8_encoded_data); fflush(stdout); free(utf16_data); utf16_data = NULL; } #else fprintf(stdout, "%s", utf8_encoded_data); #endif return; } void APar_Mark_UserData_area(uint8_t track_num, short userdata_atom, bool quantum_listing) { if (quantum_listing && track_num > 0) { fprintf(stdout, "User data; level: track=%u; atom \"%s\" ", track_num, parsedAtoms[userdata_atom].AtomicName); } else if (quantum_listing && track_num == 0) { fprintf(stdout, "User data; level: movie; atom \"%s\" ", parsedAtoms[userdata_atom].AtomicName); } else { fprintf(stdout, "User data \"%s\" ", parsedAtoms[userdata_atom].AtomicName); } return; } // the difference between APar_PrintUnicodeAssest above and // APar_SimplePrintUnicodeAssest below is: APar_PrintUnicodeAssest contains the // entire contents of the atom, NULL bytes and all APar_SimplePrintUnicodeAssest // contains a purely unicode string (either utf8 or utf16 with BOM) and slight // output formatting differences void APar_SimplePrintUnicodeAssest(char *unicode_string, int asset_length, bool print_encoding) { // 3gp files if (strncmp(unicode_string, "\xFE\xFF", 2) == 0) { // utf16 if (print_encoding) { fprintf(stdout, " (utf16): "); } unsigned char *utf8_data = Convert_multibyteUTF16_to_UTF8( unicode_string, asset_length * 6, asset_length); #if defined(_WIN32) && !defined(__CYGWIN__) if (GetVersion() & 0x80000000 || UnicodeOutputStatus == UNIVERSAL_UTF8) { // pre-NT or AP-utf8.exe (pish, thats my win98se, // and without unicows support convert utf16toutf8 // and output raw bytes) unsigned char *utf8_data = Convert_multibyteUTF16_to_UTF8( unicode_string, asset_length * 6, asset_length - 14); fprintf(stdout, "%s", utf8_data); free(utf8_data); utf8_data = NULL; } else { wchar_t *utf16_data = Convert_multibyteUTF16_to_wchar( unicode_string, asset_length / 2, true); // wchar_t* utf16_data = Convert_multibyteUTF16_to_wchar(unicode_string, // (asset_length / 2) + 1, true); APar_unicode_win32Printout(utf16_data, (char *)utf8_data); free(utf16_data); utf16_data = NULL; } #else fprintf(stdout, "%s", utf8_data); #endif free(utf8_data); utf8_data = NULL; } else { // utf8 if (print_encoding) { fprintf(stdout, " (utf8): "); } APar_fprintf_UTF8_data(unicode_string); } return; } /////////////////////////////////////////////////////////////////////////////////////// // embedded file extraction // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_Extract_uuid_binary_file uuid_atom - pointer to the struct holding the information describing the target atom originating_file - the full file path string to the parsed file output_path - a (possibly null) string where the embedded file will be extracted to If the output path is a null pointer, create a new path derived from originating file name & path - strip off the extension and use that as the base. Read into memory the contents of that particular uuid atom. Glob onto the base path the atom name and then the suffix that was embedded along with the file. Write out the file to the now fully formed uuid_outfile path. ----------------------*/ void APar_Extract_uuid_binary_file(AtomicInfo *uuid_atom, const char *originating_file, char *output_path) { uint32_t path_len = 0; uint64_t atom_offsets = 0; char *uuid_outfile = (char *)calloc( 1, sizeof(char) * MAXPATHLEN + 1); // malloc a new string because it may be a // cli arg for a specific output path if (output_path == NULL) { const char *orig_suffix = strrchr(originating_file, '.'); if (orig_suffix == NULL) { fprintf(stdout, "AP warning: a file extension for the input file was not " "found.\n\tGlobbing onto original filename...\n"); path_len = strlen(originating_file); memcpy(uuid_outfile, originating_file, path_len); } else { path_len = orig_suffix - originating_file; memcpy(uuid_outfile, originating_file, path_len); } } else { path_len = strlen(output_path); memcpy(uuid_outfile, output_path, path_len); } char *uuid_payload = (char *)calloc(1, sizeof(char) * (uuid_atom->AtomicLength - 36 + 1)); APar_readX(uuid_payload, source_file, uuid_atom->AtomicStart + 36, uuid_atom->AtomicLength - 36); uint32_t descrip_len = UInt32FromBigEndian(uuid_payload); atom_offsets += 4 + descrip_len; uint8_t suffix_len = (uint8_t)uuid_payload[atom_offsets]; char *file_suffix = (char *)calloc(1, sizeof(char) * suffix_len + 16); memcpy(file_suffix, uuid_payload + atom_offsets + 1, suffix_len); atom_offsets += 1 + suffix_len; uint8_t mime_len = (uint8_t)uuid_payload[atom_offsets]; uint64_t mimetype_string = atom_offsets + 1; atom_offsets += 1 + mime_len; uint64_t bin_len = UInt32FromBigEndian(uuid_payload + atom_offsets); atom_offsets += 4; sprintf(uuid_outfile + path_len, "-%s-uuid%s", uuid_atom->uuid_ap_atomname, file_suffix); FILE *outfile = APar_OpenFile(uuid_outfile, "wb"); if (outfile != NULL) { fwrite(uuid_payload + atom_offsets, (size_t)bin_len, 1, outfile); fclose(outfile); fprintf(stdout, "Extracted uuid=%s attachment (mime-type=%s) to file: ", uuid_atom->uuid_ap_atomname, uuid_payload + mimetype_string); APar_fprintf_UTF8_data(uuid_outfile); fprintf(stdout, "\n"); } free(uuid_payload); uuid_payload = NULL; free(uuid_outfile); uuid_outfile = NULL; free(file_suffix); file_suffix = NULL; return; } void APar_ExtractAAC_Artwork(short this_atom_num, char *pic_output_path, short artwork_count) { char *base_outpath = (char *)malloc(sizeof(char) * MAXPATHLEN + 1); if (snprintf(base_outpath, MAXPATHLEN + 1, "%s_artwork_%d", pic_output_path, artwork_count) > MAXPATHLEN) { free(base_outpath); return; } char *art_payload = (char *)malloc( sizeof(char) * (parsedAtoms[this_atom_num].AtomicLength - 16) + 1); memset(art_payload, 0, (parsedAtoms[this_atom_num].AtomicLength - 16) + 1); APar_readX(art_payload, source_file, parsedAtoms[this_atom_num].AtomicStart + 16, parsedAtoms[this_atom_num].AtomicLength - 16); char *suffix = (char *)malloc(sizeof(char) * 5); memset(suffix, 0, sizeof(char) * 5); if (memcmp(art_payload, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) == 0) { strcpy(suffix, ".png"); } else if (memcmp(art_payload, "\xFF\xD8\xFF", 3) == 0) { strcpy(suffix, ".jpg"); } strcat(base_outpath, suffix); FILE *outfile = APar_OpenFile(base_outpath, "wb"); if (outfile != NULL) { fwrite(art_payload, (size_t)(parsedAtoms[this_atom_num].AtomicLength - 16), 1, outfile); fclose(outfile); fprintf(stdout, "Extracted artwork to file: "); APar_fprintf_UTF8_data(base_outpath); fprintf(stdout, "\n"); } free(base_outpath); free(art_payload); free(suffix); return; } /*---------------------- APar_ImageExtractTest buffer - pointer to raw image data id3args - *currently unused* when testing raw image data from an image file, results like mimetype & imagetype will be placed here Loop through the ImageList array and see if the first few bytes in the image data in buffer match any of the known image_binaryheader types listed. If it does, and its png, do a further test to see if its type 0x01 which requires it to be 32x32 ----------------------*/ ImageFileFormatDefinition *APar_ImageExtractTest(char *buffer, AdjunctArgs *id3args) { ImageFileFormatDefinition *thisImage = NULL; uint8_t total_image_tests = ImageListMembers(); for (uint8_t itest = 0; itest < total_image_tests; itest++) { if (ImageList[itest].image_testbytes == 0) { if (id3args != NULL) { id3args->mimeArg = ImageList[itest].image_mimetype; } return &ImageList[itest]; } else if (memcmp(buffer, ImageList[itest].image_binaryheader, ImageList[itest].image_testbytes) == 0) { if (id3args != NULL) { id3args->mimeArg = ImageList[itest].image_mimetype; if (id3args->pictype_uint8 == 0x01) { if (memcmp(buffer + 16, "\x00\x00\x00\x20\x00\x00\x00\x20", 8) != 0 && itest != 2) { id3args->pictype_uint8 = 0x02; } } } thisImage = &ImageList[itest]; break; } } return thisImage; } /*---------------------- APar_Extract_ID3v2_file id32_atom - pointer to the AtomicInfo ID32 atom that contains this while ID3 tag (containing all the frames like APIC) frame_str - either APIC or GEOB originfile - the originating mpeg-4 file that contains the ID32 atom destination_folder - *currently not used* TODO: extract to this folder id3args - *currently not used* TODO: extract by mimetype or imagetype or description Extracts (all) files of a particular frame type (APIC or GEOB - GEOB is currently not implemented) out to a file next to the originating mpeg-4 file. First, match frame_str to get the internal frameID number for APIC/GEOB frame. Locate the .ext of the origin file, duplicate the path including the basename (excluding the extension. Loop through the linked list of ID3v2Frame and search for the internal frameID number. When an image is found, test the data that the image contains and determine file extension from the ImageFileFormatDefinition structure (containing some popular image format/extension definitions). In combination with the file extension, use the image description and image type to create the name of the output file. The image (which if was compressed on disc was expanded when read in) and simply write out its data (stored in the 5th member of the frame's field strings. ----------------------*/ void APar_Extract_ID3v2_file(AtomicInfo *id32_atom, const char *frame_str, const char *originfile, const char *destination_folder, AdjunctArgs *id3args) { uint16_t iter = 0; ID3v2Frame *eval_frame = NULL; uint32_t basepath_len = 0; char *extract_filename = NULL; int frameID = MatchID3FrameIDstr( frame_str, id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion); int frameType = KnownFrames[frameID + 1].ID3v2_FrameType; if (destination_folder == NULL) { basepath_len = (strrchr(originfile, '.') - originfile); } if (frameType == ID3_ATTACHED_PICTURE_FRAME || frameType == ID3_ATTACHED_OBJECT_FRAME) { if (id32_atom->ID32_TagInfo->ID3v2_FirstFrame == NULL) return; eval_frame = id32_atom->ID32_TagInfo->ID3v2_FirstFrame; extract_filename = (char *)malloc(sizeof(char *) * MAXPATHLEN + 1); while (eval_frame != NULL) { if (frameType == eval_frame->ID3v2_FrameType) { memset(extract_filename, 0, sizeof(char *) * MAXPATHLEN + 1); memcpy(extract_filename, originfile, basepath_len); iter++; if (eval_frame->ID3v2_FrameType == ID3_ATTACHED_PICTURE_FRAME) { ImageFileFormatDefinition *thisimage = APar_ImageExtractTest( (eval_frame->ID3v2_Frame_Fields + 4)->field_string, NULL); char *img_description = APar_ConvertField_to_UTF8(eval_frame, ID3_DESCRIPTION_FIELD); sprintf( extract_filename + basepath_len, "-img#%u-(desc=%s)-0x%02X%s", iter, img_description, (uint8_t)((eval_frame->ID3v2_Frame_Fields + 2)->field_string[0]), thisimage->image_fileextn); if (img_description != NULL) { free(img_description); img_description = NULL; } } else { char *obj_description = APar_ConvertField_to_UTF8(eval_frame, ID3_DESCRIPTION_FIELD); char *obj_filename = APar_ConvertField_to_UTF8(eval_frame, ID3_FILENAME_FIELD); sprintf(extract_filename + basepath_len, "-obj#%u-(desc=%s)-%s", iter, obj_description, obj_filename); if (obj_description != NULL) { free(obj_description); obj_description = NULL; } if (obj_filename != NULL) { free(obj_filename); obj_filename = NULL; } } FILE *extractfile = APar_OpenFile(extract_filename, "wb"); if (extractfile != NULL) { fwrite((eval_frame->ID3v2_Frame_Fields + 4)->field_string, (size_t)((eval_frame->ID3v2_Frame_Fields + 4)->field_length), 1, extractfile); fclose(extractfile); fprintf( stdout, "Extracted %s to file: %s\n", (frameType == ID3_ATTACHED_PICTURE_FRAME ? "artwork" : "object"), extract_filename); } } eval_frame = eval_frame->ID3v2_NextFrame; } } if (extract_filename != NULL) { free(extract_filename); extract_filename = NULL; } return; } /////////////////////////////////////////////////////////////////////////////////////// // iTunes-style metadata listings // /////////////////////////////////////////////////////////////////////////////////////// void APar_ExtractDataAtom(int this_atom_number) { if (source_file != NULL) { AtomicInfo *thisAtom = &parsedAtoms[this_atom_number]; char *parent_atom_name; AtomicInfo parent_atom_stats = parsedAtoms[this_atom_number - 1]; parent_atom_name = parent_atom_stats.AtomicName; uint32_t min_atom_datasize = 12; uint32_t atom_header_size = 16; if (thisAtom->AtomicClassification == EXTENDED_ATOM) { if (thisAtom->uuid_style == UUID_DEPRECATED_FORM) { min_atom_datasize += 4; atom_header_size += 4; } else { min_atom_datasize = 36; atom_header_size = 36; } } if (thisAtom->AtomicLength > min_atom_datasize) { char *data_payload = (char *)malloc( sizeof(char) * (thisAtom->AtomicLength - atom_header_size + 1)); memset(data_payload, 0, sizeof(char) * (thisAtom->AtomicLength - atom_header_size + 1)); APar_readX(data_payload, source_file, thisAtom->AtomicStart + atom_header_size, thisAtom->AtomicLength - atom_header_size); if (thisAtom->AtomicVerFlags == AtomFlags_Data_Text) { if (thisAtom->AtomicLength < (atom_header_size + 4)) { // tvnn was showing up with 4 chars instead of 3; easier to null it // out for now data_payload[thisAtom->AtomicLength - atom_header_size] = '\00'; } APar_fprintf_UTF8_data(data_payload); fprintf(stdout, "\n"); } else { if ((memcmp(parent_atom_name, "trkn", 4) == 0) || (memcmp(parent_atom_name, "disk", 4) == 0)) { if (UInt16FromBigEndian(data_payload + 4) != 0) { fprintf(stdout, "%u of %u\n", UInt16FromBigEndian(data_payload + 2), UInt16FromBigEndian(data_payload + 4)); } else { fprintf(stdout, "%u\n", UInt16FromBigEndian(data_payload + 2)); } } else if (strncmp(parent_atom_name, "gnre", 4) == 0) { if (thisAtom->AtomicLength - atom_header_size < 3) { // oh, a 1byte int for genre number char *genre_string = GenreIntToString(UInt16FromBigEndian(data_payload)); if (genre_string != NULL) { fprintf(stdout, "%s\n", genre_string); } else { fprintf(stdout, " out of bound value - %u\n", UInt16FromBigEndian(data_payload)); } } else { fprintf(stdout, " out of bound value - %u\n", UInt16FromBigEndian(data_payload)); } } else if ((strncmp(parent_atom_name, "purl", 4) == 0) || (strncmp(parent_atom_name, "egid", 4) == 0)) { fprintf(stdout, "%s\n", data_payload); } else { if (thisAtom->AtomicVerFlags == AtomFlags_Data_UInt && (thisAtom->AtomicLength <= 20 || thisAtom->AtomicLength == 24)) { uint8_t bytes_rep = (uint8_t)(thisAtom->AtomicLength - atom_header_size); switch (bytes_rep) { case 1: { if ((memcmp(parent_atom_name, "cpil", 4) == 0) || (memcmp(parent_atom_name, "pcst", 4) == 0) || (memcmp(parent_atom_name, "pgap", 4) == 0)) { if (data_payload[0] == 1) { fprintf(stdout, "true\n"); } else { fprintf(stdout, "false\n"); } } else if (strncmp(parent_atom_name, "stik", 4) == 0) { stiks *returned_stik = MatchStikNumber((uint8_t)data_payload[0]); if (returned_stik != NULL) { fprintf(stdout, "%s\n", returned_stik->stik_string); } else { fprintf( stdout, "Unknown value: %u\n", (uint8_t)data_payload[0]); } } else if (strncmp(parent_atom_name, "rtng", 4) == 0) { // okay, this is definitely an 8-bit number if (data_payload[0] == 2) { fprintf(stdout, "Clean Content\n"); } else if (data_payload[0] != 0) { fprintf(stdout, "Explicit Content\n"); } else { fprintf(stdout, "Inoffensive\n"); } } else { fprintf(stdout, "%u\n", (uint8_t)data_payload[0]); } break; } case 2: { // tmpo fprintf(stdout, "%u\n", UInt16FromBigEndian(data_payload)); break; } case 4: { // tves, tvsn if (memcmp(parent_atom_name, "sfID", 4) == 0) { sfIDs *this_store = MatchStoreFrontNumber(UInt32FromBigEndian(data_payload)); if (this_store != NULL) { fprintf(stdout, "%s (%" PRIu32 ")\n", this_store->storefront_string, this_store->storefront_number); } else { fprintf(stdout, "Unknown (%" PRIu32 ")\n", UInt32FromBigEndian(data_payload)); } } else { fprintf( stdout, "%" PRIu32 "\n", UInt32FromBigEndian(data_payload)); } break; } case 8: { fprintf( stdout, "%" PRIu64 "\n", UInt64FromBigEndian(data_payload)); break; } } } else if (thisAtom->AtomicClassification == EXTENDED_ATOM && thisAtom->AtomicVerFlags == AtomFlags_Data_uuid_binary && thisAtom->uuid_style == UUID_AP_SHA1_NAMESPACE) { uint64_t offset_into_uuiddata = 0; uint64_t descrip_len = UInt32FromBigEndian(data_payload); offset_into_uuiddata += 4; char *uuid_description = (char *)calloc(1, sizeof(char) * descrip_len + 16); // char uuid_description[descrip_len+1]; memcpy(uuid_description, data_payload + offset_into_uuiddata, descrip_len); offset_into_uuiddata += descrip_len; uint8_t suffix_len = (uint8_t)data_payload[offset_into_uuiddata]; offset_into_uuiddata += 1; char *file_suffix = (char *)calloc(1, sizeof(char) * suffix_len + 16); // char file_suffix[suffix_len+1]; memcpy( file_suffix, data_payload + offset_into_uuiddata, suffix_len); offset_into_uuiddata += suffix_len; uint8_t mime_len = (uint8_t)data_payload[offset_into_uuiddata]; offset_into_uuiddata += 1; char *uuid_mimetype = (char *)calloc(1, sizeof(char) * mime_len + 16); // char uuid_mimetype[mime_len+1]; memcpy( uuid_mimetype, data_payload + offset_into_uuiddata, mime_len); fprintf(stdout, "FILE%s; mime-type=%s; description=%s\n", file_suffix, uuid_mimetype, uuid_description); free(uuid_description); uuid_description = NULL; free(file_suffix); file_suffix = NULL; free(uuid_mimetype); uuid_description = NULL; } else { // purl & egid would end up here too, but Apple switched it // to a text string (0x00), so gets taken care above // explicitly fprintf(stdout, "hex 0x"); for (int hexx = 1; hexx <= (int)(thisAtom->AtomicLength - atom_header_size); ++hexx) { fprintf(stdout, "%02X", (uint8_t)data_payload[hexx - 1]); if ((hexx % 4) == 0 && hexx >= 4) { fprintf(stdout, " "); } if ((hexx % 16) == 0 && hexx > 16) { fprintf(stdout, "\n\t\t\t"); } if (hexx == (int)(thisAtom->AtomicLength - atom_header_size)) { fprintf(stdout, "\n"); } } } // end if AtomFlags_Data_UInt } free(data_payload); data_payload = NULL; } } } return; } void APar_Print_iTunesData(const char *path, char *output_path, uint8_t supplemental_info, uint8_t target_information, AtomicInfo *ilstAtom) { printBOM(); short artwork_count = 0; if (ilstAtom == NULL) { ilstAtom = APar_FindAtom("moov.udta.meta.ilst", false, SIMPLE_ATOM, 0); if (ilstAtom == NULL) return; } for (int i = ilstAtom->AtomicNumber; i < atom_number; i++) { AtomicInfo *thisAtom = &parsedAtoms[i]; if (strncmp(thisAtom->AtomicName, "data", 4) == 0) { // thisAtom->AtomicClassification == VERSIONED_ATOM) { AtomicInfo *parent = &parsedAtoms[APar_FindParentAtom(i, thisAtom->AtomicLevel)]; if ((thisAtom->AtomicVerFlags == AtomFlags_Data_Binary || thisAtom->AtomicVerFlags == AtomFlags_Data_Text || thisAtom->AtomicVerFlags == AtomFlags_Data_UInt) && target_information == PRINT_DATA) { if (strncmp(parent->AtomicName, "----", 4) == 0) { if (memcmp(parsedAtoms[parent->AtomicNumber + 2].AtomicName, "name", 4) == 0) { fprintf(stdout, "Atom \"%s\" [%s;%s] contains: ", parent->AtomicName, parsedAtoms[parent->AtomicNumber + 1].ReverseDNSdomain, parsedAtoms[parent->AtomicNumber + 2].ReverseDNSname); APar_ExtractDataAtom(i); } } else if (memcmp(parent->AtomicName, "covr", 4) == 0) { // libmp4v2 doesn't properly set artwork with the right // flags (its all 0x00) artwork_count++; } else { // converts iso8859 in 'ART' to a 2byte utf8 glyph; replaces // libiconv conversion memset(twenty_byte_buffer, 0, sizeof(char) * 20); isolat1ToUTF8((unsigned char *)twenty_byte_buffer, 10, (unsigned char *)parent->AtomicName, 4); if (UnicodeOutputStatus == WIN32_UTF16) { fprintf(stdout, "Atom \""); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, "\" contains: "); } else { fprintf(stdout, "Atom \"%s\" contains: ", twenty_byte_buffer); } APar_ExtractDataAtom(i); } } else if (memcmp(parent->AtomicName, "covr", 4) == 0) { artwork_count++; if (target_information == EXTRACT_ARTWORK) { APar_ExtractAAC_Artwork( thisAtom->AtomicNumber, output_path, artwork_count); } } if (thisAtom->AtomicLength <= 12) { fprintf( stdout, "\n"); // (corrupted atom); libmp4v2 touching a file with copyright } } } if (artwork_count != 0 && target_information == PRINT_DATA) { if (artwork_count == 1) { fprintf(stdout, "Atom \"covr\" contains: %i piece of artwork\n", artwork_count); } else { fprintf(stdout, "Atom \"covr\" contains: %i pieces of artwork\n", artwork_count); } } if (supplemental_info) { fprintf(stdout, "---------------------------\n"); dynUpd.updage_by_padding = false; // APar_DetermineDynamicUpdate(true); //gets the size of the padding APar_Optimize( true); // just to know if 'free' atoms can be considered padding, or (in // the case of say a faac file) it's *just* 'free' if (supplemental_info & 0x02) { // PRINT_FREE_SPACE fprintf(stdout, "free atom space: %" PRIu32 "\n", APar_ProvideTallyForAtom("free")); } if (supplemental_info & 0x04) { // PRINT_PADDING_SPACE if (!moov_atom_was_mooved) { fprintf(stdout, "padding available: %" PRIu64 " bytes\n", dynUpd.padding_bytes); } else { fprintf(stdout, "padding available: 0 (reorg)\n"); } } if (supplemental_info & 0x08 && dynUpd.moov_udta_atom != NULL) { // PRINT_USER_DATA_SPACE fprintf(stdout, "user data space: %" PRIu64 "\n", dynUpd.moov_udta_atom->AtomicLength); } if (supplemental_info & 0x10) { // PRINT_USER_DATA_SPACE fprintf(stdout, "media data space: %" PRIu32 "\n", APar_ProvideTallyForAtom("mdat")); } } return; } /////////////////////////////////////////////////////////////////////////////////////// // AP uuid metadata listings // /////////////////////////////////////////////////////////////////////////////////////// void APar_Print_APuuidv5_contents(AtomicInfo *thisAtom) { memset(twenty_byte_buffer, 0, sizeof(char) * 20); isolat1ToUTF8((unsigned char *)twenty_byte_buffer, 10, (unsigned char *)thisAtom->uuid_ap_atomname, 4); fprintf(stdout, "Atom uuid="); APar_print_uuid((ap_uuid_t *)thisAtom->AtomicName, false); fprintf(stdout, " (AP uuid for \""); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, "\") contains: "); APar_ExtractDataAtom(thisAtom->AtomicNumber); return; } void APar_Print_APuuid_deprecated_contents(AtomicInfo *thisAtom) { memset(twenty_byte_buffer, 0, sizeof(char) * 20); isolat1ToUTF8((unsigned char *)twenty_byte_buffer, 10, (unsigned char *)thisAtom->AtomicName, 4); if (UnicodeOutputStatus == WIN32_UTF16) { fprintf(stdout, "Atom uuid=\""); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, "\" contains: "); } else { fprintf(stdout, "Atom uuid=\"%s\" contains: ", twenty_byte_buffer); } APar_ExtractDataAtom(thisAtom->AtomicNumber); return; } void APar_Print_APuuid_atoms(const char *path, char *output_path, uint8_t target_information) { AtomicInfo *thisAtom = NULL; printBOM(); AtomicInfo *metaAtom = APar_FindAtom("moov.udta.meta", false, VERSIONED_ATOM, 0); if (metaAtom == NULL) return; for (int i = metaAtom->NextAtomNumber; i < atom_number; i++) { thisAtom = &parsedAtoms[i]; if (thisAtom->AtomicLevel <= metaAtom->AtomicLevel) break; // we've gone too far if (thisAtom->AtomicClassification == EXTENDED_ATOM) { if (thisAtom->uuid_style == UUID_AP_SHA1_NAMESPACE) { if (target_information == PRINT_DATA) APar_Print_APuuidv5_contents(thisAtom); if (target_information == EXTRACT_ALL_UUID_BINARYS && thisAtom->AtomicVerFlags == AtomFlags_Data_uuid_binary) { APar_Extract_uuid_binary_file(thisAtom, path, output_path); } } if (thisAtom->uuid_style == UUID_DEPRECATED_FORM && target_information == PRINT_DATA) APar_Print_APuuid_deprecated_contents(thisAtom); } } return; } /////////////////////////////////////////////////////////////////////////////////////// // 3GP asset metadata listings // /////////////////////////////////////////////////////////////////////////////////////// void APar_PrintUnicodeAssest(char *unicode_string, int asset_length) { // 3gp files if (strncmp(unicode_string, "\xFE\xFF", 2) == 0) { // utf16 fprintf(stdout, " (utf16)] : "); unsigned char *utf8_data = Convert_multibyteUTF16_to_UTF8( unicode_string, (asset_length - 13) * 6, asset_length - 14); #if defined(_WIN32) && !defined(__CYGWIN__) if (GetVersion() & 0x80000000 || UnicodeOutputStatus == UNIVERSAL_UTF8) { // pre-NT or AP-utf8.exe (pish, thats my win98se, // and without unicows support convert utf16toutf8 // and output raw bytes) unsigned char *utf8_data = Convert_multibyteUTF16_to_UTF8( unicode_string, (asset_length - 13) * 6, asset_length - 14); fprintf(stdout, "%s", utf8_data); free(utf8_data); utf8_data = NULL; } else { wchar_t *utf16_data = Convert_multibyteUTF16_to_wchar( unicode_string, (asset_length - 16) / 2, true); APar_unicode_win32Printout(utf16_data, (char *)utf8_data); free(utf16_data); utf16_data = NULL; } #else fprintf(stdout, "%s", utf8_data); #endif free(utf8_data); utf8_data = NULL; } else { // utf8 fprintf(stdout, " (utf8)] : "); APar_fprintf_UTF8_data(unicode_string); } return; } void APar_Print_single_userdata_atomcontents(uint8_t track_num, short userdata_atom, bool quantum_listing) { uint32_t box = UInt32FromBigEndian(parsedAtoms[userdata_atom].AtomicName); char bitpacked_lang[3]; memset(bitpacked_lang, 0, 3); unsigned char unpacked_lang[3]; uint32_t box_length = parsedAtoms[userdata_atom].AtomicLength; char *box_data = (char *)malloc(sizeof(char) * box_length); memset(box_data, 0, sizeof(char) * box_length); switch (box) { case 0x7469746C: //'titl' case 0x64736370: //'dscp' case 0x63707274: //'cprt' case 0x70657266: //'perf' case 0x61757468: //'auth' case 0x676E7265: //'gnre' case 0x616C626D: //'albm' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); uint16_t packed_lang = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + 12); APar_UnpackLanguage(unpacked_lang, packed_lang); APar_readX( box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 14, box_length - 14); // 4bytes length, 4 bytes name, 4 bytes flags, 2 bytes lang // get tracknumber *after* we read the whole tag; if we have a utf16 tag, it // will have a BOM, indicating if we have to search for 2 NULLs or a utf8 // single NULL, then the ****optional**** tracknumber uint16_t track_num = 1000; // tracknum is a uint8_t, so setting it > 256 // means a number wasn't found if (box == 0x616C626D) { //'albm' has an *optional* uint8_t at the end for // tracknumber; if the last byte in the tag is not // 0, then it must be the optional tracknum (or a // non-compliant, non-NULL-terminated string). This // byte is the length - (14 bytes +1tracknum) or -15 if (box_data[box_length - 15] != 0) { track_num = (uint16_t)box_data[box_length - 15]; box_data[box_length - 15] = 0; // NULL out the last byte if found to be not 0 - it will impact // unicode conversion if it remains } } fprintf(stdout, "[lang=%s", unpacked_lang); APar_PrintUnicodeAssest(box_data, box_length); if (box == 0x616C626D && track_num != 1000) { fprintf(stdout, " | Track: %u", track_num); } fprintf(stdout, "\n"); break; } case 0x72746E67: //'rtng' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); APar_readX( box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 12, 4); fprintf(stdout, "[Rating Entity=%s", box_data); memset(box_data, 0, box_length); APar_readX( box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 16, 4); fprintf(stdout, " | Criteria=%s", box_data); uint16_t packed_lang = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + 20); APar_UnpackLanguage(unpacked_lang, packed_lang); fprintf(stdout, " lang=%s", unpacked_lang); memset(box_data, 0, box_length); APar_readX(box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 22, box_length - 8); APar_PrintUnicodeAssest(box_data, box_length - 8); fprintf(stdout, "\n"); break; } case 0x636C7366: //'clsf' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); APar_readX( box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 12, box_length - 12); // 4bytes length, 4 bytes name, 4 bytes flags, 2 bytes lang fprintf(stdout, "[Classification Entity=%s", box_data); fprintf(stdout, " | Index=%u", UInt16FromBigEndian(box_data + 4)); uint16_t packed_lang = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + 18); APar_UnpackLanguage(unpacked_lang, packed_lang); fprintf(stdout, " lang=%s", unpacked_lang); APar_PrintUnicodeAssest(box_data + 8, box_length - 8); fprintf(stdout, "\n"); break; } case 0x6B797764: //'kywd' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); uint64_t box_offset = 12; uint16_t packed_lang = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset); box_offset += 2; APar_UnpackLanguage(unpacked_lang, packed_lang); uint8_t keyword_count = APar_read8( source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset); box_offset++; fprintf(stdout, "[Keyword count=%u", keyword_count); fprintf(stdout, " lang=%s]", unpacked_lang); char *keyword_data = (char *)malloc(sizeof(char) * box_length * 2); for (uint8_t x = 1; x <= keyword_count; x++) { memset(keyword_data, 0, box_length * 2); uint8_t keyword_length = APar_read8( source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset); box_offset++; APar_readX(keyword_data, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset, keyword_length); box_offset += keyword_length; APar_SimplePrintUnicodeAssest(keyword_data, keyword_length, true); } free(keyword_data); keyword_data = NULL; fprintf(stdout, "\n"); break; } case 0x6C6F6369: //'loci' aka The Most Heinous Metadata Atom Every Invented - // decimal meters? fictional location? Astromical Body? Say I // shoot it on the International Space Station? That isn't a // Astronimical Body. And 16.16 alt only goes up to 20.3 // miles (because of negatives, its really 15.15) & the ISS // is // at 230 miles. Oh, pish.... what ever shall I do? I fear I // am on the horns of a dilema. { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); uint64_t box_offset = 12; uint16_t packed_lang = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset); box_offset += 2; APar_UnpackLanguage(unpacked_lang, packed_lang); APar_readX(box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset, box_length); fprintf(stdout, "[lang=%s] ", unpacked_lang); // the length of the location string is unknown (max is box lenth), but the // long/lat/alt/body/notes needs to be retrieved. test if the location // string is utf16; if so search for 0x0000 (or if utf8, find the first // NULL). if (strncmp(box_data, "\xFE\xFF", 2) == 0) { box_offset += 2 * widechar_len(box_data, box_length) + 2; //*2 for utf16 (double-byte); +2 for the terminating NULL fprintf(stdout, "(utf16) "); } else { fprintf(stdout, "(utf8) "); box_offset += strlen(box_data) + 1; //+1 for the terminating NULL } fprintf(stdout, "Location: "); APar_SimplePrintUnicodeAssest(box_data, box_length, false); uint8_t location_role = APar_read8( source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset); box_offset++; switch (location_role) { case 0: { fprintf(stdout, " (Role: shooting location) "); break; } case 1: { fprintf(stdout, " (Role: real location) "); break; } case 2: { fprintf(stdout, " (Role: fictional location) "); break; } default: { fprintf(stdout, " (Role: [reserved]) "); break; } } char *float_buffer = (char *)malloc(sizeof(char) * 5); memset(float_buffer, 0, 5); fprintf(stdout, "[Long %lf", fixed_point_16x16bit_to_double(APar_read32( float_buffer, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset))); box_offset += 4; fprintf(stdout, " Lat %lf", fixed_point_16x16bit_to_double(APar_read32( float_buffer, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset))); box_offset += 4; fprintf(stdout, " Alt %lf ", fixed_point_16x16bit_to_double(APar_read32( float_buffer, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset))); box_offset += 4; free(float_buffer); float_buffer = NULL; if (box_offset < box_length) { fprintf(stdout, " Body: "); APar_SimplePrintUnicodeAssest( box_data + box_offset - 14, box_length - box_offset, false); if (strncmp(box_data + box_offset - 14, "\xFE\xFF", 2) == 0) { box_offset += 2 * widechar_len(box_data + box_offset - 14, box_length - box_offset) + 2; //*2 for utf16 (double-byte); +2 for the terminating NULL } else { box_offset += strlen(box_data + box_offset - 14) + 1; //+1 for the terminating NULL } } fprintf(stdout, "]"); if (box_offset < box_length) { fprintf(stdout, " Notes: "); APar_SimplePrintUnicodeAssest( box_data + box_offset - 14, box_length - box_offset, false); } fprintf(stdout, "\n"); break; } case 0x79727263: //'yrrc' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); uint16_t recording_year = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + 12); fprintf(stdout, ": %u\n", recording_year); break; } case 0x6E616D65: //'name' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); APar_fprintf_UTF8_data(": "); APar_readX( box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 8, box_length - 8); // 4bytes length, 4 bytes name, 4 bytes flags, 2 bytes lang APar_fprintf_UTF8_data(box_data); APar_fprintf_UTF8_data("\n"); break; } case 0x686E7469: //'hnti' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); APar_readX( box_data, source_file, parsedAtoms[userdata_atom + 1].AtomicStart + 8, box_length - 8); // 4bytes length, 4 bytes name, 4 bytes flags, 2 bytes lang fprintf(stdout, "for %s:\n", parsedAtoms[userdata_atom + 1].AtomicName); APar_fprintf_UTF8_data(box_data); break; } default: { break; } } return; } /////////////////////////////////////////////////////////////////////////////////////// // id3 displaying functions // /////////////////////////////////////////////////////////////////////////////////////// void APar_Print_ID3TextField(ID3v2Frame *textframe, ID3v2Fields *textfield, bool linefeed = false) { // this won't accommodate id3v2.4's multiple strings separated by NULLs if (textframe->ID3v2_Frame_Fields->field_string[0] == TE_LATIN1) { // all frames that have text encodings have the encoding as // the first field if (textfield->field_length > 0) { char *conv_buffer = (char *)calloc(1, sizeof(char *) * (textfield->field_length * 4) + 2); isolat1ToUTF8((unsigned char *)conv_buffer, sizeof(char *) * (textfield->field_length * 4) + 2, (unsigned char *)textfield->field_string, textfield->field_length); fprintf(stdout, "%s", conv_buffer); free(conv_buffer); conv_buffer = NULL; } } else if (textframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF16LE_WITH_BOM) { // technically AP *writes* uff16LE here, but // based on BOM, it could be utf16BE if (textfield->field_length > 2) { char *conv_buffer = (char *)calloc(1, sizeof(char *) * (textfield->field_length * 2) + 2); if (strncmp(textfield->field_string, "\xFF\xFE", 2) == 0) { UTF16LEToUTF8((unsigned char *)conv_buffer, sizeof(char *) * (textfield->field_length * 4) + 2, (unsigned char *)textfield->field_string + 2, textfield->field_length); fprintf(stdout, "%s", conv_buffer); } else { UTF16BEToUTF8((unsigned char *)conv_buffer, sizeof(char *) * (textfield->field_length * 4) + 2, (unsigned char *)textfield->field_string + 2, textfield->field_length); fprintf(stdout, "%s", conv_buffer); } free(conv_buffer); conv_buffer = NULL; } } else if (textframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF16BE_NO_BOM) { if (textfield->field_length > 0) { char *conv_buffer = (char *)calloc(1, sizeof(char *) * (textfield->field_length * 2) + 2); UTF16BEToUTF8((unsigned char *)conv_buffer, sizeof(char *) * (textfield->field_length * 4) + 2, (unsigned char *)textfield->field_string, textfield->field_length); fprintf(stdout, "%s", conv_buffer); free(conv_buffer); conv_buffer = NULL; } } else if (textframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF8) { fprintf(stdout, "%s", textfield->field_string); } else { fprintf(stdout, "(unknown type: 0x%X", (uint8_t)textframe->ID3v2_Frame_Fields->field_string[0]); } if (linefeed) fprintf(stdout, "\n"); return; } const char *APar_GetTextEncoding(ID3v2Frame *aframe, ID3v2Fields *textfield) { const char *text_encoding = NULL; if (aframe->ID3v2_Frame_Fields->field_string[0] == TE_LATIN1) text_encoding = "latin1"; if (aframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF16BE_NO_BOM) { if (strncmp(textfield->field_string, "\xFF\xFE", 2) == 0) { text_encoding = "utf16le"; } else if (strncmp(textfield->field_string, "\xFE\xFF", 2) == 0) { text_encoding = "utf16be"; } } if (aframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF16LE_WITH_BOM) text_encoding = "utf16le"; if (aframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF8) text_encoding = "utf8"; return text_encoding; } void APar_Print_ID3v2_tags(AtomicInfo *id32_atom) { // TODO properly printout latin1 for fields like owner // TODO for binary fields (like GRID group data) scan through to see if it // needs to be printed in hex fprintf(stdout, "Maj.Min.Rev version // was 2.%u.%u\n", id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion, // id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion); char *id32_level = (char *)calloc(1, sizeof(char *) * 16); if (id32_atom->AtomicLevel == 2) { memcpy(id32_level, "file level", 10); } else if (id32_atom->AtomicLevel == 3) { memcpy(id32_level, "movie level", 11); } else if (id32_atom->AtomicLevel == 4) { sprintf(id32_level, "track #%u", 1); // unimplemented; need to pass a variable here } unsigned char unpacked_lang[3]; APar_UnpackLanguage(unpacked_lang, id32_atom->AtomicLanguage); if (id32_atom->ID32_TagInfo->ID3v2_FirstFrame != NULL) { fprintf(stdout, "ID32 atom [lang=%s] at %s contains an ID3v2.%u.%u tag (%u tags, " "%u bytes):\n", unpacked_lang, id32_level, id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion, id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion, id32_atom->ID32_TagInfo->ID3v2_FrameCount, id32_atom->ID32_TagInfo->ID3v2Tag_Length); } else { fprintf(stdout, "ID32 atom [lang=%s] at %s contains an ID3v2.%u.%u tag. ", unpacked_lang, id32_level, id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion, id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion); if (ID3v2_TestTagFlag(id32_atom->ID32_TagInfo->ID3v2Tag_Flags, ID32_TAGFLAG_UNSYNCRONIZATION)) { fprintf(stdout, "Unsynchronized flag set. Unsupported. No tags read. %" PRIu32 " bytes.\n", id32_atom->ID32_TagInfo->ID3v2Tag_Length); } } ID3v2Frame *target_frameinfo = id32_atom->ID32_TagInfo->ID3v2_FirstFrame; while (target_frameinfo != NULL) { if (ID3v2_TestFrameFlag(target_frameinfo->ID3v2_Frame_Flags, ID32_FRAMEFLAG_GROUPING) && target_frameinfo && target_frameinfo->ID3v2_FrameType != ID3_GROUP_ID_FRAME) { fprintf(stdout, " Tag: %s GID=0x%02X \"%s\" ", target_frameinfo->ID3v2_Frame_Namestr, target_frameinfo->ID3v2_Frame_GroupingSymbol, KnownFrames[target_frameinfo->ID3v2_Frame_ID + 1] .ID3V2_FrameDescription); } else { fprintf(stdout, " Tag: %s \"%s\" ", target_frameinfo->ID3v2_Frame_Namestr, KnownFrames[target_frameinfo->ID3v2_Frame_ID + 1] .ID3V2_FrameDescription); } uint8_t frame_comp_idx = GetFrameCompositionDescription(target_frameinfo->ID3v2_FrameType); if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_UNKNOWN_FRAME) { fprintf(stdout, "(unknown frame) %" PRIu32 " bytes\n", target_frameinfo->ID3v2_Frame_Fields->field_length); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_TEXT_FRAME) { ID3v2Fields *atextfield = target_frameinfo->ID3v2_Frame_Fields + 1; if (target_frameinfo->textfield_tally > 1) { fprintf(stdout, "(%s) : { ", APar_GetTextEncoding(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 1)); } else { fprintf(stdout, "(%s) : ", APar_GetTextEncoding(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 1)); } while (true) { if (target_frameinfo->textfield_tally > 1) { fprintf(stdout, "\""); } if (target_frameinfo->ID3v2_Frame_ID == ID3v2_FRAME_CONTENTTYPE) { char *genre_string = NULL; int genre_idx = (int)strtol(atextfield->field_string, &genre_string, 10); if (genre_string != atextfield->field_string) { genre_string = ID3GenreIntToString(genre_idx); if (target_frameinfo->textfield_tally == 1) { fprintf(stdout, "%s\n", ID3GenreIntToString(genre_idx)); } else { fprintf(stdout, "%s", ID3GenreIntToString(genre_idx)); } } else { APar_Print_ID3TextField( target_frameinfo, atextfield, target_frameinfo->textfield_tally == 1 ? true : false); } } else if (target_frameinfo->ID3v2_Frame_ID == ID3v2_FRAME_COPYRIGHT) { APar_fprintf_UTF8_data("\xC2\xA9 "); APar_Print_ID3TextField( target_frameinfo, atextfield, target_frameinfo->textfield_tally == 1 ? true : false); } else if (target_frameinfo->ID3v2_Frame_ID == ID3v2_FRAME_PRODNOTICE) { APar_fprintf_UTF8_data("\xE2\x84\x97 "); APar_Print_ID3TextField( target_frameinfo, atextfield, target_frameinfo->textfield_tally == 1 ? true : false); } else { APar_Print_ID3TextField( target_frameinfo, atextfield, target_frameinfo->textfield_tally == 1 ? true : false); } if (target_frameinfo->textfield_tally > 1) { fprintf(stdout, "\""); } else { break; } atextfield = atextfield->next_field; if (atextfield == NULL) { fprintf(stdout, " }\n"); break; } else { fprintf(stdout, ", "); } } } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_TEXT_FRAME_USERDEF) { fprintf(stdout, "(user-defined text frame) "); fprintf(stdout, "%u fields\n", target_frameinfo->ID3v2_FieldCount); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_URL_FRAME) { fprintf(stdout, "(url frame) : %s\n", (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); fprintf(stdout, "%u fields\n", target_frameinfo->ID3v2_FieldCount); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_URL_FRAME_USERDEF) { fprintf(stdout, "(user-defined url frame) "); fprintf(stdout, "%u fields\n", target_frameinfo->ID3v2_FieldCount); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_UNIQUE_FILE_ID_FRAME) { if (test_limited_ascii( (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string, (target_frameinfo->ID3v2_Frame_Fields + 1)->field_length)) { fprintf(stdout, "(owner='%s') : %s\n", target_frameinfo->ID3v2_Frame_Fields->field_string, (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); } else { fprintf(stdout, "(owner='%s') : 0x", target_frameinfo->ID3v2_Frame_Fields->field_string); for (uint32_t hexidx = 0; hexidx < (target_frameinfo->ID3v2_Frame_Fields + 1)->field_length; hexidx++) { fprintf(stdout, "%02X", (uint8_t)(target_frameinfo->ID3v2_Frame_Fields + 1) ->field_string[hexidx]); } fprintf(stdout, "\n"); } } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_CD_ID_FRAME) { // TODO: print hex representation uint8_t tracklistings = 0; if (target_frameinfo->ID3v2_Frame_Fields->field_length >= 16) { tracklistings = target_frameinfo->ID3v2_Frame_Fields->field_length / 8; fprintf(stdout, "(Music CD Identifier) : Entries for %u tracks + leadout " "track.\n Hex: 0x", tracklistings - 1); } else { fprintf(stdout, "(Music CD Identifier) : Unknown format (less then 16 " "bytes).\n Hex: 0x"); } for (uint16_t hexidx = 1; hexidx < target_frameinfo->ID3v2_Frame_Fields->field_length + 1; hexidx++) { fprintf(stdout, "%02X", (uint8_t)target_frameinfo->ID3v2_Frame_Fields ->field_string[hexidx - 1]); if (hexidx % 4 == 0) fprintf(stdout, " "); } fprintf(stdout, "\n"); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_DESCRIBED_TEXT_FRAME) { fprintf(stdout, "(%s, lang=%s, desc[", APar_GetTextEncoding(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 2), (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); APar_Print_ID3TextField(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 2); fprintf(stdout, "]) : "); APar_Print_ID3TextField( target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 3, true); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_ATTACHED_PICTURE_FRAME) { fprintf( stdout, "(type=0x%02X-'%s', mimetype=%s, %s, desc[", (uint8_t)(target_frameinfo->ID3v2_Frame_Fields + 2)->field_string[0], ImageTypeList[(uint8_t)(target_frameinfo->ID3v2_Frame_Fields + 2) ->field_string[0]] .imagetype_str, (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string, APar_GetTextEncoding(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 1)); APar_Print_ID3TextField(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 3); if (ID3v2_TestFrameFlag(target_frameinfo->ID3v2_Frame_Flags, ID32_FRAMEFLAG_COMPRESSED)) { fprintf(stdout, "]) : %" PRIu32 " bytes (%" PRIu32 " compressed)\n", (target_frameinfo->ID3v2_Frame_Fields + 4)->field_length, target_frameinfo->ID3v2_Frame_Length); } else { fprintf(stdout, "]) : %" PRIu32 " bytes\n", (target_frameinfo->ID3v2_Frame_Fields + 4)->field_length); } } else if (target_frameinfo->ID3v2_FrameType == ID3_ATTACHED_OBJECT_FRAME) { fprintf(stdout, "(filename="); APar_Print_ID3TextField(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 2); fprintf(stdout, ", mimetype=%s, desc[", (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); APar_Print_ID3TextField(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 3); if (ID3v2_TestFrameFlag(target_frameinfo->ID3v2_Frame_Flags, ID32_FRAMEFLAG_COMPRESSED)) { fprintf(stdout, "]) : %" PRIu32 " bytes (%" PRIu32 " compressed)\n", (target_frameinfo->ID3v2_Frame_Fields + 4)->field_length, target_frameinfo->ID3v2_Frame_Length); } else { fprintf(stdout, "]) : %" PRIu32 " bytes\n", (target_frameinfo->ID3v2_Frame_Fields + 4)->field_length); } } else if (target_frameinfo->ID3v2_FrameType == ID3_GROUP_ID_FRAME) { fprintf( stdout, "(owner='%s') : 0x%02X", target_frameinfo->ID3v2_Frame_Fields->field_string, (uint8_t)(target_frameinfo->ID3v2_Frame_Fields + 1)->field_string[0]); if ((target_frameinfo->ID3v2_Frame_Fields + 2)->field_length > 0) { fprintf(stdout, "; groupdata='%s'\n", (target_frameinfo->ID3v2_Frame_Fields + 2)->field_string); } else { fprintf(stdout, "\n"); } } else if (target_frameinfo->ID3v2_FrameType == ID3_PRIVATE_FRAME) { fprintf(stdout, "(owner='%s') : %s\n", target_frameinfo->ID3v2_Frame_Fields->field_string, (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); } else if (target_frameinfo->ID3v2_FrameType == ID3_SIGNATURE_FRAME) { fprintf(stdout, "{GID=0x%02X) : %s\n", (uint8_t)target_frameinfo->ID3v2_Frame_Fields->field_string[0], (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); } else if (target_frameinfo->ID3v2_FrameType == ID3_PLAYCOUNTER_FRAME) { if (target_frameinfo->ID3v2_Frame_Fields->field_length == 4) { fprintf(stdout, ": %" PRIu32 "\n", syncsafe32_to_UInt32( target_frameinfo->ID3v2_Frame_Fields->field_string)); } else if (target_frameinfo->ID3v2_Frame_Fields->field_length > 4) { fprintf(stdout, ": %" PRIu64 "\n", syncsafeXX_to_UInt64( target_frameinfo->ID3v2_Frame_Fields->field_string, target_frameinfo->ID3v2_Frame_Fields->field_length)); } } else if (target_frameinfo->ID3v2_FrameType == ID3_POPULAR_FRAME) { fprintf( stdout, "(owner='%s') : %u", target_frameinfo->ID3v2_Frame_Fields->field_string, (uint8_t)(target_frameinfo->ID3v2_Frame_Fields + 1)->field_string[0]); if ((target_frameinfo->ID3v2_Frame_Fields + 2)->field_length > 0) { if ((target_frameinfo->ID3v2_Frame_Fields + 2)->field_length == 4) { fprintf( stdout, "; playcount=%" PRIu32 "\n", syncsafe32_to_UInt32( (target_frameinfo->ID3v2_Frame_Fields + 2)->field_string)); } else if ((target_frameinfo->ID3v2_Frame_Fields + 2)->field_length > 4) { fprintf( stdout, "; playcount=%" PRIu64 "\n", syncsafeXX_to_UInt64( (target_frameinfo->ID3v2_Frame_Fields + 2)->field_string, (target_frameinfo->ID3v2_Frame_Fields + 2)->field_length)); } else { fprintf(stdout, "\n"); // don't know what it was supposed to be, so skip it } } else { fprintf(stdout, "\n"); } } else { fprintf(stdout, " [idx=%u;%d]\n", frame_comp_idx, FrameTypeConstructionList[frame_comp_idx].ID3_FrameType); } target_frameinfo = target_frameinfo->ID3v2_NextFrame; } free(id32_level); id32_level = NULL; return; } /////////////////////////////////////////////////////////////////////////////////////// // metadata scheme searches // /////////////////////////////////////////////////////////////////////////////////////// void APar_Print_metachild_atomcontents(uint8_t track_num, short metachild_atom, bool quantum_listing) { if (memcmp(parsedAtoms[metachild_atom].AtomicName, "ID32", 4) == 0) { APar_ID32_ScanID3Tag(source_file, &parsedAtoms[metachild_atom]); APar_Print_ID3v2_tags(&parsedAtoms[metachild_atom]); } return; } void APar_PrintMetaChildren(AtomicInfo *metaAtom, AtomicInfo *hdlrAtom, bool quantum_listing) { if (metaAtom != NULL && hdlrAtom != NULL) { if (hdlrAtom->ancillary_data == 0x49443332) { for (int i = metaAtom->NextAtomNumber; i < atom_number; i++) { if (parsedAtoms[i].AtomicLevel <= metaAtom->AtomicLevel) break; // we've gone too far if (parsedAtoms[i].AtomicLevel == metaAtom->AtomicLevel + 1) APar_Print_metachild_atomcontents(0, i, quantum_listing); } } } return; } void APar_PrintID32Metadata(bool quantum_listing) { uint8_t total_tracks = 0; uint8_t a_track = 0; AtomicInfo *metaAtom = NULL; AtomicInfo *metahandlerAtom = NULL; char trackmeta_atom_path[50]; printBOM(); // file level metaAtom = APar_FindAtom("meta", false, VERSIONED_ATOM, 0); metahandlerAtom = APar_FindAtom("meta.hdlr", false, VERSIONED_ATOM, 0); APar_PrintMetaChildren(metaAtom, metahandlerAtom, quantum_listing); // movie level metaAtom = APar_FindAtom("moov.meta", false, VERSIONED_ATOM, 0); metahandlerAtom = APar_FindAtom("moov.meta.hdlr", false, VERSIONED_ATOM, 0); APar_PrintMetaChildren(metaAtom, metahandlerAtom, quantum_listing); // track level APar_FindAtomInTrack(total_tracks, a_track, NULL); // With track_num set to 0, it will return the // total trak atom into total_tracks here. for (uint8_t i = 1; i <= total_tracks; i++) { memset(&trackmeta_atom_path, 0, 50); sprintf(trackmeta_atom_path, "moov.trak[%u].meta", i); metaAtom = APar_FindAtom(trackmeta_atom_path, false, VERSIONED_ATOM, 0); sprintf(trackmeta_atom_path, "moov.trak[%u].meta.hdlr", i); metahandlerAtom = APar_FindAtom(trackmeta_atom_path, false, VERSIONED_ATOM, 0); APar_PrintMetaChildren(metaAtom, metahandlerAtom, quantum_listing); } return; } /*---------------------- APar_Print_ISO_UserData_per_track quantum_listing - controls whether to simply print each asset, or preface each asset with "movie level" This will only show what is under moov.trak.udta atoms (not moov.udta). Get the total number of tracks; construct the moov.trak[index].udta path to find, then if the atom after udta is of a greater level, read in from the file & print out what it contains. ----------------------*/ void APar_PrintUserDataAssests(bool quantum_listing) { printBOM(); AtomicInfo *udtaAtom = APar_FindAtom("moov.udta", false, SIMPLE_ATOM, 0); if (udtaAtom != NULL) { for (int i = udtaAtom->NextAtomNumber; i < atom_number; i++) { if (parsedAtoms[i].AtomicLevel <= udtaAtom->AtomicLevel) break; // we've gone too far if (parsedAtoms[i].AtomicLevel == udtaAtom->AtomicLevel + 1) APar_Print_single_userdata_atomcontents(0, i, quantum_listing); } } APar_PrintID32Metadata(quantum_listing); APar_Print_APuuid_atoms(NULL, NULL, PRINT_DATA); return; } /*---------------------- APar_Print_ISO_UserData_per_track This will only show what is under moov.trak.udta atoms (not moov.udta). Get the total number of tracks; construct the moov.trak[index].udta path to find, then if the atom after udta is of a greater level, read in from the file & print out what it contains. ----------------------*/ void APar_Print_ISO_UserData_per_track() { uint8_t total_tracks = 0; uint8_t a_track = 0; // unused short a_trak_atom = 0; char iso_atom_path[400]; AtomicInfo *trak_udtaAtom = NULL; APar_FindAtomInTrack(total_tracks, a_track, NULL); // With track_num set to 0, it will return the // total trak atom into total_tracks here. for (uint8_t i = 1; i <= total_tracks; i++) { memset(&iso_atom_path, 0, 400); sprintf(iso_atom_path, "moov.trak[%u].udta", i); trak_udtaAtom = APar_FindAtom(iso_atom_path, false, SIMPLE_ATOM, 0); if (trak_udtaAtom != NULL && parsedAtoms[trak_udtaAtom->NextAtomNumber].AtomicLevel == trak_udtaAtom->AtomicLevel + 1) { a_trak_atom = trak_udtaAtom->NextAtomNumber; while ( parsedAtoms[a_trak_atom].AtomicLevel > trak_udtaAtom ->AtomicLevel) { // only work on moov.trak[i].udta's child atoms if (parsedAtoms[a_trak_atom].AtomicLevel == trak_udtaAtom->AtomicLevel + 1) APar_Print_single_userdata_atomcontents(i, a_trak_atom, true); a_trak_atom = parsedAtoms[a_trak_atom].NextAtomNumber; } } } APar_PrintUserDataAssests(true); return; } /////////////////////////////////////////////////////////////////////////////////////// // Atom Tree // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_PrintAtomicTree Following the linked list (by NextAtomNumber), list each atom as they exist in the hieararchy, reflecting positions of moving, eliminating & additions. This listing can occur during the course of tagging as well to assist in diagnosing problems. ----------------------*/ void APar_PrintAtomicTree() { bool unknown_atom = false; char *tree_padding = (char *)malloc( sizeof(char) * 126); // for a 25-deep atom tree (4 spaces per atom)+single space+term. uint32_t freeSpace = 0; short thisAtomNumber = 0; printBOM(); // loop through each atom in the struct array (which holds the offset // info/data) while (true) { AtomicInfo *thisAtom = &parsedAtoms[thisAtomNumber]; memset(tree_padding, 0, sizeof(char) * 126); memset(twenty_byte_buffer, 0, sizeof(char) * 20); if (thisAtom->uuid_ap_atomname != NULL) { isolat1ToUTF8((unsigned char *)twenty_byte_buffer, 10, (unsigned char *)thisAtom->uuid_ap_atomname, 4); // converts iso8859 in 'ART' to a 2byte utf8 glyph } else { isolat1ToUTF8((unsigned char *)twenty_byte_buffer, 10, (unsigned char *)thisAtom->AtomicName, 4); // converts iso8859 in 'ART' to a 2byte utf8 glyph } strcpy(tree_padding, ""); if (thisAtom->AtomicLevel != 1) { for (uint8_t pad = 1; pad < thisAtom->AtomicLevel; pad++) { strcat(tree_padding, " "); // if the atom depth is over 1, then add spaces before // text starts to form the tree } strcat(tree_padding, " "); // add a single space } if (thisAtom->AtomicLength == 0) { fprintf(stdout, "%sAtom %s @ %" PRIu64 " of size: %" PRIu64 " (%" PRIu64 "*), ends @ %" PRIu64 "\n", tree_padding, twenty_byte_buffer, thisAtom->AtomicStart, ((uint64_t)file_size - thisAtom->AtomicStart), thisAtom->AtomicLength, (uint64_t)file_size); fprintf(stdout, "\t\t\t (*)denotes length of atom goes to End-of-File\n"); } else if (thisAtom->AtomicLength == 1) { fprintf(stdout, "%sAtom %s @ %" PRIu64 " of size: %" PRIu64 " (^), ends @ %" PRIu64 "\n", tree_padding, twenty_byte_buffer, thisAtom->AtomicStart, thisAtom->AtomicLengthExtended, (thisAtom->AtomicStart + thisAtom->AtomicLengthExtended)); fprintf(stdout, "\t\t\t (^)denotes a 64-bit atom length\n"); // uuid atoms of any sort } else if (thisAtom->AtomicClassification == EXTENDED_ATOM && thisAtom->uuid_style == UUID_DEPRECATED_FORM) { if (UnicodeOutputStatus == WIN32_UTF16) { fprintf(stdout, "%sAtom uuid=", tree_padding); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, " @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } else { fprintf(stdout, "%sAtom uuid=%s @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", tree_padding, twenty_byte_buffer, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } } else if (thisAtom->AtomicClassification == EXTENDED_ATOM && thisAtom->uuid_style != UUID_DEPRECATED_FORM) { if (thisAtom->uuid_style == UUID_AP_SHA1_NAMESPACE) { fprintf(stdout, "%sAtom uuid=", tree_padding); APar_print_uuid((ap_uuid_t *)thisAtom->AtomicName, false); fprintf(stdout, "(APuuid=%s) @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", twenty_byte_buffer, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } else { fprintf(stdout, "%sAtom uuid=", tree_padding); APar_print_uuid((ap_uuid_t *)thisAtom->AtomicName, false); fprintf(stdout, " @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } // 3gp assets (most of them anyway) } else if (thisAtom->AtomicClassification == PACKED_LANG_ATOM) { unsigned char unpacked_lang[3]; APar_UnpackLanguage(unpacked_lang, thisAtom->AtomicLanguage); if (UnicodeOutputStatus == WIN32_UTF16) { fprintf(stdout, "%sAtom ", tree_padding); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, " [%s] @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", unpacked_lang, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } else { fprintf(stdout, "%sAtom %s [%s] @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", tree_padding, twenty_byte_buffer, unpacked_lang, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } // all other atoms (the bulk of them will fall here) } else { if (UnicodeOutputStatus == WIN32_UTF16) { fprintf(stdout, "%sAtom ", tree_padding); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, " @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } else { fprintf(stdout, "%sAtom %s @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64, tree_padding, twenty_byte_buffer, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } if (thisAtom->AtomicContainerState == UNKNOWN_ATOM_TYPE) { for (uint8_t i = 0; i < (5 - thisAtom->AtomicLevel); i++) { fprintf(stdout, "\t"); } fprintf(stdout, "\t\t\t ~\n"); unknown_atom = true; } else { fprintf(stdout, "\n"); } } // simple tally & percentage of free space info if (memcmp(thisAtom->AtomicName, "free", 4) == 0) { freeSpace = freeSpace + thisAtom->AtomicLength; } // this is where the *raw* audio/video file is, the rest is // container-related fluff. if ((memcmp(thisAtom->AtomicName, "mdat", 4) == 0) && (thisAtom->AtomicLength > 100)) { mdatData += thisAtom->AtomicLength; } else if (memcmp(thisAtom->AtomicName, "mdat", 4) == 0 && thisAtom->AtomicLength == 0) { // mdat.length = 0 = ends at EOF mdatData = file_size - thisAtom->AtomicStart; } else if (memcmp(thisAtom->AtomicName, "mdat", 4) == 0 && thisAtom->AtomicLengthExtended != 0) { mdatData += thisAtom->AtomicLengthExtended; // this is still adding a (limited) // uint64_t into a uint32_t } if (parsedAtoms[thisAtomNumber].NextAtomNumber == 0) { break; } else { thisAtomNumber = parsedAtoms[thisAtomNumber].NextAtomNumber; } } if (unknown_atom) { fprintf(stdout, "\n ~ denotes an unknown atom\n"); } fprintf(stdout, "------------------------------------------------------\n"); fprintf(stdout, "Total size: %" PRIu64 " bytes; ", (uint64_t)file_size); fprintf(stdout, "%i atoms total.\n", atom_number - 1); fprintf(stdout, "Media data: %" PRIu64 " bytes; %" PRIu64 " bytes all other atoms (%2.3lf%% atom overhead).\n", mdatData, file_size - mdatData, (double)(file_size - mdatData) / (double)file_size * 100.0); fprintf(stdout, "Total free atom space: %" PRIu32 " bytes; %2.3lf%% waste.", freeSpace, (double)freeSpace / (double)file_size * 100.0); if (freeSpace) { dynUpd.updage_by_padding = false; // APar_DetermineDynamicUpdate(true); //gets the size of the padding APar_Optimize( true); // just to know if 'free' atoms can be considered padding, or (in // the case of say a faac file) it's *just* 'free' if (!moov_atom_was_mooved) { fprintf(stdout, " Padding available: %" PRIu64 " bytes.", dynUpd.padding_bytes); } } if (gapless_void_padding > 0) { fprintf(stdout, "\nGapless playback null space at end of file: %" PRIu64 " bytes.", gapless_void_padding); } fprintf(stdout, "\n------------------------------------------------------\n"); ShowVersionInfo(); fprintf(stdout, "------------------------------------------------------\n"); free(tree_padding); tree_padding = NULL; return; } /*---------------------- APar_SimpleAtomPrintout print a simple flat list of atoms as they were created ----------------------*/ void APar_SimpleAtomPrintout() { // loop through each atom in the struct array // (which holds the offset info/data) printBOM(); for (int i = 0; i < atom_number; i++) { AtomicInfo *thisAtom = &parsedAtoms[i]; fprintf(stdout, "%i - Atom \"%s\" (level %u) has next atom at #%i\n", i, thisAtom->AtomicName, thisAtom->AtomicLevel, thisAtom->NextAtomNumber); } fprintf(stdout, "Total of %i atoms.\n", atom_number - 1); } atomicparsley-20240608.083822.1ed9031/src/nsfile.mm000066400000000000000000000203371463107535600210100ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - nsfile.mm AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #include "AtomicParsley.h" #import /*---------------------- APar_TestTracksForKind By testing which tracks are contained within the file, for Mac OS X we can avoid having to change file extension by instead using Finder.app metadata to signal the same info as file extension. For each trak atom, find the 'stsd' atom - its ancillary_data will contain the track type that is contained - the info is filled in as the file was initially parsed in APar_ScanAtoms. Then using Mac OS X Cocoa calls (in AP_NSFile_utils), set the Finder TYPE/CREATOR codes to signal to the OS/Finder/iTunes that this file is .m4a or .m4v without having to change its extension based on what the tracks actually contain. There are 2 issues with this - iTunes requires the Quicktime player type/creator codes for video that has multi-channel audio, and for chapterized video files. TODO: address these issues. ----------------------*/ void APar_TestTracksForKind() { uint8_t total_tracks = 0; uint8_t track_num = 0; AtomicInfo *codec_atom = NULL; // short codec_atom = 0; // With track_num set to 0, it will return the total trak atom into // total_tracks here. APar_FindAtomInTrack(total_tracks, track_num, NULL); if (total_tracks > 0) { while (total_tracks > track_num) { track_num += 1; codec_atom = APar_FindAtomInTrack(total_tracks, track_num, "stsd"); if (codec_atom == NULL) return; // now test this trak's stsd codec against these 4cc codes: switch (codec_atom->ancillary_data) { // video types case 0x61766331: // "avc1" track_codecs.has_avc1 = true; break; case 0x6D703476: // "mp4v" track_codecs.has_mp4v = true; break; case 0x64726D69: // "drmi" track_codecs.has_drmi = true; break; // audio types case 0x616C6163: // "alac" track_codecs.has_alac = true; break; case 0x6D703461: // "mp4a" track_codecs.has_mp4a = true; break; case 0x64726D73: // "drms" track_codecs.has_drms = true; break; // chapterized types (audio podcasts or movies) case 0x74657874: // "text" track_codecs.has_timed_text = true; break; case 0x6A706567: // "jpeg" track_codecs.has_timed_jpeg = true; break; // either podcast type (audio-only) or timed text subtitles case 0x74783367: // "tx3g" track_codecs.has_timed_tx3g = true; break; // other case 0x6D703473: // "mp4s" track_codecs.has_mp4s = true; break; case 0x72747020: // "rtp " track_codecs.has_rtp_hint = true; break; } } } return; } // TODO: there is a problem with this code seen in: "5.1channel audio-orig.mp4" // it makes no difference what the file contains, iTunes won't see any (ANY) // metadata if its hook/'M4A '. in fact, iTunes won't play the file at all // changing the exact file (with all kinds of metadata) to TVOD/mpg4 - iTunes // can play it fine, but doesn't fetch any metadata // // it might be beneficial to eval for channels and if its audio only & // multichannel to NOT change the TYPE/creator codes uint32_t APar_4CC_CreatorCode(const char *filepath, uint32_t new_type_code) { uint32_t return_value = 0; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *inFile = [NSString stringWithUTF8String:filepath]; if (new_type_code) { NSNumber *creator_code = [NSNumber numberWithUnsignedLong:'hook']; NSNumber *type_code = [NSNumber numberWithUnsignedLong:new_type_code]; NSDictionary *output_attributes = [NSDictionary dictionaryWithObjectsAndKeys:creator_code, NSFileHFSCreatorCode, type_code, NSFileHFSTypeCode, nil]; if (![[NSFileManager defaultManager] changeFileAttributes:output_attributes atPath:inFile]) { NSLog(@" AtomicParsley error: setting type and creator code on %@", inFile); } } else { NSDictionary *file_attributes = [[NSFileManager defaultManager] fileAttributesAtPath:inFile traverseLink:YES]; return_value = [[file_attributes objectForKey:NSFileHFSTypeCode] unsignedLongValue]; // NSLog(@"code: %@\n", [file_attributes objectForKey:NSFileHFSTypeCode] ); } [pool release]; return return_value; } // there is a scenario that is as of now unsupported (or botched, depending if // you use the feature), although it would be easy to implement. To make a file // bookmarkable, the TYPE code is set to 'M4B ' - which can be *also* done by // changing the extension to ".m4b". However, due to the way that the file is // tested here, a ".mp4" with 'M4B ' type code will get changed into a normal // audio file (not-bookmarkable). void APar_SupplySelectiveTypeCreatorCodes(const char *inputPath, const char *outputPath, uint8_t forced_type_code) { if (forced_type_code != NO_TYPE_FORCING) { if (forced_type_code == FORCE_M4B_TYPE) { APar_4CC_CreatorCode(outputPath, 'M4B '); } return; } const char *input_suffix = strrchr(inputPath, '.'); // user-defined output paths may have the original file as ".m4a" & show up // fine when output to ".m4a" output to ".mp4" and it becomes a generic (sans // TYPE/CREATOR) file that defaults to Quicktime Player const char *output_suffix = strrchr(outputPath, '.'); char *typecode = (char *)malloc(sizeof(char) * 4); memset(typecode, 0, sizeof(char) * 4); uint32_t type_code = APar_4CC_CreatorCode(inputPath, 0); UInt32_TO_String4(type_code, typecode); // fprintf(stdout, "%s - %s\n", typecode, input_suffix); APar_TestTracksForKind(); if (strncasecmp(input_suffix, ".mp4", 4) == 0 || strncasecmp(output_suffix, ".mp4", 4) == 0) { // only work on the generic .mp4 extension if (track_codecs.has_avc1 || track_codecs.has_mp4v || track_codecs.has_drmi) { type_code = APar_4CC_CreatorCode(outputPath, 'M4V '); // for a podcast an audio track with either a text, jpeg or url track is // required, otherwise it will fall through to generic m4a; files that are // already .m4b or 'M4B ' don't even enter into this situation, so they // are safe if the file had video with subtitles (tx3g), then it would get // taken care of above in the video section - unsupported by QT currently } else if (track_codecs.has_mp4a && (track_codecs.has_timed_text || track_codecs.has_timed_jpeg || track_codecs.has_timed_tx3g)) { type_code = APar_4CC_CreatorCode(outputPath, 'M4B '); // default to audio; technically so would a drms iTMS drm audio file with // ".mp4". But that would also mean it was renamed. They should be 'M4P ' } else { type_code = APar_4CC_CreatorCode(outputPath, 'M4A '); } } else if (track_codecs.has_avc1 || track_codecs.has_mp4v || track_codecs.has_drmi) { type_code = APar_4CC_CreatorCode(outputPath, 'M4V '); } return; } atomicparsley-20240608.083822.1ed9031/src/nsimage.mm000066400000000000000000000211731463107535600211520ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - nsimage.mm AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright 2005-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #include "AtomicParsley.h" #import static void DetermineType(const char *picfilePath, bool &isJPEG, bool &isPNG) { char picHeader[20]; FILE *pic_file = fopen(picfilePath, "rb"); u_int64_t r = fread(picHeader, 8, 1, pic_file); fclose(pic_file); if (memcmp(picHeader, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) == 0) { isPNG = true; isJPEG = false; } else if (memcmp(picHeader, "\xFF\xD8\xFF", 3) == 0) { isJPEG = true; isPNG = false; } else { isPNG = false; isJPEG = false; } } static char *DeriveNewPath(const char *filePath, PicPrefs myPicPrefs, char *newpath, size_t newpath_len) { const char *suffix = strrchr(filePath, '.'); size_t filepath_len = strlen(filePath); memset(newpath, 0, newpath_len); size_t base_len = filepath_len - strlen(suffix); memcpy(newpath, filePath, base_len); memcpy(newpath + base_len, "-resized-", 9); char *randstring = (char *)calloc(1, sizeof(char) * 20); struct timeval tv; gettimeofday(&tv, NULL); srand((int)tv.tv_usec / 1000); // Seeds rand() int randNum = rand() % 10000; sprintf(randstring, "%i", randNum); strcat(newpath, randstring); if (myPicPrefs.allJPEG) { strcat(newpath, ".jpg"); } else if (myPicPrefs.allPNG) { strcat(newpath, ".png"); } else { strcat(newpath, suffix); } free(randstring); randstring = NULL; return newpath; } static NSImage *DoResize(NSImage *sourceImage, NSSize newSize) { if (!sourceImage.isValid) { return nil; } NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:newSize.width pixelsHigh:newSize.height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:0 bitsPerPixel:0]; rep.size = newSize; [NSGraphicsContext saveGraphicsState]; [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:rep]]; [sourceImage drawInRect:NSMakeRect(0, 0, newSize.width, newSize.height) fromRect:NSZeroRect operation:NSCompositingOperationCopy fraction:1.0]; [NSGraphicsContext restoreGraphicsState]; NSImage *newImage = [[NSImage alloc] initWithSize:newSize]; [newImage addRepresentation:rep]; return newImage; } bool ResizeGivenImage(const char *filePath, PicPrefs myPicPrefs, char *resized_path, size_t resized_path_len) { bool resize = false; NSImage *source = [[NSImage alloc] initWithContentsOfFile:[NSString stringWithUTF8String:filePath]]; if (source == nil) { fprintf(stderr, "Image '%s' could not be loaded.\n", filePath); exit(1); } NSSize sourceSize = [source size]; float hmax, vmax, aspect; hmax = sourceSize.width; vmax = sourceSize.height; aspect = sourceSize.height / sourceSize.width; // fprintf(stdout, "aspect %f2.4\n", aspect); if (myPicPrefs.max_dimension != 0) { if (((int)sourceSize.width > myPicPrefs.max_dimension) || ((int)sourceSize.height > myPicPrefs.max_dimension)) { resize = true; // only if dimensions are LARGER than our max do we resize if (hmax > vmax) { hmax = myPicPrefs.max_dimension; vmax = myPicPrefs.max_dimension * aspect; } else { hmax = myPicPrefs.max_dimension / aspect; vmax = myPicPrefs.max_dimension; } } } ///// determine dpi/ppi float hres, vres, hdpi, vdpi; NSImageRep *myRep = [[source representations] objectAtIndex:0]; hres = [myRep pixelsWide]; // native pixel dimensions vres = [myRep pixelsHigh]; hdpi = hres / sourceSize .width; // in native resolution (multiply by 72 to get native dpi) vdpi = vres / sourceSize.height; if (((int)hdpi != 1) || ((int)vdpi != 1)) { resize = true; hmax = hres; vmax = vres; if (myPicPrefs.max_dimension != 0) { // we also need to recheck we don't go over our max dimensions (again) if (((int)hres > myPicPrefs.max_dimension) || ((int)vres > myPicPrefs.max_dimension)) { if (hmax > vmax) { hmax = myPicPrefs.max_dimension; vmax = myPicPrefs.max_dimension * aspect; } else { hmax = myPicPrefs.max_dimension / aspect; vmax = myPicPrefs.max_dimension; } } } } if (myPicPrefs.squareUp) { if (myPicPrefs.max_dimension != 0) { vmax = myPicPrefs.max_dimension; hmax = myPicPrefs.max_dimension; resize = true; } else { // this will stretch the image to the largest dimension. Hope you don't // try to scale a 160x1200 image... it could get ugly if (hmax > vmax) { vmax = hmax; resize = true; } else if (vmax > hmax) { hmax = vmax; resize = true; } } } if (myPicPrefs.force_dimensions) { if (myPicPrefs.force_height > 0 && myPicPrefs.force_width > 0) { vmax = myPicPrefs.force_height; hmax = myPicPrefs.force_width; resize = true; } } uint64_t pic_file_size = findFileSize(filePath); if (((int)pic_file_size > myPicPrefs.max_Kbytes) && (myPicPrefs.max_Kbytes != 0)) { resize = true; } bool isJPEG, isPNG; DetermineType(filePath, isJPEG, isPNG); if ((isJPEG && myPicPrefs.allPNG) || (isPNG && myPicPrefs.allJPEG)) { // handle jpeg->png & png->jpg conversion resize = true; } NSRect destinationRect = NSMakeRect(0, 0, hmax, vmax); NSSize size = NSMakeSize(hmax, vmax); if (resize) { NSImage *image = DoResize(source, size); NSData *imageData = [image TIFFRepresentation]; NSBitmapImageRep *bitmap = [NSBitmapImageRep imageRepWithData:imageData]; NSBitmapImageFileType filetype; NSDictionary *props; if ((isPNG && !myPicPrefs.allJPEG) || myPicPrefs.allPNG) { filetype = NSBitmapImageFileTypePNG; props = nil; } else { filetype = NSBitmapImageFileTypeJPEG; props = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.7] forKey:NSImageCompressionFactor]; } NSData *data = [bitmap representationUsingType:filetype properties:props]; unsigned dataLength = [data length]; // holds the file length int iter = 0; float compression = 0.65; if ((myPicPrefs.max_Kbytes != 0) && (filetype == NSBitmapImageFileTypeJPEG)) { while ((dataLength > (unsigned)myPicPrefs.max_Kbytes) && (iter < 10)) { props = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:compression] forKey:NSImageCompressionFactor]; data = [bitmap representationUsingType:filetype properties:props]; dataLength = [data length]; compression = compression - 0.05; iter++; } } NSString *outFile = [NSString stringWithUTF8String:DeriveNewPath(filePath, myPicPrefs, resized_path, resized_path_len)]; [[NSFileManager defaultManager] createFileAtPath:outFile contents:data attributes:nil]; [image release]; [bitmap release]; memcpy(resized_path, [outFile cStringUsingEncoding:NSUTF8StringEncoding], [outFile lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); } [source release]; return resize; } atomicparsley-20240608.083822.1ed9031/src/parsley.cpp000066400000000000000000007501071463107535600213650ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - parsley.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C) 2005-2007 puck_lock with contributions from others; see the CREDITS file ---------------------- Code Contributions by: * Mike Brancato - Debian patches & build support * Lowell Stewart - null-termination bugfix for Apple compliance * Brian Story - native Win32 patches; memset/framing/leaks fixes */ //==================================================================// #include "AtomDefs.h" #include "AtomicParsley.h" #include //#define DEBUG_V ///////////////////////////////////////////////////////////////////////////// // Global Variables // ///////////////////////////////////////////////////////////////////////////// bool modified_atoms = false; bool alter_original = false; bool preserve_timestamps = false; FILE *source_file = NULL; uint64_t file_size; struct AtomicInfo parsedAtoms[MAX_ATOMS]; short atom_number = 0; uint8_t generalAtomicLevel = 1; bool file_opened = false; bool parsedfile = false; bool move_moov_atom = true; bool moov_atom_was_mooved = false; AtomicInfo *hdlrAtom = NULL; AtomicInfo *movie_header_atom = NULL; bool complete_free_space_erasure = false; bool psp_brand = false; bool force_existing_hierarchy = false; int metadata_style = UNDEFINED_STYLE; bool deep_atom_scan = false; uint64_t max_buffer = #ifdef __linux__ 0.5 /* splice() allows us to use less buffer space */ #else 10 #endif * 1024 * 1024; uint64_t bytes_before_mdat = 0; uint64_t bytes_into_mdat = 0; uint64_t mdat_supplemental_offset = 0; uint64_t removed_bytes_tally = 0; uint64_t new_file_size = 0; // used for the progressbar uint32_t brand = 0; uint64_t mdatData = 0; // now global, used in bitrate calcs uint64_t gapless_void_padding = 0; // possibly used in the context of gapless playback support by Apple struct DynamicUpdateStat dynUpd; struct padding_preferences pad_prefs; short max_display_width = 55; char *file_progress_buffer = (char *)calloc( 1, sizeof(char) * (max_display_width + 50)); //+50 for any overflow in "%100", or "|" #if defined(__APPLE__) struct PicPrefs myPicturePrefs; #endif bool parsed_prefs = false; char *twenty_byte_buffer = (char *)malloc(sizeof(char) * 20); EmployedCodecs track_codecs = { false, false, false, false, false, false, false, false, false, false}; uint8_t UnicodeOutputStatus = UNIVERSAL_UTF8; // on windows, controls whether input/output strings are // utf16 or raw utf8; reset in wmain() uint8_t forced_suffix_type = NO_TYPE_FORCING; ///////////////////////////////////////////////////////////////////////////// // Versioning // ///////////////////////////////////////////////////////////////////////////// void ShowVersionInfo() { #if defined(_WIN32) char *unicode_enabled; if (UnicodeOutputStatus == WIN32_UTF16) { #ifndef __CYGWIN__ unicode_enabled = "(utf16)"; #else unicode_enabled = "(utf8 with utf16 CD access)"; #endif // its utf16 in the sense that any text entering on a modern Win32 system // enters as utf16le - but gets converted immediately after AP.exe starts // to utf8 all arguments, strings, filenames, options are sent around as // utf8. For modern Win32 systems, filenames get converted to utf16 for // output as needed. Any strings to be set as utf16 in 3gp assets are // converted to utf16be as needed (true for all OS implementations). // Printing out to the console should be utf8. } else if (UnicodeOutputStatus == UNIVERSAL_UTF8) { #ifndef __CYGWIN__ unicode_enabled = "(raw utf8)"; #else unicode_enabled = "(utf8 with raw utf8 CD access)"; #endif // utf8 in the sense that any text entered had its utf16 upper byte // stripped and reduced to (unchecked) raw utf8 for utilities that work in // utf8. Any unicode (utf16) filenames were clobbered in that processes are // invalid now. Any intermediate folder with unicode in it will now likely // cause an error of some sort. } #else #define unicode_enabled "(utf8)" #endif fprintf(stdout, "AtomicParsley version: %s %s %s\n", PACKAGE_VERSION, BUILD_INFO, unicode_enabled); } /////////////////////////////////////////////////////////////////////////////////////// // Generic Functions // /////////////////////////////////////////////////////////////////////////////////////// int APar_TestArtworkBinaryData(const char *artworkPath) { int artwork_dataType = 0; FILE *artfile = APar_OpenFile(artworkPath, "rb"); if (artfile != NULL) { APar_read64(twenty_byte_buffer, artfile, 0); if (strncmp(twenty_byte_buffer, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) == 0) { artwork_dataType = AtomFlags_Data_PNGBinary; } else if (memcmp(twenty_byte_buffer, "\xFF\xD8\xFF", 3) == 0) { artwork_dataType = AtomFlags_Data_JPEGBinary; } else { fprintf(stdout, "AtomicParsley error: %s\n\t image file is not jpg/png and " "cannot be embedded.\n", artworkPath); exit(1); } fclose(artfile); } else { fprintf(stdout, "AtomicParsley error: %s\n\t image file could not be opened.\n", artworkPath); exit(1); } return artwork_dataType; } void APar_FreeMemory() { for (int iter = 0; iter < atom_number; iter++) { if (parsedAtoms[iter].AtomicName != NULL) { free(parsedAtoms[iter].AtomicName); parsedAtoms[iter].AtomicName = NULL; } if (parsedAtoms[iter].AtomicData != NULL) { free(parsedAtoms[iter].AtomicData); parsedAtoms[iter].AtomicData = NULL; } if (parsedAtoms[iter].ReverseDNSname != NULL) { free(parsedAtoms[iter].ReverseDNSname); parsedAtoms[iter].ReverseDNSname = NULL; } if (parsedAtoms[iter].ReverseDNSdomain != NULL) { free(parsedAtoms[iter].ReverseDNSdomain); parsedAtoms[iter].ReverseDNSdomain = NULL; } if (parsedAtoms[iter].uuid_ap_atomname != NULL) { free(parsedAtoms[iter].uuid_ap_atomname); parsedAtoms[iter].uuid_ap_atomname = NULL; } if (parsedAtoms[iter].ID32_TagInfo != NULL) { // a cascade of tests & free-ing of all the accrued ID3v2 tag/frame/field // data APar_FreeID32Memory(parsedAtoms[iter].ID32_TagInfo); free(parsedAtoms[iter].ID32_TagInfo); parsedAtoms[iter].ID32_TagInfo = NULL; } } free(twenty_byte_buffer); twenty_byte_buffer = NULL; free(file_progress_buffer); file_progress_buffer = NULL; return; } /////////////////////////////////////////////////////////////////////////////////////// // Picture Preferences Functions // /////////////////////////////////////////////////////////////////////////////////////// #if defined(__APPLE__) PicPrefs APar_ExtractPicPrefs(char *env_PicOptions) { if (!parsed_prefs) { parsed_prefs = true; // only set default values & parse once myPicturePrefs.max_dimension = 0; // dimensions won't be used to alter image myPicturePrefs.dpi = 72; myPicturePrefs.max_Kbytes = 0; // no target size to shoot for myPicturePrefs.allJPEG = false; myPicturePrefs.allPNG = false; myPicturePrefs.addBOTHpix = false; myPicturePrefs.force_dimensions = false; myPicturePrefs.force_height = 0; myPicturePrefs.force_width = 0; myPicturePrefs.removeTempPix = true; // we'll just make this the default char *unparsed_opts = env_PicOptions; if (env_PicOptions == NULL) return myPicturePrefs; while (unparsed_opts[0] != 0) { if (strncmp(unparsed_opts, "MaxDimensions=", 14) == 0) { unparsed_opts += 14; myPicturePrefs.max_dimension = (int)strtol(unparsed_opts, NULL, 10); } else if (strncmp(unparsed_opts, "DPI=", 4) == 0) { unparsed_opts += 4; myPicturePrefs.dpi = (int)strtol(unparsed_opts, NULL, 10); } else if (strncmp(unparsed_opts, "MaxKBytes=", 10) == 0) { unparsed_opts += 10; myPicturePrefs.max_Kbytes = (int)strtol(unparsed_opts, NULL, 10) * 1024; } else if (strncmp(unparsed_opts, "AllPixJPEG=", 11) == 0) { unparsed_opts += 11; if (strcmp(unparsed_opts, "true") == 0) { myPicturePrefs.allJPEG = true; } } else if (strncmp(unparsed_opts, "AllPixPNG=", 10) == 0) { unparsed_opts += 10; if (strcmp(unparsed_opts, "true") == 0) { myPicturePrefs.allPNG = true; } } else if (strncmp(unparsed_opts, "AddBothPix=", 11) == 0) { unparsed_opts += 11; if (strcmp(unparsed_opts, "true") == 0) { myPicturePrefs.addBOTHpix = true; } } else if (strcmp(unparsed_opts, "SquareUp") == 0) { unparsed_opts += 7; myPicturePrefs.squareUp = true; } else if (strcmp(unparsed_opts, "removeTempPix") == 0) { unparsed_opts += 13; myPicturePrefs.removeTempPix = true; } else if (strcmp(unparsed_opts, "keepTempPix") == 0) { // NEW unparsed_opts += 11; myPicturePrefs.removeTempPix = false; } else if (strncmp(unparsed_opts, "ForceHeight=", 12) == 0) { unparsed_opts += 12; myPicturePrefs.force_height = strtol(unparsed_opts, NULL, 10); } else if (strncmp(unparsed_opts, "ForceWidth=", 11) == 0) { unparsed_opts += 11; myPicturePrefs.force_width = strtol(unparsed_opts, NULL, 10); } else { unparsed_opts++; } } } if (myPicturePrefs.force_height > 0 && myPicturePrefs.force_width > 0) myPicturePrefs.force_dimensions = true; return myPicturePrefs; } #endif /////////////////////////////////////////////////////////////////////////////////////// // Locating/Finding Atoms // /////////////////////////////////////////////////////////////////////////////////////// AtomicInfo *APar_FindAtomInTrack(uint8_t &total_tracks, uint8_t &track_num, const char *search_atom_str) { uint8_t track_tally = 0; short iter = 0; while (parsedAtoms[iter].NextAtomNumber != 0) { if (memcmp(parsedAtoms[iter].AtomicName, "trak", 4) == 0 && parsedAtoms[iter].AtomicLevel == 2) { track_tally += 1; if (track_num == 0) { total_tracks += 1; } else if (track_num == track_tally) { // drill down into stsd short next_atom = parsedAtoms[iter].NextAtomNumber; while (parsedAtoms[next_atom].AtomicLevel > parsedAtoms[iter].AtomicLevel) { if (strncmp(parsedAtoms[next_atom].AtomicName, search_atom_str, 4) == 0) { return &parsedAtoms[next_atom]; } else { next_atom = parsedAtoms[next_atom].NextAtomNumber; } } } } iter = parsedAtoms[iter].NextAtomNumber; } return NULL; } short APar_FindPrecedingAtom(short an_atom_num) { short precedingAtom = 0; short iter = 0; while (parsedAtoms[iter].NextAtomNumber != 0) { if (parsedAtoms[iter].NextAtomNumber == parsedAtoms[an_atom_num].NextAtomNumber) { break; } else { precedingAtom = iter; iter = parsedAtoms[iter].NextAtomNumber; } } return precedingAtom; } short APar_FindParentAtom(int order_in_tree, uint8_t this_atom_level) { short thisAtom = 0; short iter = order_in_tree; while (parsedAtoms[iter].AtomicNumber != 0) { iter = APar_FindPrecedingAtom(iter); if (parsedAtoms[iter].AtomicLevel == this_atom_level - 1) { thisAtom = iter; break; } } return thisAtom; } /*---------------------- APar_ProvideAtomPath this_atom - index into array of parsedAtoms for the wanted path of an atom atom_path - string into which the path will be placed (working backwards) fromFile - controls the manner of extracting parents (atom sizes from file, or a simpler atomic level if from memory) First, determine exactly how many atoms will constitute the full path and calculate where into the string to first start placing atom names. Start by working off the current atom. Using fromFile, either use a more stringent atom start/length from a file, or a more relaxed atom level if from memory. The array in memory won't have proper atom sizes except for the last child atom typically ('data' will have a proper size, but its parent and all other parents will not have sizing automatically updated - which happens only at writeout time). ----------------------*/ void APar_ProvideAtomPath(short this_atom, char *&atom_path, bool fromFile) { short preceding_atom = this_atom; uint8_t current_atomic_level = parsedAtoms[this_atom].AtomicLevel; int str_offset = (parsedAtoms[this_atom].AtomicLevel - 1) * 5; // 5 = 'atom" + '.' if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM) { str_offset += 5; // include a "uuid=" string; } memcpy(atom_path + str_offset, parsedAtoms[preceding_atom].AtomicName, 4); str_offset -= 5; if (parsedAtoms[preceding_atom].AtomicLevel != 1) { memcpy(atom_path + str_offset + 4, ".", 1); } if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM) { memcpy(atom_path + str_offset, "uuid=", 5); str_offset -= 5; } while (parsedAtoms[preceding_atom].AtomicNumber != 0) { if (fromFile) { if (parsedAtoms[preceding_atom].AtomicStart < parsedAtoms[this_atom].AtomicStart && parsedAtoms[preceding_atom].AtomicLength > parsedAtoms[this_atom].AtomicLength && parsedAtoms[preceding_atom].AtomicStart + parsedAtoms[preceding_atom].AtomicLength >= parsedAtoms[this_atom].AtomicStart + parsedAtoms[this_atom].AtomicLength && parsedAtoms[preceding_atom].AtomicContainerState <= DUAL_STATE_ATOM) { memcpy( atom_path + str_offset, parsedAtoms[preceding_atom].AtomicName, 4); str_offset -= 5; if (str_offset >= 0) { memcpy(atom_path + str_offset + 4, ".", 1); } preceding_atom = APar_FindPrecedingAtom(preceding_atom); } else { preceding_atom = APar_FindPrecedingAtom(preceding_atom); } } else { if (parsedAtoms[preceding_atom].AtomicLevel < current_atomic_level) { memcpy( atom_path + str_offset, parsedAtoms[preceding_atom].AtomicName, 4); str_offset -= 5; if (str_offset >= 0) { memcpy(atom_path + str_offset + 4, ".", 1); } current_atomic_level = parsedAtoms[preceding_atom].AtomicLevel; preceding_atom = APar_FindPrecedingAtom(preceding_atom); } else { preceding_atom = APar_FindPrecedingAtom(preceding_atom); } } if (preceding_atom == 0 || str_offset < 0) { break; } } return; } bool APar_Eval_ChunkOffsetImpact(short an_atom_num) { bool impact_calculations_directly = false; short iter = 0; uint8_t found_desired_atom = 0; while (true) { if (strncmp(parsedAtoms[iter].AtomicName, "mdat", 4) == 0) { if (found_desired_atom) { impact_calculations_directly = true; } break; } else { iter = parsedAtoms[iter].NextAtomNumber; } if (iter == 0) { break; } if (iter == an_atom_num) { found_desired_atom = 1; } } return impact_calculations_directly; } short APar_FindLastAtom() { short this_atom_num = 0; // start our search with the first atom while (parsedAtoms[this_atom_num].NextAtomNumber != 0) { this_atom_num = parsedAtoms[this_atom_num].NextAtomNumber; } return this_atom_num; } short APar_FindLastChild_of_ParentAtom(short thisAtom) { short child_atom = parsedAtoms[thisAtom].NextAtomNumber; short last_atom = thisAtom; // if there are no children, this will be the // first and last atom in the hiearchy while (true) { if (parsedAtoms[child_atom].AtomicLevel > parsedAtoms[thisAtom].AtomicLevel) { last_atom = child_atom; } child_atom = parsedAtoms[child_atom].NextAtomNumber; if (child_atom == 0 || parsedAtoms[child_atom].AtomicLevel <= parsedAtoms[thisAtom].AtomicLevel) { break; } } return last_atom; } /*---------------------- APar_ReturnChildrenAtoms this_atom - the parent atom that contains any number of children atoms (that are currenly unknown) atom_index - the index of the desired child. Passing 0 will return a count of the *total* number of children atoms under this_atom Working off of AtomicLevel, test the atoms that follow this_atom to see if they are immediately below this_atom. Increment total_children if is - if total_children should match our index, return that desired child at index atom. ----------------------*/ short APar_ReturnChildrenAtoms(short this_atom, uint8_t atom_index) { short child_atom = 0; uint8_t total_children = 0; short iter = parsedAtoms[this_atom].NextAtomNumber; while (true) { if ((parsedAtoms[iter].AtomicLevel == parsedAtoms[this_atom].AtomicLevel + 1 && this_atom > 0) || (this_atom == 0 && parsedAtoms[iter].AtomicLevel == 1)) { total_children++; if (atom_index == total_children) { child_atom = iter; break; } } if (parsedAtoms[iter].AtomicLevel <= parsedAtoms[this_atom].AtomicLevel && this_atom != 0) { break; } else { iter = parsedAtoms[iter].NextAtomNumber; } if (iter == 0) { break; } } if (atom_index == 0) { child_atom = (short)total_children; } return child_atom; } /*---------------------- APar_FindChildAtom parent_atom - the parent atom that contains any number of children atoms (that are currenly unknown) child_name - the name of the atom to search for under the parent_atom Given an atom, search its children for child_name ----------------------*/ AtomicInfo *APar_FindChildAtom(short parent_atom, const char *child_name, uint8_t child_name_len = 4, uint16_t desired_index = 1) { AtomicInfo *found_child = NULL; short test_child_idx = parsedAtoms[parent_atom].NextAtomNumber; uint16_t current_count = 0; while (parsedAtoms[test_child_idx].AtomicLevel > parsedAtoms[parent_atom].AtomicLevel) { if ((memcmp(parsedAtoms[test_child_idx].AtomicName, child_name, child_name_len) == 0 || memcmp(child_name, "any", 4) == 0) && parsedAtoms[test_child_idx].AtomicLevel == parsedAtoms[parent_atom].AtomicLevel + 1) { current_count++; if (desired_index == current_count) { found_child = &parsedAtoms[test_child_idx]; break; } } test_child_idx = parsedAtoms[test_child_idx].NextAtomNumber; } return found_child; } /*---------------------- APar_AtomicComparison proto_atom - the temporary atom structure to run the tests on test_atom - the exising atom to compare the proto_atom against match_full_uuids - selects whether to match by atom names (4 bytes) or uuids(16 bytes) which are stored on AtomicName reverseDNSdomain - the reverse DNS like com.foo.thing (only used with reverseDNS atoms: ----, mean, name) Test if proto_atom matches a single atom (test_atom) by name, level & classification (packed_lang_atom, extended atom...); for certain types of data (like packed_lang & reverseDNS 'moov.udta.meta.ilst.----.name:[iTunNORM] atoms currently) add finer grained tests. The return result will be NULL if not matched, or returns the atom it matches. ----------------------*/ AtomicInfo *APar_AtomicComparison(AtomicInfo *proto_atom, short test_atom, bool match_full_uuids, const char *reverseDNSdomain) { AtomicInfo *return_atom = NULL; size_t ATOM_TEST_LEN = (match_full_uuids ? 16 : 4); if (parsedAtoms[test_atom].AtomicClassification == EXTENDED_ATOM && parsedAtoms[test_atom].uuid_style == UUID_DEPRECATED_FORM) { // accommodate deprecated form if (memcmp(parsedAtoms[test_atom].uuid_ap_atomname, proto_atom->AtomicName, 4) == 0) { return &parsedAtoms[test_atom]; } } // can't do AtomicVerFlags because lots of utilities don't write the proper // iTunes flags for iTunes metadata if (memcmp(proto_atom->AtomicName, parsedAtoms[test_atom].AtomicName, ATOM_TEST_LEN) == 0 && proto_atom->AtomicLevel == parsedAtoms[test_atom].AtomicLevel && (proto_atom->AtomicClassification == parsedAtoms[test_atom].AtomicClassification || proto_atom->AtomicClassification == UNKNOWN_ATOM)) { if (proto_atom->AtomicClassification == PACKED_LANG_ATOM) { // 0x05D9 = 'any' and will be used (internally) to match on // name,class,container state alone, disregarding AtomicLanguage if (proto_atom->AtomicLanguage == parsedAtoms[test_atom].AtomicLanguage || proto_atom->AtomicLanguage == 0x05D9) { return_atom = &parsedAtoms[test_atom]; } } else if (proto_atom->ReverseDNSname != NULL && parsedAtoms[test_atom].ReverseDNSname != NULL) { // match on moov.udta.meta.ilst.----.name:[something] (reverse DNS atom) if (strcmp(proto_atom->ReverseDNSname, parsedAtoms[test_atom].ReverseDNSname) == 0) { if (reverseDNSdomain == NULL) { // lock onto the first reverseDNS form irrespective of // domain (TODO: manualAtomRemove will cause this to be // NULL) return_atom = &parsedAtoms[test_atom]; } else { #if defined(DEBUG_V) fprintf( stdout, "AP_AtomicComparison testing wanted rDNS %s domain against " "atom '%s' %s rDNS domain\n", reverseDNSdomain, parsedAtoms[test_atom].AtomicName, parsedAtoms[APar_FindPrecedingAtom(test_atom)].ReverseDNSdomain); #endif if (strcmp(reverseDNSdomain, parsedAtoms[APar_FindPrecedingAtom(test_atom)] .ReverseDNSdomain) == 0) { return_atom = &parsedAtoms[test_atom]; } } } } else { return_atom = &parsedAtoms[test_atom]; } } return return_atom; } /*---------------------- APar_FindLastLikeNamedAtom atom_name - the name of the atom to search for; the string itself may have more than 4 bytes containing_hierarchy - the parent hierarchy that is expected to carry multiply named atoms differing (in language for example) Follow through the atom tree; if a test atom is matched by name, and is a child to the container atom, remember that atom. If nothing matches, the index of the container atom is returned; otherwise the last like named atom is returned. ----------------------*/ short APar_FindLastLikeNamedAtom(char *atom_name, short containing_hierarchy) { short last_identically_named_atom = APar_FindLastChild_of_ParentAtom( containing_hierarchy); // default returns the last atom in the parent, not // the parent short eval_atom = parsedAtoms[containing_hierarchy].NextAtomNumber; while (true) { if (parsedAtoms[eval_atom].AtomicLevel < parsedAtoms[containing_hierarchy].AtomicLevel + 1 || eval_atom == 0) { break; } else { if (memcmp(parsedAtoms[eval_atom].AtomicName, atom_name, 4) == 0 && parsedAtoms[eval_atom].AtomicLevel == parsedAtoms[containing_hierarchy].AtomicLevel + 1) { last_identically_named_atom = eval_atom; } eval_atom = parsedAtoms[eval_atom].NextAtomNumber; } } return last_identically_named_atom; } void APar_FreeSurrogateAtom(AtomicInfo *surrogate_atom) { if (surrogate_atom->ReverseDNSname != NULL) { free(surrogate_atom->ReverseDNSname); surrogate_atom->ReverseDNSname = NULL; } return; } /*---------------------- APar_CreateSurrogateAtom Make a temporary AtomicInfo structure to run comparisons against; currently comparisons are done on name, level, classification (versioned...), langauge (3gp assets), and iTunes-style reverse dns 'name' carrying a string describing the purpose of the data (iTunNORM). This atom exists outside of a file's atom hieararchy that resides in the parsedAtoms[] array. ----------------------*/ void APar_CreateSurrogateAtom(AtomicInfo *surrogate_atom, const char *atom_name, uint8_t atom_level, uint8_t atom_class, uint16_t atom_lang, char *revdns_name, uint8_t revdns_name_len) { surrogate_atom->AtomicName = (char *)atom_name; surrogate_atom->AtomicLevel = atom_level; if (revdns_name != NULL && revdns_name_len) { surrogate_atom->ReverseDNSname = (char *)malloc( sizeof(char) * revdns_name_len > 8 ? revdns_name_len + 1 : 9); memset(surrogate_atom->ReverseDNSname, 0, sizeof(char) * revdns_name_len > 8 ? revdns_name_len + 1 : 9); memcpy(surrogate_atom->ReverseDNSname, revdns_name, revdns_name_len); } else { APar_FreeSurrogateAtom(surrogate_atom); } surrogate_atom->AtomicClassification = atom_class; surrogate_atom->AtomicLanguage = atom_lang; return; } /*---------------------- APar_FindAtom atom_name - the full path describing the hiearchy the desired atom can be found in createMissing - either create the missing interim atoms as required, or return a NULL if not found atom_type - the classification of the last atom (packed language, uuid extended atom...) atom_lang - the language of the 3gp asset used when atom_type is packed language type match_full_uuids - match 16byte full uuids (typically removing ( possibly non-AP) uuids via --manualAtomRemoval; AP uuids (the new ones) still work on 4bytes** reverseDNSdomain - the reverse DNS like com.foo.thing (only used with reverseDNS atoms: ----, mean, name) Follow through the atom tree starting with the atom following 'ftyp'. Testing occurs on an atom level basis; a stand-in temporary skeletal atom is created to evaluate. If they atoms are deemed matching, atom_name is advanced forward (it still contains the full path, but only 4bytes are typically used at a time) and testing occurs until either the desired atom is found, or the last containing hiearchy with an exising atom is exhausted without making new atoms. NOTE: atom_name can come in these forms: classic/vanilla/ordinary atoms: moov.udta.meta.ilst.cprt.data iTunes reverseDNS atoms: moov.udta.meta.ilst.----.name:[iTunNORM] uuid user-extension atoms: moov.udta.meta.uuid=tdtg (the deprecated form) uuid user-extension atoms: moov.udta.meta.uuid=ba45fcaa-7ef5-5201-8a63-78886495ab1f index-based atoms: moov.trak[2].mdia.minf NOTE: On my computer it takes about .04 second to scan the file, .1 second to add about 2 dozen tags, and 1.0 second to copy a file. Updating a file from start to finish takes 0.21 seconds. As many loops as this new APar_FindAtom eliminates, it is only marginally faster than the old code. ** the reason why the old deprecated uuid form & the new uuid full 16byte form work off of a 4byte value (the atom name) is that because we are using a version 5 sha1 hashed uuid of a name in a given namespace, the identical name in the identical namespace will yield identical an identical uuid (if corrected for endianness). This means that that matching by 4 bytes of atom name is the functional equvalent of matching by 16byte uuids. ----------------------*/ AtomicInfo *APar_FindAtom(const char *atom_name, bool createMissing, uint8_t atom_type, uint16_t atom_lang, bool match_full_uuids, const char *reverseDNSdomain) { AtomicInfo *thisAtom = NULL; char *search_atom_name = (char *)atom_name; char *reverse_dns_name = NULL; uint8_t revdns_name_len = 0; uint8_t atom_index = 0; // if there are atoms mutliple identically named at the same level, // this is where to store the count as it occurs uint8_t desired_index = 1; uint8_t search_atom_type = UNKNOWN_ATOM; int known_atom = -1; short search_atom_start_num = parsedAtoms[0] .NextAtomNumber; // don't test 'ftyp'; its atom_number[0] & will be // used to know when we have hit the end of the tree; // can't hardcode it to '1' because ftyp's following // atom can change; only ftyp as parsedAtoms[0] is // guaranteed. uint8_t present_atomic_level = 1; AtomicInfo *last_known_present_parent = NULL; AtomicInfo atom_surrogate = {0}; #if defined(DEBUG_V) fprintf(stdout, "debug: AP_FindAtom entry trying to find '%s'; create missing: %u\n", atom_name, createMissing); #endif while (search_atom_name != NULL) { desired_index = 1; // reset the index if (atom_type == EXTENDED_ATOM && strncmp(search_atom_name, "uuid=", 5) == 0) { search_atom_name += 5; search_atom_type = atom_type; } #if defined(DEBUG_V) fprintf(stdout, "debug: AP_FindAtom loop evaluate test %s (index=%u)\n", search_atom_name, atom_index); #endif size_t portion_len = strlen(search_atom_name); if (strncmp(search_atom_name + 4, ":[", 2) == 0 && search_atom_name[portion_len - 1] == ']') { reverse_dns_name = search_atom_name + 4 + 2; // 4bytes atom name 2bytes ":[" revdns_name_len = portion_len - 7; // 4bytes atom name, 2 bytes ":[", 1 byte "]" search_atom_type = atom_type; } else if (search_atom_name[4] == '[') { desired_index = strtoul(search_atom_name + 5, NULL, 10); #if defined(DEBUG_V) fprintf(stdout, "debug: AP_FindAtom >##< '%s' at index=%u\n", search_atom_name, desired_index); #endif } if (strlen(search_atom_name) == 4) { if (atom_type == UNKNOWN_ATOM) { known_atom = APar_MatchToKnownAtom(search_atom_name, last_known_present_parent->AtomicName, false, atom_name); search_atom_type = KnownAtoms[known_atom].box_type; } else { search_atom_type = atom_type; } } APar_CreateSurrogateAtom(&atom_surrogate, search_atom_name, present_atomic_level, search_atom_type, atom_lang, reverse_dns_name, revdns_name_len); atom_index = 0; short iter = search_atom_start_num; while (true) { AtomicInfo *result = NULL; // if iter == 0, that means test against 'ftyp' - and since its always 0, // don't test it; its to know that the end of the tree is reached if (iter != 0 && (parsedAtoms[iter].AtomicLevel == present_atomic_level || reverse_dns_name != NULL)) { result = APar_AtomicComparison( &atom_surrogate, iter, (search_atom_type == EXTENDED_ATOM ? match_full_uuids : false), reverseDNSdomain); #if defined(DEBUG_V) fprintf(stdout, "debug: AP_FindAtom compare %s(%u) against %s (wanted " "index=%u)\n", search_atom_name, atom_index, parsedAtoms[iter].AtomicName, desired_index); } else { fprintf(stdout, "debug: AP_FindAtom %s rejected against %s\n", search_atom_name, parsedAtoms[iter].AtomicName); #endif } if (result != NULL) { // something matched atom_index++; #if defined(DEBUG_V) fprintf(stdout, "debug: AP_FindAtom ***matched*** current index=%u (want " "%u)\n", atom_index, desired_index); #endif if (search_atom_type != UNKNOWN_ATOM || (search_atom_type == UNKNOWN_ATOM && known_atom != -1)) { thisAtom = result; #if defined(DEBUG_V) fprintf(stdout, "debug: AP_FindAtom perfect match: %s(%u) == " "existing %s(%u)\n", search_atom_name, desired_index, parsedAtoms[iter].AtomicName, atom_index); #endif } else { last_known_present_parent = result; // if not, then it isn't the last atom, and must be some // form of parent } if (desired_index == atom_index) { search_atom_start_num = parsedAtoms[iter].NextAtomNumber; break; } } if (parsedAtoms[iter].AtomicLevel < present_atomic_level && reverse_dns_name == NULL) { iter = 0; // force the ending determination of whether to make new atoms // or not; } if (iter == 0 && createMissing) { // create that atom if (last_known_present_parent != NULL) { short last_hierarchical_atom = 0; #if defined(DEBUG_V) fprintf( stdout, "debug: AP_FindAtom-------missing atom, need to create '%s'\n", search_atom_name); #endif if (search_atom_type == PACKED_LANG_ATOM) { last_hierarchical_atom = APar_FindLastLikeNamedAtom( atom_surrogate.AtomicName, last_known_present_parent->AtomicNumber); } else { last_hierarchical_atom = APar_FindLastChild_of_ParentAtom( last_known_present_parent->AtomicNumber); } thisAtom = APar_CreateSparseAtom(&atom_surrogate, last_known_present_parent, last_hierarchical_atom); search_atom_start_num = thisAtom->AtomicNumber; if (strlen(search_atom_name) >= 4) { last_known_present_parent = thisAtom; } } else { // its a file-level atom that needs to be created, so it won't have a // last_known_present_parent if (strlen(atom_name) == 4) { short total_root_level_atoms = APar_ReturnChildrenAtoms(0, 0); short test_root_atom = 0; // scan through all top level atoms for (uint8_t root_atom_i = 1; root_atom_i <= total_root_level_atoms; root_atom_i++) { test_root_atom = APar_ReturnChildrenAtoms(0, root_atom_i); if (memcmp(parsedAtoms[test_root_atom].AtomicName, "moov", 4) == 0) { break; } } if (test_root_atom != 0) { thisAtom = APar_CreateSparseAtom( &atom_surrogate, NULL, APar_FindLastChild_of_ParentAtom(test_root_atom)); } } } break; } else if (iter == 0 && !createMissing) { search_atom_name = NULL; // force the break; break; } // fprintf(stdout, "while loop %s %u %u\n", parsedAtoms[iter].AtomicName, // atom_index, desired_index); iter = parsedAtoms[iter].NextAtomNumber; } if (iter == 0 && (search_atom_name == NULL || search_atom_type == EXTENDED_ATOM)) { break; } else { uint8_t periodicity = 0; // allow atoms with periods in their names while (true) { // search_atom_name = strsep(&atom_name,".") equivalent if (search_atom_name[0] == 0) { search_atom_name = NULL; break; } else if (search_atom_name[0] == '.' && periodicity > 3) { search_atom_name++; periodicity++; break; } else { search_atom_name++; periodicity++; } } present_atomic_level++; } } // APar_PrintAtomicTree(); //because PrintAtomicTree calls // DetermineDynamicUpdate (which calls this FindAtom function) to print out // padding space, an infinite loop occurs APar_FreeSurrogateAtom(&atom_surrogate); return thisAtom; } /////////////////////////////////////////////////////////////////////////////////////// // File scanning & atom parsing // /////////////////////////////////////////////////////////////////////////////////////// void APar_AtomizeFileInfo(uint64_t Astart, uint64_t Alength, uint64_t Aextendedlength, char *Astring, uint8_t Alevel, uint8_t Acon_state, uint8_t Aclass, uint32_t Averflags, uint16_t Alang, uuid_vitals *uuid_info) { static bool passed_mdat = false; AtomicInfo *thisAtom; if (atom_number < 0 || atom_number >= MAX_ATOMS) { fprintf(stderr, "too many atoms\n"); abort(); } thisAtom = &parsedAtoms[atom_number]; thisAtom->AtomicStart = Astart; thisAtom->AtomicLength = Alength; thisAtom->AtomicLengthExtended = Aextendedlength; thisAtom->AtomicNumber = atom_number; thisAtom->AtomicLevel = Alevel; thisAtom->AtomicContainerState = Acon_state; thisAtom->AtomicClassification = Aclass; thisAtom->AtomicName = (char *)malloc(sizeof(char) * 20); memset(thisAtom->AtomicName, 0, sizeof(char) * 20); if (Aclass == EXTENDED_ATOM) { thisAtom->uuid_style = uuid_info->uuid_form; if (uuid_info->uuid_form == UUID_DEPRECATED_FORM) { memcpy(thisAtom->AtomicName, Astring, 4); thisAtom->uuid_ap_atomname = (char *)calloc(1, sizeof(char) * 16); memcpy(thisAtom->uuid_ap_atomname, Astring, 4); } else { memcpy(thisAtom->AtomicName, uuid_info->binary_uuid, 16); if (uuid_info->uuid_form == UUID_AP_SHA1_NAMESPACE) { thisAtom->uuid_ap_atomname = (char *)calloc(1, sizeof(char) * 16); memcpy(thisAtom->uuid_ap_atomname, uuid_info->uuid_AP_atom_name, 4); } } } else { memcpy(thisAtom->AtomicName, Astring, 4); } thisAtom->AtomicVerFlags = Averflags; thisAtom->AtomicLanguage = Alang; thisAtom->ancillary_data = 0; // set the next atom number of the PREVIOUS atom (we didn't know there would // be one until now); this is our default normal mode if (atom_number > 0) { parsedAtoms[atom_number - 1].NextAtomNumber = atom_number; } thisAtom->NextAtomNumber = 0; // this could be the end... (we just can't quite // say until we find another atom) if (strncmp(Astring, "mdat", 4) == 0) { passed_mdat = true; } if (!passed_mdat && Alevel == 1) { bytes_before_mdat += Alength; // this value gets used during FreeFree (for // removed_bytes_tally) & chunk offset calculations } thisAtom->ID32_TagInfo = NULL; atom_number++; // increment to the next AtomicInfo array return; } uint8_t APar_GetCurrentAtomDepth(uint64_t atom_start, uint64_t atom_length) { uint8_t level = 1; for (int i = 0; i < atom_number; i++) { AtomicInfo *thisAtom = &parsedAtoms[i]; if (atom_start == (thisAtom->AtomicStart + thisAtom->AtomicLength)) { return thisAtom->AtomicLevel; } else { if ((atom_start < thisAtom->AtomicStart + thisAtom->AtomicLength) && (atom_start > thisAtom->AtomicStart)) { level++; } } } return level; } void APar_IdentifyBrand(char *file_brand) { brand = UInt32FromBigEndian(file_brand); switch (brand) { // what ISN'T supported case 0x71742020: //'qt ' --this is listed at mp4ra, but there are features // of the file that aren't supported (like the 4 NULL bytes // after the last udta child atom fprintf(stdout, "AtomicParsley error: Quicktime movie files are not supported.\n"); exit(2); break; // // 3GPP2 specification documents brands // case 0x33673262: //'3g2b' 3GPP2 release A metadata_style = THIRD_GEN_PARTNER_VER2_REL_A; // 3GPP2 C.S0050-A_v1.0_060403, Annex A.2 // lists differences between 3GPP & 3GPP2 // - assets are not listed break; case 0x33673261: //'3g2a' //3GPP2 release 0 metadata_style = THIRD_GEN_PARTNER_VER2; break; // // 3GPP specification documents brands, not all are listed at mp4ra // case 0x33677037: //'3gp7' //Release 7 introduces // ID32; though it doesn't list a iso bmffv2 compatible // brand. Technically, ID32 could be used on older 3gp // brands, but iso2 would have to be added to the compatible // brand list. case 0x33677337: //'3gs7' //I don't feel the need to // do that, since other things might have to be done. And I'm // not looking into it. case 0x33677237: //'3gr7' case 0x33676537: //'3ge7' case 0x33676737: //'3gg7' metadata_style = THIRD_GEN_PARTNER_VER1_REL7; break; case 0x33677036: //'3gp6' //3gp assets which were // introducted by NTT DoCoMo to the Rel6 workgroup on January // 16, 2003 with S4-030005.zip from // http://www.3gpp.org/ftp/tsg_sa/WG4_CODEC/TSGS4_25/Docs/ (! // albm, loci) case 0x33677236: //'3gr6' progressive case 0x33677336: //'3gs6' streaming case 0x33676536: //'3ge6' extended presentations (jpeg images) case 0x33676736: //'3gg6' general (not yet suitable; superset) metadata_style = THIRD_GEN_PARTNER_VER1_REL6; break; case 0x33677034: //'3gp4' //3gp assets (the full // complement) are available: source clause is S5.5 of // TS26.244 (Rel6.4 & later): case 0x33677035: //'3gp5' //"that the file conforms // to the specification; it includes everything required by, metadata_style = THIRD_GEN_PARTNER; // and nothing contrary to the specification (though // there may be other material)" break; // it stands to reason that 3gp assets aren't contrary since 'udta' // is defined by iso bmffv1 // // other brands that are have compatible brands relating to 3GPP/3GPP2 // case 0x6B646469: //'kddi' //3GPP2 EZmovie (optionally // restricted) media; these have a 3GPP2 compatible brand metadata_style = THIRD_GEN_PARTNER_VER2; break; case 0x6D6D7034: //'mmp4' metadata_style = THIRD_GEN_PARTNER; break; // // what IS supported for iTunes-style metadata // case 0x4D534E56: //'MSNV' (PSP) - this isn't actually listed at mp4ra, but // since they are popular... metadata_style = ITUNES_STYLE; psp_brand = true; break; case 0x4D344120: //'M4A ' -- these are all listed at // https://mp4ra.org/#/brands as registered brands case 0x4D344220: //'M4B ' case 0x4D345020: //'M4P ' case 0x4D345620: //'M4V ' case 0x4D345648: //'MV4H' case 0x4D345650: //'M4VP' case 0x64617368: //'dash' case 0x66347620: //'f4v' case 0x6D703432: //'mp42' case 0x6D703431: //'mp41' case 0x69736F6D: //'isom' case 0x69736F32: //'iso2' case 0x61766331: //'avc1' metadata_style = ITUNES_STYLE; break; // // other brands that are derivatives of the ISO Base Media File Format // case 0x6D6A7032: //'mjp2' case 0x6D6A3273: //'mj2s' metadata_style = MOTIONJPEG2000; break; // other lesser unsupported brands; http://www.mp4ra.org/filetype.html like // dv, mp21 & ... whatever mpeg7 brand is default: fprintf(stdout, "AtomicParsley error: unsupported MPEG-4 file brand found '%s'\n", file_brand); exit(2); break; } return; } void APar_TestCompatibleBrand(FILE *file, uint64_t atom_start, uint64_t atom_length) { if (atom_length <= 16) return; uint32_t compatible_brand = 0; for (uint32_t brand = 16; brand < atom_length; brand += 4) { compatible_brand = APar_read32(twenty_byte_buffer, file, atom_start + brand); if (compatible_brand == 0x6D703432 || compatible_brand == 0x69736F32) { parsedAtoms[atom_number - 1].ancillary_data = compatible_brand; } } return; } void APar_Extract_stsd_codec(FILE *file, uint64_t midJump) { memset(twenty_byte_buffer, 0, 12); APar_readX(twenty_byte_buffer, file, midJump, 12); parsedAtoms[atom_number - 1].ancillary_data = UInt32FromBigEndian(twenty_byte_buffer + 4); return; } /*---------------------- APar_LocateDataReference fill ----------------------*/ void APar_LocateDataReference(short chunk_offset_idx, FILE *file) { uint32_t data_ref_idx = 0; short sampletable_atom_idx = 0; short minf_atom_idx = 0; AtomicInfo *stsd_atom, *dinf_atom, *dref_atom, *target_reference_atom = NULL; sampletable_atom_idx = APar_FindParentAtom( chunk_offset_idx, parsedAtoms[chunk_offset_idx].AtomicLevel); stsd_atom = APar_FindChildAtom(sampletable_atom_idx, "stsd"); if (stsd_atom == NULL) { return; } data_ref_idx = APar_read32(twenty_byte_buffer, file, stsd_atom->AtomicStart + 28); minf_atom_idx = APar_FindParentAtom( sampletable_atom_idx, parsedAtoms[sampletable_atom_idx].AtomicLevel); dinf_atom = APar_FindChildAtom(minf_atom_idx, "dinf"); dref_atom = APar_FindChildAtom(dinf_atom->AtomicNumber, "dref"); target_reference_atom = APar_FindChildAtom(dref_atom->AtomicNumber, "any", 4, data_ref_idx); if (target_reference_atom != NULL) { parsedAtoms[chunk_offset_idx].ancillary_data = target_reference_atom->AtomicVerFlags; } return; } void APar_SampleTableIterator(FILE *file) { uint8_t total_tracks = 0; uint8_t a_track = 0; char track_path[36]; memset(track_path, 0, 36); AtomicInfo *samples_parent = NULL; AtomicInfo *chunk_offset_atom = NULL; APar_FindAtomInTrack(total_tracks, a_track, NULL); // gets the number of // tracks for (uint8_t trk_idx = 1; trk_idx <= total_tracks; trk_idx++) { sprintf(track_path, "moov.trak[%u].mdia.minf.stbl", trk_idx); samples_parent = APar_FindAtom(track_path, false, SIMPLE_ATOM, 0, false); if (samples_parent != NULL) { chunk_offset_atom = APar_FindChildAtom(samples_parent->AtomicNumber, "stco"); if (chunk_offset_atom == NULL) chunk_offset_atom = APar_FindChildAtom(samples_parent->AtomicNumber, "co64"); if (chunk_offset_atom != NULL) { APar_LocateDataReference(chunk_offset_atom->AtomicNumber, file); } } } return; } /*---------------------- APar_MatchToKnownAtom atom_name - the name of our newly found atom atom_container - the name of the parent container atom fromFile - controls the manner of extracting parents (passed thu to another function) Using the atom_name of this new atom, search through KnownAtoms, testing that the names match. If they do, move onto a finer grained sieve. If the parent can be at any level (like "free"), just let it through; if the parent is "ilst" (iTunes-style metadata), or a uuid, return a generic match The final test is the one most atoms will go through. Some atoms can have different parents - up to 5 different parents are allowed by this version of AP Iterate through the known parents, and test it against atom_container. If they match, return the properties of the known atom ----------------------*/ int APar_MatchToKnownAtom(const char *atom_name, const char *atom_container, bool fromFile, const char *find_atom_path) { uint32_t total_known_atoms = (sizeof(KnownAtoms) / sizeof(*KnownAtoms)); uint32_t return_known_atom = 0; // if this atom is contained by 'ilst', then it is *highly* likely an // iTunes-style metadata parent atom if (memcmp(atom_container, "ilst", 4) == 0 && memcmp(atom_name, "uuid", 4) != 0) { return_known_atom = total_known_atoms - 2; // 2nd to last KnowAtoms is a generic placeholder iTunes-parent atom // fprintf(stdout, "found iTunes parent %s = atom %s\n", // KnownAtoms[return_known_atom].known_atom_name, atom_name); // if this atom is "data" get the full path to it; we will take any atom // under 'ilst' and consider it an iTunes metadata parent atom } else if (memcmp(atom_name, "data", 4) == 0 && find_atom_path != NULL) { if (strncmp(find_atom_path, "moov.udta.meta.ilst.", 20) == 0) { return_known_atom = total_known_atoms - 1; // last KnowAtoms is a generic placeholder iTunes-data atom // fprintf(stdout, "found iTunes data child\n"); } } else if (memcmp(atom_name, "data", 4) == 0) { char *fullpath = (char *)malloc(sizeof(char) * 200); memset(fullpath, 0, sizeof(char) * 200); if (fromFile) { APar_ProvideAtomPath( parsedAtoms[atom_number - 1].AtomicNumber, fullpath, fromFile); } else { // find_atom_path only is NULL in APar_ScanAtoms (where fromFile is // true) and in APar_CreateSparseAtom, where atom_number was just // filled APar_ProvideAtomPath( parsedAtoms[atom_number].AtomicNumber, fullpath, fromFile); } // fprintf(stdout, "APar_ProvideAtomPath gives %s (%s-%s)\n", fullpath, // atom_name, atom_container); if (strncmp(fullpath, "moov.udta.meta.ilst.", 20) == 0) { return_known_atom = total_known_atoms - 1; // last KnowAtoms is a generic placeholder iTunes-data atom // fprintf(stdout, "found iTunes data child\n"); } free(fullpath); fullpath = NULL; // if this atom is "esds" get the full path to it; take any atom under // 'stsd' as a parent to esds (that parent would be a 4CC codec; not all do // have 'esds'...) } else if (memcmp(atom_name, "esds", 4) == 0) { char *fullpath = (char *)malloc(sizeof(char) * 300); memset(fullpath, 0, sizeof(char) * 200); APar_ProvideAtomPath( parsedAtoms[atom_number - 1].AtomicNumber, fullpath, fromFile); if (strncmp(fullpath, "moov.trak.mdia.minf.stbl.stsd.", 30) == 0) { return_known_atom = total_known_atoms - 3; // manually return the esds // atom } free(fullpath); fullpath = NULL; } else { // try matching the name of the atom for (uint32_t i = 1; i < total_known_atoms; i++) { if (memcmp(atom_name, KnownAtoms[i].known_atom_name, 4) == 0) { // name matches, now see if the container atom matches any known // container for that atom if (strncmp(KnownAtoms[i].known_parent_atoms[0], "_ANY_LEVEL", 10) == 0) { return_known_atom = i; // the list starts at 0; the unknown atom is at // 0; first known atom (ftyp) is at 1 break; } else { uint8_t total_known_containers = (uint8_t)(sizeof(KnownAtoms[i].known_parent_atoms) / sizeof(*KnownAtoms[i].known_parent_atoms)); // always 5 for (uint8_t iii = 0; iii < total_known_containers; iii++) { if (KnownAtoms[i].known_parent_atoms[iii] != NULL) { if (strncmp(atom_container, KnownAtoms[i].known_parent_atoms[iii], strlen(atom_container)) == 0) { // strlen(atom_container) return_known_atom = i; // the list starts at 0; the unknown atom is at 0; first // known atom (ftyp) is at 1 break; } } } } if (return_known_atom) { break; } } } } if (return_known_atom > total_known_atoms) { return_known_atom = 0; } // accommodate any future child to dref; force to being versioned if (return_known_atom == 0 && memcmp(atom_container, "dref", 4) == 0) { return_known_atom = total_known_atoms - 4; // return a generic *VERSIONED* child atom; otherwise an atom without // flags will be present & chunk offsets will not update } return return_known_atom; } /*---------------------- APar_Manually_Determine_Parent atom_start - the place in the file where the atom begins atom_length - length of the eval atom container - a string of the last known container atom (or FILE_LEVEL) given the next atom (unknown string at this point), run some tests using its starting point and length. Iterate backwards through the already parsed atoms, and for each test if it could contain this atom. Tests include if the container starts before ours (which it would to contain the new atom), that its length is longer than our length (any parent would need to be longer than us if even by 8 bytes), the start + length sum of the parent atom (where it ends), needs to be greater than or equal to where this new atom ends, and finally, that the eval containing atom be some form of parent as defined in KnownAtoms ----------------------*/ void APar_Manually_Determine_Parent(uint64_t atom_start, uint64_t atom_length, char *container) { short preceding_atom = atom_number - 1; while (parsedAtoms[preceding_atom].AtomicNumber != 0) { if (parsedAtoms[preceding_atom].AtomicStart < atom_start && parsedAtoms[preceding_atom].AtomicLength > atom_length && parsedAtoms[preceding_atom].AtomicStart + parsedAtoms[preceding_atom].AtomicLength >= atom_start + atom_length && parsedAtoms[preceding_atom].AtomicContainerState <= DUAL_STATE_ATOM) { memcpy(container, parsedAtoms[preceding_atom].AtomicName, 5); break; } else { preceding_atom--; } if (preceding_atom == 0) { strcpy(container, "FILE_LEVEL"); } } } /*---------------------- APar_ScanAtoms path - the complete path to the originating file to be tested deepscan_REQ - controls whether we go into 'stsd' or just a superficial scan if the file has not yet been scanned (this gets called by nearly every cli option), then open the file and start scanning. Read in the first 12 bytes and see if bytes 4-8 are 'ftyp' as any modern MPEG-4 file will have 'ftyp' first. Accommodations are also in place for the jpeg2000 signature, but the sig. must be followed by 'ftyp' and have an 'mjp2' or 'mj2s' brand. If it does, start scanning the rest of the file. An MPEG-4 file is logically organized into discrete hierarchies called "atoms" or "boxes". Each atom is at minimum 8 bytes long. Bytes 1-4 make an unsigned 32-bit integer that denotes how long this atom is (ie: 8 would mean this atom is 8 bytes long). The next 4 bytes (bytes 5-8) make the atom name. If the atom presents longer than 8 bytes, then that supplemental data would be what the atom carries. Atoms are broadly separated into 2 categories: parents & children (or container & leaf). Typically, a parent can hold other atoms, but not data; a child can hold data but not other atoms. This 'rule' is broken sometimes (the atoms listed as DUAL_STATE_ATOM), but largely holds. Each atom is read in as 8 bytes. The atom name is extracted, and using the last known container (either FILE_LEVEL or an actual atom name), the new atom's hierarchy is found based on its length & position. Using its containing atom, the KnownAtoms table is searched to locate the properties of that atom (parent/ child, versioned/simple), and jumping around in the file is based off that known atom's type. Atoms that fall into a hybrid category (DUAL_STATE_ATOMs) are explicitly handled. If an atom is known to be versioned, the version-and- flags attribute is read. If an atom is listed as having a language attribute, it is read to support multiple languages (as most 3GP assets do). ----------------------*/ void APar_ScanAtoms(const char *path, bool deepscan_REQ) { if (!parsedfile) { file_size = findFileSize(path); FILE *file = APar_OpenFile(path, "rb"); if (file != NULL) { char *data = (char *)calloc(1, 13); char *container = (char *)calloc(1, 20); memcpy(container, "FILE_LEVEL", 10); bool corrupted_data_atom = false; bool jpeg2000signature = false; uuid_vitals uuid_info = {0}; uuid_info.binary_uuid = (char *)malloc( sizeof(char) * 16 + 1); // this will hold any potential 16byte uuids uuid_info.uuid_AP_atom_name = (char *)malloc( sizeof(char) * 5); // this will hold any atom name that is written // after the uuid written by AP if (data == NULL) return; uint64_t dataSize = 0; uint64_t jump = 0; APar_readX(data, file, 0, 12); char *atom = data + 4; if (memcmp(data, "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A ", 12) == 0) { jpeg2000signature = true; } if (memcmp(atom, "ftyp", 4) == 0 || jpeg2000signature) { dataSize = UInt32FromBigEndian(data); jump = dataSize; APar_AtomizeFileInfo(0, jump, 0, atom, generalAtomicLevel, CHILD_ATOM, SIMPLE_ATOM, 0, 0, &uuid_info); if (!jpeg2000signature) { APar_IdentifyBrand(data + 8); APar_TestCompatibleBrand(file, 0, dataSize); } fseeko(file, jump, SEEK_SET); while (jump < file_size) { uuid_info.uuid_form = UUID_DEPRECATED_FORM; // start with the assumption that any found // atom is in the depracted uuid form APar_readX_noseek(data, file, 8); char *atom = data + 4; dataSize = UInt32FromBigEndian(data); if (jpeg2000signature) { if (memcmp(atom, "ftyp", 4) == 0) { APar_readX_noseek(twenty_byte_buffer, file, 4); APar_IdentifyBrand(twenty_byte_buffer); } else { exit(0); // the atom right after the jpeg2000/mjpeg2000 signature // is *supposed* to be 'ftyp' } jpeg2000signature = false; } if (dataSize > file_size - jump) { dataSize = file_size - jump; } if (dataSize == 0 && (atom[0] == 0 && atom[1] == 0 && atom[2] == 0 && atom[3] == 0)) { gapless_void_padding = file_size - jump; // Apple has decided to add around 2k of NULL // space outside of any atom structure // starting with iTunes 7.0.0 break; // its possible this is part of gapless playback - but then // why would it come after the 'free' at the end of a file // like gpac writes? } // after actual tested its elimination, it doesn't seem to be // required for gapless playback // diagnose damage to 'cprt' by libmp4v2 in 1.4.1 & 1.5.0.1 // typically, the length of this atom (dataSize) will exceeed it // parent (which is reported as 17) true length ot this data will be 9 // - impossible for iTunes-style 'data' atom. if (memcmp(atom, "data", 4) == 0 && parsedAtoms[atom_number - 1].AtomicContainerState == PARENT_ATOM) { if (dataSize > parsedAtoms[atom_number - 1].AtomicLength) { dataSize = parsedAtoms[atom_number - 1].AtomicLength - 8; // force its length to its true length fprintf(stdout, "AtomicParsley warning: the 'data' child of the '%s' " "atom seems to be corrupted.\n", parsedAtoms[atom_number - 1].AtomicName); corrupted_data_atom = true; } } // end diagnosis; APar_Manually_Determine_Parent will still determine // it to be a versioned atom (it tests by names), but at file write // out, it will write with a length of 9 bytes APar_Manually_Determine_Parent(jump, dataSize, container); int filtered_known_atom = APar_MatchToKnownAtom(atom, container, true, NULL); uint32_t atom_verflags = 0; uint16_t atom_language = 0; if (memcmp(atom, "uuid", 4) == 0) { memset(uuid_info.binary_uuid, 0, 17); APar_readX(uuid_info.binary_uuid, file, jump + 8, 16); if (UInt32FromBigEndian(uuid_info.binary_uuid + 8) == 0) { // the deperacted uuid form memcpy(atom, uuid_info.binary_uuid, 4); atom_verflags = APar_read32(uuid_info.binary_uuid, file, jump + 12); if (atom_verflags > AtomFlags_Data_UInt) { atom_verflags = 0; } } else { uint8_t uuid_version = APar_extract_uuid_version(NULL, uuid_info.binary_uuid); APar_endian_uuid_bin_str_conversion(uuid_info.binary_uuid); if (uuid_version == 5) { uuid_info.uuid_form = UUID_SHA1_NAMESPACE; // read in what AP would set the atom name to. The new uuid form // is: // 4bytes atom length, 4 bytes 'uuid', 16bytes uuidv5, 4bytes // name of uuid in AP namespace, 4bytes versioning, 4bytes NULL, // Xbytes data APar_readX(uuid_info.uuid_AP_atom_name, file, jump + 24, 4); char uuid_of_foundname_in_AP_namesapce[20]; APar_generate_uuid_from_atomname( uuid_info.uuid_AP_atom_name, uuid_of_foundname_in_AP_namesapce); if (memcmp(uuid_info.binary_uuid, uuid_of_foundname_in_AP_namesapce, 16) == 0) { uuid_info.uuid_form = UUID_AP_SHA1_NAMESPACE; // our own uuid ver5 atoms in the // AtomicParsley.sf.net namespace atom_verflags = APar_read32(twenty_byte_buffer, file, jump + 28); } } else { uuid_info.uuid_form = UUID_OTHER; } } } if (KnownAtoms[filtered_known_atom].box_type == VERSIONED_ATOM && !corrupted_data_atom) { atom_verflags = APar_read32(twenty_byte_buffer, file, jump + 8); } if (KnownAtoms[filtered_known_atom].box_type == PACKED_LANG_ATOM) { atom_verflags = APar_read32(twenty_byte_buffer, file, jump + 8); // the problem with storing the language is that the uint16_t 2 // bytes that carry the actual language are in different places on // different atoms some atoms have it right after the atom // version/flags; some like rating/classification have it 8 bytes // later; yrrc doesn't have it at all char bitpacked_lang[4]; memset(bitpacked_lang, 0, 4); uint32_t userdata_box = UInt32FromBigEndian(atom); switch (userdata_box) { case 0x7469746C: //'titl' case 0x64736370: //'dscp' case 0x63707274: //'cprt' case 0x70657266: //'perf' case 0x61757468: //'auth' case 0x676E7265: //'gnre' case 0x616C626D: //'albm' case 0x6B797764: //'kywd' case 0x6C6F6369: //'loci' case 0x49443332: //'ID32' ; technically not a 'user data box', but // this only extracts the packed language (which // ID32 does have) { atom_language = APar_read16(bitpacked_lang, file, jump + 12); break; } case 0x636C7366: //'clsf' { atom_language = APar_read16(bitpacked_lang, file, jump + 18); break; } case 0x72746E67: //'rtng' { atom_language = APar_read16(bitpacked_lang, file, jump + 20); break; } // case 0x79727263 : //'yrrc' is the only 3gp tag that doesn't // support multiple languages; won't even get here because != // PACKED_LANG_ATOM default: { break; // which means that any new/unknown packed language atoms // will have their language of 0; AP will only support 1 of // this atom name then } } } // mdat.length=1; and ONLY supported for mdat atoms - no idea if the // spec says "only mdat", but that's what I'm doing for now if ((strncmp(atom, "mdat", 4) == 0) && (generalAtomicLevel == 1) && (dataSize == 1)) { uint64_t extended_dataSize = APar_read64(twenty_byte_buffer, file, jump + 8); APar_AtomizeFileInfo( jump, 1, extended_dataSize, atom, generalAtomicLevel, KnownAtoms[filtered_known_atom].container_state, KnownAtoms[filtered_known_atom].box_type, atom_verflags, atom_language, &uuid_info); } else { APar_AtomizeFileInfo( jump, dataSize, 0, atom, generalAtomicLevel, KnownAtoms[filtered_known_atom].container_state, corrupted_data_atom ? SIMPLE_ATOM : KnownAtoms[filtered_known_atom].box_type, atom_verflags, atom_language, &uuid_info); } corrupted_data_atom = false; // read in the name of an iTunes-style internal reverseDNS directly // into parsedAtoms if (memcmp(atom, "mean", 4) == 0 && memcmp(parsedAtoms[atom_number - 2].AtomicName, "----", 4) == 0) { parsedAtoms[atom_number - 1].ReverseDNSdomain = (char *)calloc(1, sizeof(char) * dataSize); // jump + 12 because 'name' atom is the 2nd child APar_readX(parsedAtoms[atom_number - 1].ReverseDNSdomain, file, jump + 12, dataSize - 12); } if (memcmp(atom, "name", 4) == 0 && memcmp(parsedAtoms[atom_number - 2].AtomicName, "mean", 4) == 0 && memcmp(parsedAtoms[atom_number - 3].AtomicName, "----", 4) == 0) { parsedAtoms[atom_number - 1].ReverseDNSname = (char *)calloc(1, sizeof(char) * dataSize); // jump + 12 because 'name' atom is the 2nd child APar_readX(parsedAtoms[atom_number - 1].ReverseDNSname, file, jump + 12, dataSize - 12); } if (dataSize == 0) { // length = 0 means it reaches to EOF break; } switch (KnownAtoms[filtered_known_atom].container_state) { case PARENT_ATOM: { jump += 8; break; } case CHILD_ATOM: { if (memcmp(atom, "hdlr", 4) == 0) { APar_readX(twenty_byte_buffer, file, jump + 16, 4); parsedAtoms[atom_number - 1].ancillary_data = UInt32FromBigEndian(twenty_byte_buffer); } if ((generalAtomicLevel == 1) && (dataSize == 1)) { // mdat.length =1 64-bit length that is more of a cludge. jump += parsedAtoms[atom_number - 1].AtomicLengthExtended; } else { jump += dataSize; } break; } case DUAL_STATE_ATOM: { if (memcmp(atom, "meta", 4) == 0) { jump += 12; } else if (memcmp(atom, "dref", 4) == 0) { jump += 16; } else if (memcmp(atom, "iinf", 4) == 0) { jump += 14; } else if (memcmp(atom, "stsd", 4) == 0) { if (deepscan_REQ) { // for a tree ONLY, we go all the way, parsing everything; for // any other option, we leave this atom as a monolithic entity jump += 16; } else { APar_Extract_stsd_codec( file, jump + 16); // just get the codec used for this track jump += dataSize; } } else if (memcmp(atom, "schi", 4) == 0) { if (memcmp(container, "sinf", 4) == 0) { // seems for iTMS drm files, schi is a simple parent // atom, and 'user' comes right after it jump += 8; } else { jump += dataSize; // no idea what it would be under srpp, so // just skip over it } } else if (memcmp(container, "stsd", 4) == 0) { // each one is different, so list its size manually // the beauty of this is that even if there is an error here or a // new codec shows up, it only affects SHOWING the tree. Getting a // tree for display ONLY purposes is different from when setting a // tag - display ONLY goes into stsd; tagging makes 'stsd' // monolithic. so setting metadata on unknown or improperly // enumerated codecs (which might have different lengths) don't // affect tagging. uint32_t named_atom = UInt32FromBigEndian(atom); switch (named_atom) { case 0x6D703473: { // mp4s jump += 16; break; } case 0x73727470: // srtp case 0x72747020: { //'rtp ' jump += 24; break; } case 0x616C6163: // alac case 0x6D703461: // mp4a case 0x73616D72: // samr case 0x73617762: // sawb case 0x73617770: // sawp case 0x73657663: // sevc case 0x73716370: // sqcp case 0x73736D76: // ssmv case 0x64726D73: { // drms jump += 36; break; } case 0x74783367: { // tx3g jump += 46; break; } case 0x6D6A7032: // mjp2 case 0x6D703476: // mp4v case 0x61766331: // avc1 case 0x6A706567: // jpeg case 0x73323633: // s263 case 0x64726D69: { // drmi jump += 86; break; } default: { // anything else that isn't covered here will just jump // past any child atoms (avcp, text, enc*) jump += dataSize; } } } break; } case UNKNOWN_ATOM_TYPE: { jump += dataSize; break; } } // end swtich generalAtomicLevel = APar_GetCurrentAtomDepth(jump, dataSize); if ((jump > 8 ? jump : 8) >= file_size) { // prevents jumping past EOF // for the smallest of atoms break; } fseeko(file, jump, SEEK_SET); } } else { fprintf(stderr, "\nAtomicParsley error: bad mpeg4 file (ftyp atom " "missing or alignment error).\n\n"); data = NULL; exit(1); // return; } APar_SampleTableIterator(file); free(data); data = NULL; free(container); container = NULL; free(uuid_info.binary_uuid); free(uuid_info.uuid_AP_atom_name); fclose(file); } if (brand == 0x69736F6D) { //'isom' test for amc files & its (?always present?) uuid // 0x63706764A88C11D48197009027087703 char EZ_movie_uuid[100]; memset(EZ_movie_uuid, 0, sizeof(EZ_movie_uuid)); memcpy(EZ_movie_uuid, "uuid=" "\x63\x70\x67\x64\xA8\x8C\x11\xD4\x81\x97\x00\x90\x27\x08\x77\x03", 21); // this is in an endian form, so it needs to be converted APar_endian_uuid_bin_str_conversion(EZ_movie_uuid + 5); if (APar_FindAtom(EZ_movie_uuid, false, EXTENDED_ATOM, 0, true) != NULL) { metadata_style = UNDEFINED_STYLE; } } parsedfile = true; } if (!deep_atom_scan && !parsedfile && APar_FindAtom("moov", false, SIMPLE_ATOM, 0) == NULL) { fprintf(stderr, "\nAtomicParsley error: bad mpeg4 file (no 'moov' atom).\n\n"); exit(1); } return; } /////////////////////////////////////////////////////////////////////////////////////// // mod time functions // /////////////////////////////////////////////////////////////////////////////////////// void APar_FlagMovieHeader() { if (movie_header_atom == NULL) movie_header_atom = APar_FindAtom("moov.mvhd", false, VERSIONED_ATOM, 0); if (movie_header_atom == NULL) return; if (movie_header_atom != NULL) movie_header_atom->ancillary_data = 0x666C6167; return; } void APar_FlagTrackHeader(AtomicInfo *thisAtom) { AtomicInfo *trak_atom = NULL; AtomicInfo *track_header_atom = NULL; short current_atom_idx = thisAtom->AtomicNumber; short current_level = thisAtom->AtomicLevel; if (thisAtom->AtomicLevel >= 3) { while (true) { short parent_atom = APar_FindParentAtom(current_atom_idx, current_level); current_atom_idx = parent_atom; current_level = parsedAtoms[parent_atom].AtomicLevel; if (current_level == 2) { if (memcmp(parsedAtoms[parent_atom].AtomicName, "trak", 4) == 0) { trak_atom = &parsedAtoms[parent_atom]; } break; } else if (current_level == 1) { break; } } if (trak_atom != NULL) { track_header_atom = APar_FindChildAtom(trak_atom->AtomicNumber, "tkhd"); if (track_header_atom != NULL) { track_header_atom->ancillary_data = 0x666C6167; } } } APar_FlagMovieHeader(); return; } /////////////////////////////////////////////////////////////////////////////////////// // Atom Removal Functions // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_EliminateAtom this_atom_number - the index into parsedAtoms[] of the atom to be erased resume_atom_number - the point in parsedAtoms[] where the tree will be picked up This manually removes the atoms from being used. The atom is still in parsedAtoms[] at the same location it was previously, but because parsedAtoms is used as a linked list & followed by NextAtomNumber, effectively, this atom (and atoms leading to resume_atom_number) are no longer considered part of the tree. ----------------------*/ void APar_EliminateAtom(short this_atom_number, int resume_atom_number) { if (this_atom_number > 0 && this_atom_number < atom_number && resume_atom_number >= 0 && resume_atom_number < atom_number) { APar_FlagTrackHeader(&parsedAtoms[this_atom_number]); short preceding_atom_pos = APar_FindPrecedingAtom(this_atom_number); if (APar_Eval_ChunkOffsetImpact(this_atom_number)) { removed_bytes_tally += parsedAtoms[this_atom_number] .AtomicLength; // used in validation routine } parsedAtoms[preceding_atom_pos].NextAtomNumber = resume_atom_number; memset(parsedAtoms[this_atom_number].AtomicName, 0, 4); // blank out the name of the parent atom name parsedAtoms[this_atom_number].AtomicNumber = -1; parsedAtoms[this_atom_number].NextAtomNumber = -1; } return; } /*---------------------- APar_RemoveAtom atom_path - the "peri.od_d.elim.inat.ed__.atom.path" string that represents the target atom atom_type - the type of atom to be eliminated (packed language, extended...) of the target atom UD_lang - the language code for a packed language atom (ignored for non-packed language atoms) rDNS_domain - the reverse DNS domain (com.foo.thing) of the atom (ignored for non reverse DNS '----' atoms) APar_RemoveAtom tries to find the atom in the string. If it exists, then depending on its atom_type, it or its last child will get passed along for elimination. TODO: the last child part could use some more intelligence at some point; its relatively hardcoded. ----------------------*/ void APar_RemoveAtom(const char *atom_path, uint8_t atom_type, uint16_t UD_lang, const char *rDNS_domain) { AtomicInfo *desiredAtom = APar_FindAtom(atom_path, false, atom_type, UD_lang, (atom_type == EXTENDED_ATOM ? true : false), rDNS_domain); if (desiredAtom == NULL) return; // the atom didn't exist or wasn't found if (desiredAtom->AtomicNumber == 0) return; // we got the default atom, ftyp - and since that can't be removed, // it must not exist (or it was missed) modified_atoms = true; if (atom_type != EXTENDED_ATOM) { if (atom_type == PACKED_LANG_ATOM || desiredAtom->AtomicClassification == UNKNOWN_ATOM) { APar_EliminateAtom(desiredAtom->AtomicNumber, desiredAtom->NextAtomNumber); // reverseDNS atom } else if (desiredAtom->ReverseDNSname != NULL) { short parent_atom = APar_FindParentAtom(desiredAtom->AtomicNumber, desiredAtom->AtomicLevel); short last_elim_atom = APar_FindLastChild_of_ParentAtom(parent_atom); APar_EliminateAtom(parent_atom, parsedAtoms[last_elim_atom].NextAtomNumber); } else if (memcmp(desiredAtom->AtomicName, "data", 4) == 0 && desiredAtom->AtomicLevel == 6) { short parent_atom = APar_FindParentAtom(desiredAtom->AtomicNumber, desiredAtom->AtomicLevel); short last_elim_atom = APar_FindLastChild_of_ParentAtom(parent_atom); APar_EliminateAtom(parent_atom, parsedAtoms[last_elim_atom].NextAtomNumber); } else if (desiredAtom->AtomicContainerState <= DUAL_STATE_ATOM) { short last_elim_atom = APar_FindLastChild_of_ParentAtom(desiredAtom->AtomicNumber); APar_EliminateAtom(desiredAtom->AtomicNumber, parsedAtoms[last_elim_atom].NextAtomNumber); } else if (UD_lang == 1) { // yrrc APar_EliminateAtom(desiredAtom->AtomicNumber, desiredAtom->NextAtomNumber); } else { short last_elim_atom = APar_FindLastChild_of_ParentAtom(desiredAtom->AtomicNumber); APar_EliminateAtom(desiredAtom->AtomicNumber, last_elim_atom); } // this will only work for AtomicParsley created uuid atoms that don't have // children, but since all uuid atoms are treaded as non-parent atoms... no // problems } else if (atom_type == EXTENDED_ATOM) { APar_EliminateAtom(desiredAtom->AtomicNumber, desiredAtom->NextAtomNumber); } return; } /*---------------------- APar_freefree purge_level - an integer ranging from -1 to some positive number of the level a 'free' atom must be on for it to be erased. Some tagging utilities (things based on libmp4v2 & faac irrespective of which tagging system used) have a dirty little secret. They way the work is to copy the 'moov' atom - in its entirety - to the end of the file, and make the changes there. The original 'moov' file is nulled out, but the file only increases in size. Even if you eliminate the tag, the file grows. Only when the 'moov' atom is last do these taggers work efficiently - and they are blazingly fast, no doubt about that - but they are incredibly wasteful. It is possible to switch between using AP and mp4tags and build a file with dozens of megabytes wasted just be changing a single letter. This function can be used to iterate through the atoms in the file, and selectively eliminate 'free' atoms. Pass a -1 and every last 'free' atom that exists will be eliminated (but another will appear later as padding - to completely eliminate any resulting 'free' atoms, the environmental variable "AP_PADDING" needs to be set with MID_PAD to 0). Pass a 0, and all 'free' atoms preceding 'moov' or after 'mdat' (including gpac's pesky "Place Your Ad Here" free-at-the-end) will be removed. A value of >= 1 will eliminate 'free' atoms between those levels and level 1 (or file level). ----------------------*/ void APar_freefree(int purge_level) { modified_atoms = true; short eval_atom = 0; short prev_atom = 0; short moov_atom = 0; // a moov atom has yet to be seen short mdat_atom = 0; // any ol' mdat if (purge_level == -1) { complete_free_space_erasure = true; // prevent any in situ dynamic updating when trying to remove all // free atoms. Also triggers a more efficient means of forcing // padding } while (true) { prev_atom = eval_atom; eval_atom = parsedAtoms[eval_atom].NextAtomNumber; if (eval_atom == 0) { // we've hit the last atom break; } if (memcmp(parsedAtoms[eval_atom].AtomicName, "free", 4) == 0 || memcmp(parsedAtoms[eval_atom].AtomicName, "skip", 4) == 0) { // fprintf(stdout, "i am of size %" PRIu64 " purge level %i (%u) -> %i\n", // parsedAtoms[eval_atom].AtomicLength, purge_level, // parsedAtoms[eval_atom].AtomicLevel, eval_atom); if (purge_level == -1 || purge_level >= parsedAtoms[eval_atom].AtomicLevel || (purge_level == 0 && parsedAtoms[eval_atom].AtomicLevel == 1 && (moov_atom == 0 || mdat_atom != 0))) { short prev_atom = APar_FindPrecedingAtom(eval_atom); if (parsedAtoms[eval_atom].NextAtomNumber == 0) { // we've hit the last atom APar_EliminateAtom(eval_atom, parsedAtoms[eval_atom].NextAtomNumber); parsedAtoms[prev_atom].NextAtomNumber = 0; } else { APar_EliminateAtom(eval_atom, parsedAtoms[eval_atom].NextAtomNumber); } eval_atom = prev_atom; // go back to the previous atom and continue the search } } if (memcmp(parsedAtoms[eval_atom].AtomicName, "moov", 4) == 0) { moov_atom = eval_atom; } if (memcmp(parsedAtoms[eval_atom].AtomicName, "mdat", 4) == 0) { mdat_atom = eval_atom; } } return; } /////////////////////////////////////////////////////////////////////////////////////// // Atom Moving Functions // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_MoveAtom this_atom_number - the atom that will follow the new_position atom new_position - the atom that the atoms at that level will precede, followed by this_atom_number (including its hierarchy of child atoms) first find which atoms lead to both atoms (precedingAtom flows to this_atom_number, lastStationaryAtom flows to new_position). Depending on whether there are children, find the last atom in the atoms that will be moving. Shunt as required; reordering occurs by following NextAtomNumber (linked list) ----------------------*/ void APar_MoveAtom(short this_atom_number, short new_position) { short precedingAtom = 0; short lastStationaryAtom = 0; short iter = 0; // look for the preceding atom (either directly before of the same level, or // moov's last nth level child while (parsedAtoms[iter].NextAtomNumber != 0) { if (parsedAtoms[iter].NextAtomNumber == this_atom_number) { precedingAtom = iter; break; } else { if (parsedAtoms[iter].NextAtomNumber == 0) { // we found the last atom (which we end our search on) break; } } iter = parsedAtoms[iter].NextAtomNumber; } iter = 0; // search where to insert our new atom while (parsedAtoms[iter].NextAtomNumber != 0) { if (parsedAtoms[iter].NextAtomNumber == new_position) { lastStationaryAtom = iter; break; } iter = parsedAtoms[iter].NextAtomNumber; if (parsedAtoms[iter].NextAtomNumber == 0) { // we found the last atom lastStationaryAtom = iter; break; } } // fprintf(stdout, "%s preceded by %s, last would be %s\n", // parsedAtoms[this_atom_number].AtomicName, // parsedAtoms[precedingAtom].AtomicName, // parsedAtoms[lastStationaryAtom].AtomicName); if (parsedAtoms[this_atom_number].AtomicContainerState <= DUAL_STATE_ATOM) { if (parsedAtoms[new_position].AtomicContainerState <= DUAL_STATE_ATOM) { short last_SwapChild = APar_FindLastChild_of_ParentAtom(this_atom_number); short last_WiredChild = APar_FindLastChild_of_ParentAtom(new_position); // fprintf(stdout, "moving %s, last child atom %s\n", // parsedAtoms[this_atom_number].AtomicName, // parsedAtoms[last_SwapChild].AtomicName); fprintf(stdout, "wired %s, // last child atom %s\n", parsedAtoms[new_position].AtomicName, // parsedAtoms[last_WiredChild].AtomicName); fprintf(stdout, "stationary // atom %s , preceding atom %s\n", // parsedAtoms[lastStationaryAtom].AtomicName, // parsedAtoms[precedingAtom].AtomicName); short swap_resume = parsedAtoms[last_SwapChild].NextAtomNumber; short wired_resume = parsedAtoms[last_WiredChild].NextAtomNumber; parsedAtoms[precedingAtom].NextAtomNumber = swap_resume; // shunt the main tree (over the [this_atom_number] atom // to be move) to other tween atoms, parsedAtoms[lastStationaryAtom].NextAtomNumber = new_position; // pick up with the 2nd to last hierarchy parsedAtoms[last_WiredChild].NextAtomNumber = this_atom_number; // and route the 2nd to last hierarchy to wrap // around to the this_atom_number atom parsedAtoms[last_SwapChild].NextAtomNumber = wired_resume; // and continue with whatever was after the // [new_position] atom } else { short last_child = APar_FindLastChild_of_ParentAtom(this_atom_number); parsedAtoms[lastStationaryAtom].NextAtomNumber = this_atom_number; parsedAtoms[precedingAtom].NextAtomNumber = parsedAtoms[last_child].NextAtomNumber; parsedAtoms[last_child].NextAtomNumber = new_position; } } else { parsedAtoms[lastStationaryAtom].NextAtomNumber = this_atom_number; parsedAtoms[precedingAtom].NextAtomNumber = parsedAtoms[this_atom_number].NextAtomNumber; parsedAtoms[this_atom_number].NextAtomNumber = new_position; } return; } /////////////////////////////////////////////////////////////////////////////////////// // Atom Creation Functions // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_InterjectNewAtom atom_name - the 4 character name of the atom cntr_state - the type of container it will be (child, parent, dual state) atom_class - the atom type it will be (simple, versioned, uuid, versioned with packed language) atom_length - the forced length of this atom (undefined beyond intrinsic length of the container type except for 'free' atoms) atom_verflags - the 1 byte atom version & 3 bytes atom flags for the atom if versioned packed_lang - the 2 byte packed language for the atom if versioned with packed language type atom_level - the level of this atom (1 denotes file level, anything else denotes a child of the last preceding parent or dual state atom) preceding_atom - the atom that precedes this newly created interjected atom Creates a single new atom (carrying NULLed data) inserted after preceding_atom ----------------------*/ short APar_InterjectNewAtom(const char *atom_name, uint8_t cntr_state, uint8_t atom_class, uint64_t atom_length, uint32_t atom_verflags, uint16_t packed_lang, uint8_t atom_level, short preceding_atom) { if (deep_atom_scan && !modified_atoms) { return 0; } if (atom_number >= MAX_ATOMS) { fprintf(stderr, "too many atoms\n"); abort(); } AtomicInfo *new_atom = &parsedAtoms[atom_number]; new_atom->AtomicNumber = atom_number; new_atom->AtomicName = (char *)malloc(sizeof(char) * 6); memset(new_atom->AtomicName, 0, sizeof(char) * 6); memcpy(new_atom->AtomicName, atom_name, 4); new_atom->AtomicContainerState = cntr_state; new_atom->AtomicClassification = atom_class; new_atom->AtomicVerFlags = atom_verflags; new_atom->AtomicLevel = atom_level; new_atom->AtomicLength = atom_length; new_atom->AtomicLanguage = packed_lang; new_atom->AtomicData = (char *)calloc( 1, sizeof(char) * MAXDATA_PAYLOAD + 1); // puts a hard limit on the length of // strings (the spec doesn't) new_atom->ID32_TagInfo = NULL; new_atom->NextAtomNumber = parsedAtoms[preceding_atom].NextAtomNumber; parsedAtoms[preceding_atom].NextAtomNumber = atom_number; atom_number++; return new_atom->AtomicNumber; } /*---------------------- APar_CreateSparseAtom surrogate_atom - an skeletal template of the atom to be created; currently name, level, lang, (if uuid/extended: container & class) are copied; other stats should be filled in routines that called for their creation and know things like flags & if is to carry an data (this doesn't malloc) parent_atom - the stats for the parent atom (used to match through the KnownAtoms array and get things like container & class (for most atoms) preceding_atom - the new atom will follow this atom Create a single new atom (not carrying any data) copied from a template to follow preceding_atom ----------------------*/ AtomicInfo *APar_CreateSparseAtom(AtomicInfo *surrogate_atom, AtomicInfo *parent_atom, short preceding_atom) { if (atom_number >= MAX_ATOMS) { fprintf(stderr, "too many atoms\n"); abort(); } AtomicInfo *new_atom = &parsedAtoms[atom_number]; new_atom->AtomicNumber = atom_number; new_atom->AtomicStart = 0; int known_atom = 0; new_atom->AtomicName = (char *)malloc(sizeof(char) * 20); memset(new_atom->AtomicName, 0, sizeof(char) * 20); size_t copy_bytes_len = (surrogate_atom->AtomicClassification == EXTENDED_ATOM ? 16 : 4); memcpy(new_atom->AtomicName, surrogate_atom->AtomicName, copy_bytes_len); new_atom->AtomicLevel = surrogate_atom->AtomicLevel; new_atom->AtomicLanguage = surrogate_atom->AtomicLanguage; // this is almost assuredly wrong for everything except a simple parent atom & // needs to be handled properly afterwards; Note the use of 'Sparse' new_atom->AtomicVerFlags = 0; new_atom->AtomicLength = 8; new_atom->NextAtomNumber = parsedAtoms[preceding_atom].NextAtomNumber; parsedAtoms[preceding_atom].NextAtomNumber = atom_number; // if 'uuid' atom, copy the info directly, otherwise use KnownAtoms to get the // info if (surrogate_atom->AtomicClassification == EXTENDED_ATOM) { new_atom->AtomicContainerState = CHILD_ATOM; new_atom->AtomicClassification = surrogate_atom->AtomicClassification; new_atom->uuid_style = UUID_AP_SHA1_NAMESPACE; } else { // determine the type of atom from our array of KnownAtoms; this is a worst // case scenario; it should be handled properly afterwards make sure the // level & the atom gets integrated into NextAtomNumber before // APar_MatchToKnownAtom because getting the fullpath will rely on that known_atom = APar_MatchToKnownAtom( surrogate_atom->AtomicName, (parent_atom == NULL ? "FILE_LEVEL" : parent_atom->AtomicName), false, NULL); new_atom->AtomicContainerState = KnownAtoms[known_atom].container_state; new_atom->AtomicClassification = KnownAtoms[known_atom].box_type; } new_atom->ID32_TagInfo = NULL; atom_number++; return new_atom; } /*---------------------- APar_Unified_atom_Put target_atom - pointer to the structure describing the atom we are setting unicode_data - a pointer to a string (possibly utf8 already); may go onto conversion to utf16 prior to the put text_tag_style - flag to denote that unicode_data is to become utf-16, or stay the flavors of utf8 (iTunes style, 3gp style...) ancillary_data - a (possibly cast) 32-bit number of any type of supplemental data to be set anc_bit_width - controls the number of bytes to set for ancillary data [0 to skip, 8 (1byte) - 32 (4 bytes)] take any variety of data & tack it onto the malloced AtomicData at the next available spot (determined by its length) priority is given to the numerical ancillary_data so that language can be set prior to setting whatever unicode data. Finally, advance the length of the atom so that we can tack onto the end repeated times (up to the max malloced amount - which isn't checked [blush]) if unicode_data is NULL itself, then only ancillary_data will be set - which is endian safe cuz o' bitshifting (or set 1 byte at a time) works on iTunes-style & 3GP asset style but NOT binary safe (use APar_atom_Binary_Put) TODO: work past the max malloced amount onto a new larger array ----------------------*/ void APar_Unified_atom_Put(AtomicInfo *target_atom, const char *unicode_data, uint8_t text_tag_style, uint64_t ancillary_data, uint8_t anc_bit_width) { uint64_t atom_data_pos = 0; if (target_atom == NULL) { return; } if (target_atom->AtomicClassification == EXTENDED_ATOM) { if (target_atom->uuid_style == UUID_SHA1_NAMESPACE) atom_data_pos = target_atom->AtomicLength - 32; else if (target_atom->uuid_style == UUID_OTHER) atom_data_pos = target_atom->AtomicLength - 24; } else { atom_data_pos = target_atom->AtomicLength - 12; } switch (anc_bit_width) { case 0: { // aye, 'twas a false alarm; arg (I'm a pirate), we just wanted to // set a text string break; } case 8: { // compilation, podcast flag, advisory target_atom->AtomicData[atom_data_pos] = (uint8_t)ancillary_data; target_atom->AtomicLength++; atom_data_pos++; break; } case 16: { // lang & its ilk target_atom->AtomicData[atom_data_pos] = (ancillary_data & 0xff00) >> 8; target_atom->AtomicData[atom_data_pos + 1] = (ancillary_data & 0xff) << 0; target_atom->AtomicLength += 2; atom_data_pos += 2; break; } case 32: { // things like coordinates and.... stuff (ah, the prose) target_atom->AtomicData[atom_data_pos] = (ancillary_data & 0xff000000) >> 24; target_atom->AtomicData[atom_data_pos + 1] = (ancillary_data & 0xff0000) >> 16; target_atom->AtomicData[atom_data_pos + 2] = (ancillary_data & 0xff00) >> 8; target_atom->AtomicData[atom_data_pos + 3] = (ancillary_data & 0xff) << 0; target_atom->AtomicLength += 4; atom_data_pos += 4; break; } default: { break; } } if (unicode_data != NULL) { if (text_tag_style == UTF16_3GP_Style) { uint32_t string_length = strlen(unicode_data) + 1; uint32_t glyphs_req_bytes = mbstowcs(NULL, unicode_data, string_length) * 2; // passing NULL pre-calculates the size of wchar_t needed; unsigned char *utf16_conversion = (unsigned char *)calloc(1, sizeof(unsigned char) * string_length * 2); UTF8ToUTF16BE(utf16_conversion, glyphs_req_bytes, (unsigned char *)unicode_data, string_length); target_atom->AtomicData[atom_data_pos] = (char)(0xFE); // BOM target_atom->AtomicData[atom_data_pos + 1] = (char)(0xFF); // BOM atom_data_pos += 2; // BOM /* copy the string directly onto AtomicData at the address of the start of * AtomicData + the current length in atom_data_pos */ /* in marked contrast to iTunes-style metadata where a string is a single * string, 3gp tags like keyword & classification are more complex */ /* directly putting the text into memory and being able to tack on more * becomes a necessary accommodation */ memcpy(target_atom->AtomicData + atom_data_pos, utf16_conversion, glyphs_req_bytes); target_atom->AtomicLength += glyphs_req_bytes; // double check terminating NULL (don't want to double add them - // blush.... or have them missing - blushing on the.... other side) if (target_atom->AtomicData[atom_data_pos + (glyphs_req_bytes - 1)] + target_atom->AtomicData[atom_data_pos + glyphs_req_bytes] != 0) { target_atom->AtomicLength += 4; //+4 because add 2 bytes for the character we just found + 2bytes // for the req. NULL } free(utf16_conversion); utf16_conversion = NULL; } else if (text_tag_style == UTF8_iTunesStyle_Binary) { // because this will be 'binary' data // (a misnomer for purl & egid), // memcpy 4 bytes into AtomicData, not // at the start of it uint32_t binary_bytes = strlen(unicode_data); memcpy(target_atom->AtomicData + atom_data_pos, unicode_data, binary_bytes + 1); target_atom->AtomicLength += binary_bytes; } else { uint32_t total_bytes = 0; if (text_tag_style == UTF8_3GP_Style) { total_bytes = strlen(unicode_data); total_bytes++; // include the terminating NULL } else if (text_tag_style == UTF8_iTunesStyle_256glyphLimited) { uint32_t raw_bytes = strlen(unicode_data); total_bytes = utf8_length( unicode_data, 256); // counts the number of characters, not bytes if (raw_bytes > total_bytes && total_bytes > 255) { fprintf(stdout, "AtomicParsley warning: %s was trimmed to 255 characters (%u " "characters over)\n", parsedAtoms[APar_FindParentAtom(target_atom->AtomicNumber, target_atom->AtomicLevel)] .AtomicName, utf8_length(unicode_data + total_bytes, 0)); } else { total_bytes = raw_bytes; } } else if (text_tag_style == UTF8_iTunesStyle_Unlimited) { total_bytes = strlen(unicode_data); if (atom_data_pos + total_bytes > MAXDATA_PAYLOAD) { target_atom->AtomicData = (char *)realloc(target_atom->AtomicData, sizeof(char) * (atom_data_pos + total_bytes + 1)); memset(target_atom->AtomicData + atom_data_pos, 0, total_bytes + 1); } } // if we are setting iTunes-style metadata, add 0 to the pointer; for 3gp // user data atoms - add in the (length-default bare atom lenth): account // for language uint16_t (plus any other crap we will set); unicodeWin32 // with wchar_t was converted right after program started, so do a direct // copy memcpy(target_atom->AtomicData + atom_data_pos, unicode_data, total_bytes + 1); target_atom->AtomicLength += total_bytes; } } return; } /*---------------------- APar_atom_Binary_Put target_atom - pointer to the structure describing the atom we are setting binary_data - a pointer to a string of binary data bytecount - number of bytes to copy atomic_data_offset - place binary data some bytes offset from the start of AtomicData Simple placement of binary data (perhaps containing NULLs) onto AtomicData. TODO: if over MAXDATA_PAYLOAD malloc a new char string ----------------------*/ void APar_atom_Binary_Put(AtomicInfo *target_atom, const char *binary_data, uint32_t bytecount, uint64_t atomic_data_offset) { if (target_atom == NULL) return; if (atomic_data_offset + bytecount + target_atom->AtomicLength <= MAXDATA_PAYLOAD) { memcpy( target_atom->AtomicData + atomic_data_offset, binary_data, bytecount); target_atom->AtomicLength += bytecount; } else { fprintf(stdout, "AtomicParsley warning: some data was longer than the " "allotted space and was skipped\n"); } return; } /*---------------------- APar_Verify__udta_meta_hdlr__atom only test if the atom is present for now, it will be created just before writeout time - to insure it only happens once. ----------------------*/ void APar_Verify__udta_meta_hdlr__atom() { bool Create__udta_meta_hdlr__atom = false; if (metadata_style == ITUNES_STYLE && hdlrAtom == NULL) { hdlrAtom = APar_FindAtom("moov.udta.meta.hdlr", false, VERSIONED_ATOM, 0); if (hdlrAtom == NULL) { Create__udta_meta_hdlr__atom = true; } } if (Create__udta_meta_hdlr__atom) { // if Quicktime (Player at the least) is used to create any type of mp4 // file, the entire udta hierarchy is missing. If iTunes doesn't find this // "moov.udta.meta.hdlr" atom (and its data), it refuses to let any // information be changed & the dreaded "Album Artwork Not Modifiable" shows // up. It's because this atom is missing. Oddly, QT Player can see the info, // but this only works for mp4/m4a files. hdlrAtom = APar_FindAtom("moov.udta.meta.hdlr", true, VERSIONED_ATOM, 0); APar_MetaData_atom_QuickInit(hdlrAtom->AtomicNumber, 0, 0); APar_Unified_atom_Put(hdlrAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0x6D646972, 32); //'mdir' APar_Unified_atom_Put(hdlrAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0x6170706C, 32); //'appl' APar_Unified_atom_Put( hdlrAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 32); APar_Unified_atom_Put( hdlrAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 32); APar_Unified_atom_Put( hdlrAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); } return; } /*---------------------- APar_MetaData_atomGenre_Set atomPayload - the desired string value of the genre genre is special in that it gets carried on 2 atoms. A standard genre (as listed in ID3v1GenreList) is represented as a number on a 'gnre' atom any value other than those, and the genre is placed as a string onto a 'gen' atom. Only one or the other can be present. So if atomPayload is a non-NULL value, first try and match the genre into the ID3v1GenreList standard genres. Try to remove the other type of genre atom, then find or create the new genre atom and put the data manually onto the atom. ----------------------*/ void APar_MetaData_atomGenre_Set(const char *atomPayload) { if (metadata_style == ITUNES_STYLE) { const char *standard_genre_atom = "moov.udta.meta.ilst.gnre"; const char *std_genre_data_atom = "moov.udta.meta.ilst.gnre.data"; const char *custom_genre_atom = "moov.udta.meta.ilst.gen"; const char *cstm_genre_data_atom = "moov.udta.meta.ilst.gen.data"; if (strlen(atomPayload) == 0) { APar_RemoveAtom(std_genre_data_atom, VERSIONED_ATOM, 0); // find the atom; don't create if it's "" to remove APar_RemoveAtom(cstm_genre_data_atom, VERSIONED_ATOM, 0); // find the atom; don't create if it's "" to remove } else { uint8_t genre_number = StringGenreToInt(atomPayload); AtomicInfo *genreAtom; APar_Verify__udta_meta_hdlr__atom(); modified_atoms = true; if (genre_number != 0) { // first find if a custom genre atom ("gen") exists; erase the // custom-string genre atom in favor of the standard genre atom AtomicInfo *verboten_genre_atom = APar_FindAtom(custom_genre_atom, false, SIMPLE_ATOM, 0); if (verboten_genre_atom != NULL) { if (strlen(verboten_genre_atom->AtomicName) > 0) { if (strncmp(verboten_genre_atom->AtomicName, "gen", 4) == 0) { APar_RemoveAtom(cstm_genre_data_atom, VERSIONED_ATOM, 0); } } } genreAtom = APar_FindAtom(std_genre_data_atom, true, VERSIONED_ATOM, 0); APar_MetaData_atom_QuickInit( genreAtom->AtomicNumber, AtomFlags_Data_Binary, 0); APar_Unified_atom_Put( genreAtom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 8); APar_Unified_atom_Put( genreAtom, NULL, UTF8_iTunesStyle_256glyphLimited, genre_number, 8); } else { AtomicInfo *verboten_genre_atom = APar_FindAtom(standard_genre_atom, false, SIMPLE_ATOM, 0); if (verboten_genre_atom != NULL) { if (verboten_genre_atom->AtomicNumber > 5 && verboten_genre_atom->AtomicNumber < atom_number) { if (strncmp(verboten_genre_atom->AtomicName, "gnre", 4) == 0) { APar_RemoveAtom(std_genre_data_atom, VERSIONED_ATOM, 0); } } } genreAtom = APar_FindAtom(cstm_genre_data_atom, true, VERSIONED_ATOM, 0); APar_MetaData_atom_QuickInit( genreAtom->AtomicNumber, AtomFlags_Data_Text, 0); APar_Unified_atom_Put( genreAtom, atomPayload, UTF8_iTunesStyle_256glyphLimited, 0, 0); } } APar_FlagMovieHeader(); } // end if (metadata_style == ITUNES_STYLE) return; } /*---------------------- APar_MetaData_atomLyrics_Set lyricsPath - the path that was provided to a (hopefully) existent txt file lyrics will be read from a file because they can contain multiple lines. Lines are stored with old Mac-style line endings (single carriage return). ----------------------*/ void APar_MetaData_atomLyrics_Set(const char *lyricsPath) { if (metadata_style == ITUNES_STYLE) { TestFileExistence(lyricsPath, true); uint64_t file_len = findFileSize(lyricsPath); APar_Verify__udta_meta_hdlr__atom(); modified_atoms = true; AtomicInfo *lyricsData_atom = APar_FindAtom("moov.udta.meta.ilst.lyr.data", true, VERSIONED_ATOM, 0); APar_MetaData_atom_QuickInit( lyricsData_atom->AtomicNumber, AtomFlags_Data_Text, 0, file_len + 1); FILE *lyrics_file = APar_OpenFile(lyricsPath, "rb"); uint64_t remaining = file_len; char *dest = lyricsData_atom->AtomicData + 4; char *sol; while (remaining && (sol = fgets(dest, remaining + 1, lyrics_file))) { size_t len = strlen(sol); // NUL bytes in the file will cause parts of the // text to be skipped // normalize different EOL styles to carriage returns if (sol[len - 1] == '\n') { if (sol[len - 2] == '\r') sol[--len] = '\0'; else sol[len - 1] = '\r'; } remaining -= len; dest += len; } lyricsData_atom->AtomicLength += dest - (lyricsData_atom->AtomicData + 4); fclose(lyrics_file); APar_FlagMovieHeader(); } // end if (metadata_style == ITUNES_STYLE) return; } /*---------------------- APar_MetaData_atomArtwork_Init atom_num - the AtomicNumber of the atom in the parsedAtoms array (probably newly created) artworkPath - the path that was provided on a (hopefully) existant jpg/png file artwork will be inited differently because we need to test a) that the file exists and b) get its size in bytes. This info will be used at the size of the 'data' atom under 'covr' - and the path will be carried on AtomicData until write-out time, when the binary contents of the original will be copied onto the atom. ----------------------*/ void APar_MetaData_atomArtwork_Init(short atom_num, const char *artworkPath) { TestFileExistence(artworkPath, true); uint64_t picture_size = findFileSize(artworkPath); if (picture_size > 0) { APar_MetaData_atom_QuickInit( atom_num, APar_TestArtworkBinaryData(artworkPath), 0, picture_size); FILE *artfile = APar_OpenFile(artworkPath, "rb"); uint32_t bytes_read = APar_ReadFile(parsedAtoms[atom_num].AtomicData + 4, artfile, picture_size); //+4 for the 4 null bytes if (bytes_read > 0) parsedAtoms[atom_num].AtomicLength += bytes_read; fclose(artfile); } return; } /*---------------------- APar_MetaData_atomArtwork_Set artworkPath - the path that was provided on a (hopefully) existant jpg/png file env_PicOptions - picture embedding preferences from a 'export PIC_OPTIONS=foo' setting artwork gets stored under a single 'covr' atom, but with many 'data' atoms - each 'data' atom contains the binary data for each picture. When the 'covr' atom is found, we create a sparse atom at the end of the existing 'data' atoms, and then perform any of the image manipulation features on the image. The path of the file (either original, modified artwork, or both) are returned to use for possible atom creation ----------------------*/ void APar_MetaData_atomArtwork_Set(const char *artworkPath, char *env_PicOptions) { if (metadata_style == ITUNES_STYLE) { const char *artwork_atom = "moov.udta.meta.ilst.covr"; if (strcmp(artworkPath, "REMOVE_ALL") == 0) { APar_RemoveAtom(artwork_atom, SIMPLE_ATOM, 0); } else { APar_Verify__udta_meta_hdlr__atom(); modified_atoms = true; AtomicInfo *desiredAtom = APar_FindAtom(artwork_atom, true, SIMPLE_ATOM, 0); AtomicInfo sample_data_atom = {0}; #if defined(__APPLE__) // used on Darwin adding a 2nd image (the original) short parent_atom = desiredAtom->AtomicNumber; #endif APar_CreateSurrogateAtom( &sample_data_atom, "data", 6, VERSIONED_ATOM, 0, NULL, 0); desiredAtom = APar_CreateSparseAtom( &sample_data_atom, desiredAtom, APar_FindLastChild_of_ParentAtom(desiredAtom->AtomicNumber)); #if defined(__APPLE__) // determine if any picture preferences will impact the picture file in // any way myPicturePrefs = APar_ExtractPicPrefs(env_PicOptions); size_t resized_filepath_len = MAXPATHLEN + 1; char *resized_filepath = (char *)calloc(1, resized_filepath_len); if (ResizeGivenImage(artworkPath, myPicturePrefs, resized_filepath, resized_filepath_len)) { APar_MetaData_atomArtwork_Init(desiredAtom->AtomicNumber, resized_filepath); if (myPicturePrefs.addBOTHpix) { // create another sparse atom to hold the new image data desiredAtom = APar_CreateSparseAtom( &sample_data_atom, desiredAtom, APar_FindLastChild_of_ParentAtom(parent_atom)); APar_MetaData_atomArtwork_Init(desiredAtom->AtomicNumber, artworkPath); if (myPicturePrefs.removeTempPix) remove(resized_filepath); } } else { APar_MetaData_atomArtwork_Init(desiredAtom->AtomicNumber, artworkPath); } free(resized_filepath); resized_filepath = NULL; #else // perhaps some libjpeg based resizing/modification for non-Mac OS X based // platforms APar_MetaData_atomArtwork_Init(desiredAtom->AtomicNumber, artworkPath); #endif } APar_FlagMovieHeader(); } ////end if (metadata_style == ITUNES_STYLE) return; } /*---------------------- APar_sprintf_atompath dest_path - the destination string that will hold the final path target_atom_name - the name of the atom that will be used to fill the %s portion of the tokenized path track_index - the index into the trak[X] that needs to be found; fills the %u portion of the tokenized path udta_container - the area in which 'udta' will be: either movie level (the easiest) or track level (which requires an index into a specific 'trak' atom) dest_len - the amount of malloc'ed bytes for dest_path Fill a destinaiton path with the fully expressed atom path taking track indexes into consideration ----------------------*/ void APar_sprintf_atompath(char *dest_path, const char *target_atom_name, uint8_t track_index, uint8_t udta_container, uint16_t dest_len) { memset(dest_path, 0, dest_len); if (udta_container == MOVIE_LEVEL_ATOM) { memcpy(dest_path, "moov.udta.", 10); memcpy(dest_path + 10, target_atom_name, 4); } else { sprintf(dest_path, "moov.trak[%u].udta.%s", track_index, target_atom_name); } return; } /*---------------------- APar_3GP_Keyword_atom_Format keywords_globbed - the globbed string of keywords ('foo1,foo2,foo_you') keyword_count - count of keywords in the above globbed string set_UTF16_text - whether to encode as utf16 formed_keyword_struct - the char string that will hold the converted keyword struct (manually formatted) 3gp keywords are a little more complicated. Since they will be entered separated by some form of punctuation, they need to be separated out They also will possibly be converted to utf16 - and they NEED to start with the BOM then. Prefacing each keyword is the 8bit length of the string And each keyword needs to be NULL terminated. Technically it would be possible to even have mixed encodings (not supported here). ----------------------*/ uint32_t APar_3GP_Keyword_atom_Format(char *keywords_globbed, uint8_t keyword_count, bool set_UTF16_text, char *&formed_keyword_struct) { uint64_t formed_string_offset = 0; uint64_t string_len = 0; char *a_keyword = strsep(&keywords_globbed, ","); for (uint8_t i = 1; i <= keyword_count; i++) { string_len = strlen(a_keyword); if (set_UTF16_text) { uint32_t glyphs_req_bytes = mbstowcs(NULL, a_keyword, string_len + 1) * 2; // passing NULL pre-calculates the size of wchar_t needed; formed_keyword_struct[formed_string_offset + 1] = (char)(0xFE); // BOM formed_keyword_struct[formed_string_offset + 2] = (char)(0xFF); // BOM formed_string_offset += 3; // BOM + keyword length that has yet to be set int bytes_converted = UTF8ToUTF16BE( (unsigned char *)(formed_keyword_struct + formed_string_offset), glyphs_req_bytes, (unsigned char *)a_keyword, string_len); if (bytes_converted > 1) { formed_keyword_struct[formed_string_offset - 3] = (uint8_t)bytes_converted + 4; // keyword length is NOW set formed_string_offset += bytes_converted + 2; // NULL terminator } } else { uint32_t string_len = strlen(a_keyword); formed_keyword_struct[formed_string_offset] = (uint8_t)string_len + 1; // add the terminating NULL formed_string_offset++; memcpy( formed_keyword_struct + formed_string_offset, a_keyword, string_len); formed_string_offset += (string_len + 1); } a_keyword = strsep(&keywords_globbed, ","); } return formed_string_offset; } /*---------------------- APar_uuid_atom_Init atom_path - the parent hierarchy of the desired atom (with the location of the specific uuid atom supplied as '=%s') uuidName - the name of the atom (possibly provided in a forbidden utf8 - only latin1 aka iso8859 is acceptable) dataType - for now text is only supported; really its atom version/flags as used by iTunes uuidValue - the string that will get embedded onto the atom shellAtom - flag to denote whether the atom may possibly come as utf8 encoded uuid atoms are user-supplied/user-defined atoms that allow for extended tagging support. Because a uuid atom is malleable, and defined by the utility that created it, any information carried by a uuid is arbitrary, and cannot be guaranteed by a non-originating utility. In AtomicParsley uuid atoms, the data is presented much like an iTunes-style atom - except that the information gets carried directly on the uuid atom - no 'data' child atom exists. A uuid atom is a special longer type of traditional atom. As a traditional atom, it name is 'uuid' - and the 4 bytes after that represent its uuid name. Because the data will be directly sitting on the atom, a different means of finding these atoms exists, as well as creating the acutal uuidpath itself. Once created however, placing information on it is very much like any other atom - done via APar_Unified_atom_Put //4bytes atom length, 4 bytes 'uuid', 16bytes uuidv5, 4bytes name of uuid in AP namespace, 4bytes versioning, 4bytes NULL, Xbytes data ----------------------*/ AtomicInfo *APar_uuid_atom_Init(const char *atom_path, const char *uuidName, const uint32_t dataType, const char *uuidValue, bool shellAtom) { AtomicInfo *desiredAtom = NULL; char uuid_path[256]; char uuid_binary_str[20]; char uuid_4char_name[10]; memset(uuid_path, 0, sizeof(uuid_path)); memset(uuid_binary_str, 0, sizeof(uuid_binary_str)); memset(uuid_4char_name, 0, sizeof(uuid_4char_name)); uint16_t path_len = 0; if (shellAtom) { UTF8Toisolat1((unsigned char *)&uuid_4char_name, 4, (unsigned char *)uuidName, strlen(uuidName)); } else { memcpy(uuid_4char_name, uuidName, 4); } APar_generate_uuid_from_atomname(uuid_4char_name, uuid_binary_str); APar_endian_uuid_bin_str_conversion(uuid_binary_str); // this will only append (and knock off) %s (anything) at the end of a string path_len = strlen(atom_path); memcpy(uuid_path, atom_path, path_len - 2); memcpy(uuid_path + (path_len - 2), uuid_binary_str, 16); #if defined(DEBUG_V) fprintf(stdout, "debug: APar_uuid_atom_Init desired atom '%s' converted to uuidv5: ", uuidName); APar_print_uuid((ap_uuid_t *)(uuid_path + (path_len - 2))); #endif if (uuidValue == NULL || strlen(uuidValue) == 0) { APar_RemoveAtom(uuid_path, EXTENDED_ATOM, 0); // find the atom; don't create if it's "" to remove APar_FlagMovieHeader(); return NULL; } else { if (!(dataType == AtomFlags_Data_Text || dataType == AtomFlags_Data_uuid_binary)) { // the only supported types fprintf(stdout, "AP warning: only text or file types are allowed on uuid atom %s " "(%" PRIu32 "-%u); skipping\n", uuidName, dataType, AtomFlags_Data_Text); return NULL; } // uuid atoms won't have 'data' child atoms - they will carry the data // directly as opposed to traditional iTunes-style metadata that does store // the information on 'data' atoms. But user-defined is user-defined, so // that is how it will be defined here. modified_atoms = true; desiredAtom = APar_FindAtom(uuid_path, true, EXTENDED_ATOM, 0, true); desiredAtom->uuid_ap_atomname = (char *)calloc( 1, sizeof(char) * 10); // only useful to print out the atom tree midway // through an operation memcpy(desiredAtom->uuid_ap_atomname, uuid_4char_name, 4); // only useful to print out the atom tree midway through an // operation if (dataType == AtomFlags_Data_Text) APar_MetaData_atom_QuickInit( desiredAtom->AtomicNumber, dataType, 20); //+20 including the 4 NULL bytes preceding any string we set // NOTE: setting a file into a uuid atom (dataType == // AtomFlags_Data_uuid_binary) is handled in main.cpp - the length of the // file extension, description and file all add up to the amount to malloc // AtomicData to, so handle that separately. parsedAtoms[desiredAtom->AtomicNumber].AtomicClassification = EXTENDED_ATOM; APar_FlagMovieHeader(); } return desiredAtom; } /*---------------------- APar_MetaData_atom_QuickInit atom_num - the position in the parsedAtoms array (either found in the file or a newly created sparse atom) so AtomicData can be initialized atomFlags - the AtomicVerFlags for the iTunes-style metadata atom supplemental_length - iTunes-style metadata for 'data' atoms is >= 16bytes long; AtomicParsley created uuid atoms will be +4bytes directly on that atom allotment - the bytes of AtomicData to malloc (defaults to MAXDATA_PAYLOAD + 1 (+50) unless changed - like uuids from file) Metadata_QuickInit will initialize a pre-found atom to MAXDATA_PAYLOAD so that it can carry info on AtomicData ----------------------*/ void APar_MetaData_atom_QuickInit(short atom_num, const uint32_t atomFlags, uint32_t supplemental_length, uint32_t allotment) { // this will skip the finding of atoms and just malloc the AtomicData; used by // genre & artwork parsedAtoms[atom_num].AtomicData = (char *)calloc(1, sizeof(char) * allotment + 50); if (parsedAtoms[atom_num].AtomicData == NULL) { fprintf(stdout, "AP error: there was insufficient memory available for allocation. " "Exiting.%c\n", '\a'); exit(1); } parsedAtoms[atom_num].AtomicLength = 16 + supplemental_length; // 4bytes atom length, 4 bytes atom length, 4 // bytes version/flags, 4 bytes NULL parsedAtoms[atom_num].AtomicVerFlags = atomFlags; parsedAtoms[atom_num].AtomicContainerState = CHILD_ATOM; parsedAtoms[atom_num].AtomicClassification = VERSIONED_ATOM; return; } /*---------------------- APar_MetaData_atom_Init atom_path - the hierarchical path to the specific atom carrying iTunes-style metadata that will be found (and created if necessary) MD_Payload - the information to be carried (also used as a test if NULL to remove the atom) atomFlags - the AtomicVerFlags for the atom (text, integer or unsigned integer) Metadata_Init will search for and create the necessary hierarchy so that the atom can be initialized to carry the payload data on AtomicData. This will provide a shell of an atom with 4bytes length, 4bytes name, 4bytes version/flags, 4bytes NULL + any other data ----------------------*/ AtomicInfo *APar_MetaData_atom_Init(const char *atom_path, const char *MD_Payload, const uint32_t atomFlags) { // this will handle the vanilla iTunes-style metadata atoms; genre will be // handled elsewehere because it gets carried on 2 different atoms, and // artwork gets special treatment because it can have multiple child data // atoms if (metadata_style != ITUNES_STYLE) return NULL; bool retain_atom = true; if (strlen(MD_Payload) == 0) { retain_atom = false; } if (retain_atom) { APar_Verify__udta_meta_hdlr__atom(); } AtomicInfo *desiredAtom = APar_FindAtom(atom_path, retain_atom, VERSIONED_ATOM, 0); // finds the atom; if not present, creates the atom if (desiredAtom == NULL) return NULL; modified_atoms = true; if (!retain_atom) { AtomicInfo *parent_atom = &parsedAtoms[APar_FindParentAtom( desiredAtom->AtomicNumber, desiredAtom->AtomicLevel)]; if (desiredAtom->AtomicNumber > 0 && parent_atom->AtomicNumber > 0) { APar_EliminateAtom(parent_atom->AtomicNumber, desiredAtom->NextAtomNumber); APar_FlagMovieHeader(); return NULL; } } else { parsedAtoms[desiredAtom->AtomicNumber].AtomicData = (char *)malloc( sizeof(char) * MAXDATA_PAYLOAD + 1); // puts a hard limit on the length of strings (the spec doesn't) memset(parsedAtoms[desiredAtom->AtomicNumber].AtomicData, 0, sizeof(char) * MAXDATA_PAYLOAD + 1); parsedAtoms[desiredAtom->AtomicNumber].AtomicLength = 16; // 4bytes atom length, 4 bytes atom length, 4 bytes version/flags, 4 // bytes NULL parsedAtoms[desiredAtom->AtomicNumber].AtomicVerFlags = atomFlags; parsedAtoms[desiredAtom->AtomicNumber].AtomicContainerState = CHILD_ATOM; parsedAtoms[desiredAtom->AtomicNumber].AtomicClassification = VERSIONED_ATOM; APar_FlagMovieHeader(); } return desiredAtom; } /*---------------------- APar_UserData_atom_Init userdata_atom_name - the name of the atom to be set ('titl', 'loci', 'cprt') atom_payload - the information to be carried (also used as a test if NULL to remove the atom) udta_container - determines whether to create an atom path at movie level or track level track_idx - provide the track for the target atom if at track level userdata_lang - the language for the tag (multiple tags with the same name, but different languages are supported for some of these atoms) UserData_Init can be called multiple times from a single cli invocation (if used to target at atom at track level for all tracks). Since it can be called repeatedly, the atom path is created here based on the container for 'udta': 'moov' for movie level or 'trak' at track level. If there is no payload that atom path is found & if found to exists is removed. If the payload does contain something, the created atom path is created, initialized & the language setting is set (for internal AP use) for that atom. NOTE: the language setting (if supported - yrrc doesn't) occurs in different places in 3GP atoms. Most occur right after atom flags/versioning - but in rtng/clsf they occur later. The language is instead stored in binary form amid the data for the atom, but is also put into the parsedAtoms[] array (that information is only used in finding atoms not the actual writing of atoms out). Both (storing the language in AtomicData in binary form & put in the parsedAtoms[] AtomicInfo array) forms are required to implement multiple language support for 3gp atoms. NOTE2: Perhaps there is something wrong with Apple's implementation of 3gp metadata, or I'm loosing my mind. The exact same utf8 string that shows up in a 3gp file as ??? - ??? shows up *perfect* in an mp4 or mov container. Encoded as utf16 same problem a sample string using Polish glyphs in utf8 has some gylphs missing with lang=eng. The same string with 'lang=pol', and different glyphs are missing. The problem occurs using unicode.org's ConvertUTF8toUTF16 or using libxmls's UTF8ToUTF16BE (when converting to utf16) in the same places - except for the copyright symbol which unicode.org's ConvertUTF16toUTF8 didn't properly convert - which was the reason why libxml's functions are now used. And at no point can I get the audio protected P-in-a-circle glyph to show up in utf8 or utf16. To summarize, either I am completely overlooking some interplay (of lang somehow changing the utf8 or utf16 standard), the unicode translations are off (which in the case of utf8 is embedded directly on Mac OS X, so that can't be it), or Apple's 3gp implementation is off. TODO NOTE: the track modification date should change if set at track level because of this ----------------------*/ AtomicInfo *APar_UserData_atom_Init(const char *userdata_atom_name, const char *atom_payload, uint8_t udta_container, uint8_t track_idx, uint16_t userdata_lang) { uint8_t atom_type = PACKED_LANG_ATOM; uint8_t total_tracks = 0; uint8_t a_track = 0; // unused AtomicInfo *desiredAtom = NULL; char *userdata_atom_path = NULL; if (userdata_lang == 0) atom_type = VERSIONED_ATOM; APar_FindAtomInTrack(total_tracks, a_track, NULL); // With track_num set to 0, it will return the // total trak atom into total_tracks here. if (track_idx > total_tracks || (track_idx == 0 && udta_container == SINGLE_TRACK_ATOM)) { APar_assert(false, 5, userdata_atom_name); return NULL; } userdata_atom_path = (char *)malloc(sizeof(char) * 400); if (udta_container == MOVIE_LEVEL_ATOM) { APar_sprintf_atompath( userdata_atom_path, userdata_atom_name, 0xFF, MOVIE_LEVEL_ATOM, 400); } else if (udta_container == ALL_TRACKS_ATOM) { APar_sprintf_atompath(userdata_atom_path, userdata_atom_name, track_idx, ALL_TRACKS_ATOM, 400); } else { APar_sprintf_atompath(userdata_atom_path, userdata_atom_name, track_idx, SINGLE_TRACK_ATOM, 400); } if (strlen(atom_payload) == 0) { APar_RemoveAtom(userdata_atom_path, atom_type, atom_type == VERSIONED_ATOM ? 1 : userdata_lang); // find the atom; don't create if it's // "" to remove free(userdata_atom_path); userdata_atom_path = NULL; return NULL; } else { modified_atoms = true; desiredAtom = APar_FindAtom(userdata_atom_path, true, atom_type, userdata_lang); desiredAtom->AtomicData = (char *)calloc( 1, sizeof(char) * MAXDATA_PAYLOAD); // puts a hard limit on the length // of strings (the spec doesn't) desiredAtom->AtomicLength = 12; // 4bytes atom length, 4 bytes atom length, // 4 bytes version/flags (NULLs) desiredAtom->AtomicVerFlags = 0; desiredAtom->AtomicContainerState = CHILD_ATOM; desiredAtom->AtomicClassification = atom_type; desiredAtom->AtomicLanguage = userdata_lang; APar_FlagTrackHeader(desiredAtom); } free(userdata_atom_path); userdata_atom_path = NULL; return desiredAtom; } /*---------------------- APar_reverseDNS_atom_Init rDNS_atom_name - the name of the descriptor for the reverseDNS atom form (like iTunNORM) rDNS_payload - the information to be carried (also used as a test if NULL to remove the atom) atomFlags - text, integer, binary flag of the data payload rDNS_domain - the reverse domain itself (like net.sourceforge.atomicparsley or com.apple.iTunes) FILL IN ----------------------*/ AtomicInfo *APar_reverseDNS_atom_Init(const char *rDNS_atom_name, const char *rDNS_payload, const uint32_t *atomFlags, const char *rDNS_domain) { AtomicInfo *desiredAtom = NULL; char *reverseDNS_atompath = (char *)calloc(1, sizeof(char) * 2001); if (metadata_style != ITUNES_STYLE) { free(reverseDNS_atompath); reverseDNS_atompath = NULL; return NULL; } sprintf(reverseDNS_atompath, "moov.udta.meta.ilst.----.name:[%s]", rDNS_atom_name); // moov.udta.meta.ilst.----.name:[iTunNORM] if (rDNS_payload != NULL) { if (strlen(rDNS_payload) == 0) { APar_RemoveAtom(reverseDNS_atompath, VERSIONED_ATOM, 0, rDNS_domain); free(reverseDNS_atompath); reverseDNS_atompath = NULL; return NULL; } APar_Verify__udta_meta_hdlr__atom(); } else { APar_RemoveAtom(reverseDNS_atompath, VERSIONED_ATOM, 0, rDNS_domain); APar_FlagMovieHeader(); free(reverseDNS_atompath); reverseDNS_atompath = NULL; return NULL; } desiredAtom = APar_FindAtom(reverseDNS_atompath, false, VERSIONED_ATOM, 0, false, rDNS_domain); // finds the atom; do NOT create it if not // found - manually create the hierarchy if (desiredAtom == NULL) { AtomicInfo *ilst_atom = APar_FindAtom("moov.udta.meta.ilst", true, SIMPLE_ATOM, 0); short last_iTunes_list_descriptor = APar_FindLastChild_of_ParentAtom( ilst_atom->AtomicNumber); // the *last* atom contained by ilst - even if // its the 4th 'data' atom short rDNS_four_dash_parent = APar_InterjectNewAtom("----", PARENT_ATOM, SIMPLE_ATOM, 8, 0, 0, ilst_atom->AtomicLevel + 1, last_iTunes_list_descriptor); short rDNS_mean_atom = APar_InterjectNewAtom("mean", CHILD_ATOM, VERSIONED_ATOM, 12, AtomFlags_Data_Binary, 0, ilst_atom->AtomicLevel + 2, rDNS_four_dash_parent); uint32_t domain_len = strlen(rDNS_domain); parsedAtoms[rDNS_mean_atom].ReverseDNSdomain = (char *)calloc(1, sizeof(char) * 101); memcpy( parsedAtoms[rDNS_mean_atom].ReverseDNSdomain, rDNS_domain, domain_len); APar_atom_Binary_Put( &parsedAtoms[rDNS_mean_atom], rDNS_domain, domain_len, 0); short rDNS_name_atom = APar_InterjectNewAtom("name", CHILD_ATOM, VERSIONED_ATOM, 12, AtomFlags_Data_Binary, 0, ilst_atom->AtomicLevel + 2, rDNS_mean_atom); uint32_t name_len = strlen(rDNS_atom_name); parsedAtoms[rDNS_name_atom].ReverseDNSname = (char *)calloc(1, sizeof(char) * 101); memcpy( parsedAtoms[rDNS_name_atom].ReverseDNSname, rDNS_atom_name, name_len); APar_atom_Binary_Put( &parsedAtoms[rDNS_name_atom], rDNS_atom_name, name_len, 0); AtomicInfo proto_rDNS_data_atom = {0}; APar_CreateSurrogateAtom(&proto_rDNS_data_atom, "data", ilst_atom->AtomicLevel + 2, VERSIONED_ATOM, 0, NULL, 0); desiredAtom = APar_CreateSparseAtom(&proto_rDNS_data_atom, ilst_atom, rDNS_name_atom); APar_MetaData_atom_QuickInit( desiredAtom->AtomicNumber, *atomFlags, 0, MAXDATA_PAYLOAD); } else { if (strcmp(rDNS_domain, "com.apple.iTunes") == 0) { // for the iTunes domain, only support 1 'data' entry APar_MetaData_atom_QuickInit( desiredAtom->NextAtomNumber, *atomFlags, 0, MAXDATA_PAYLOAD); desiredAtom = &parsedAtoms[desiredAtom->NextAtomNumber]; } else { // now create a 'data' atom at the end of the hierarchy (allowing // multiple entries) short rDNSparent_idx = APar_FindParentAtom(desiredAtom->AtomicNumber, desiredAtom->AtomicLevel); short last_child = APar_FindLastChild_of_ParentAtom(rDNSparent_idx); AtomicInfo proto_rDNS_data_atom = {0}; APar_CreateSurrogateAtom(&proto_rDNS_data_atom, "data", parsedAtoms[last_child].AtomicLevel, VERSIONED_ATOM, 0, NULL, 0); desiredAtom = APar_CreateSparseAtom( &proto_rDNS_data_atom, &parsedAtoms[rDNSparent_idx], last_child); APar_MetaData_atom_QuickInit( desiredAtom->AtomicNumber, *atomFlags, 0, MAXDATA_PAYLOAD); } } APar_FlagMovieHeader(); free(reverseDNS_atompath); reverseDNS_atompath = NULL; modified_atoms = true; return desiredAtom; } AtomicInfo *APar_ID32_atom_Init(const char *frameID_str, signed char meta_area, const char *lang_str, uint16_t id32_lang) { uint8_t total_tracks = 0; uint8_t a_track = 0; // unused AtomicInfo *meta_atom = NULL; AtomicInfo *hdlr_atom = NULL; char *id32_trackpath = NULL; AtomicInfo *ID32_atom = NULL; bool non_referenced_data = false; bool remove_ID32_atom = false; APar_FindAtomInTrack(total_tracks, a_track, NULL); // With track_num set to 0, it will return the // total trak atom into total_tracks here. if (meta_area > 0) { if ((uint8_t)meta_area > total_tracks) { APar_assert(false, 6, frameID_str); return NULL; } } id32_trackpath = (char *)calloc(1, sizeof(char) * 100); if (meta_area == 0 - FILE_LEVEL_ATOM) { meta_atom = APar_FindAtom("meta", false, DUAL_STATE_ATOM, 0); } else if (meta_area == 0 - MOVIE_LEVEL_ATOM) { meta_atom = APar_FindAtom("moov.meta", false, DUAL_STATE_ATOM, 0); //} else if (meta_area = 0) { // setting id3tags for all tracks will *not* be supported; } else if (meta_area > 0) { sprintf(id32_trackpath, "moov.trak[%u].meta", meta_area); meta_atom = APar_FindAtom(id32_trackpath, false, DUAL_STATE_ATOM, 0); } if (meta_atom != NULL) { hdlr_atom = APar_FindChildAtom(meta_atom->AtomicNumber, "hdlr"); if (hdlr_atom != NULL) { if (hdlr_atom->ancillary_data != 0x49443332) { memset(id32_trackpath, 0, 5); // well, it won't be used for anything else since the handler // type doesn't match, might as well convert the handler type // using it UInt32_TO_String4(hdlr_atom->ancillary_data, id32_trackpath); APar_assert(false, 7, id32_trackpath); free(id32_trackpath); return NULL; } } } // its possible the ID32 atom targeted already exists - finding it in the // traditional form (not external, and not locally referenced) is easiest. // Locally referenced isn't. if (meta_area == 0 - FILE_LEVEL_ATOM) { ID32_atom = APar_FindAtom("meta.ID32", false, PACKED_LANG_ATOM, id32_lang); } else if (meta_area == 0 - MOVIE_LEVEL_ATOM) { ID32_atom = APar_FindAtom("moov.meta.ID32", false, PACKED_LANG_ATOM, id32_lang); //} else if (meta_area = 0) { // setting id3tags for all tracks will *not* be supported; } else if (meta_area > 0) { sprintf(id32_trackpath, "moov.trak[%u].meta.ID32", meta_area); ID32_atom = APar_FindAtom(id32_trackpath, false, PACKED_LANG_ATOM, id32_lang); } if (ID32_atom != NULL) { free(id32_trackpath); id32_trackpath = NULL; if (ID32_atom->ID32_TagInfo == NULL) { APar_ID32_ScanID3Tag(source_file, ID32_atom); } return ID32_atom; // and that completes finding the ID32 atom and verifying // that it was local and in a traditionally represented // form. } else { if (meta_atom != NULL) { // if the primary item atom is present, it points to either a local data // reference (flag of 0x000001) or external data which is unsupported. // Either way skip it. //..or probably another test would be if a data REFERENCE atom were // present.... but you would have to match item_IDs - which are found in // pitm (required for ID32). if (APar_FindChildAtom(meta_atom->AtomicNumber, "pitm") != NULL) { non_referenced_data = false; APar_assert(false, 8, frameID_str); free(id32_trackpath); return NULL; } else { // the inline/3gpp 'ID32' atom calls for referenced content to // carry a 'pitm' atom. No worries - its just a 'meta'. non_referenced_data = true; } } else { // no 'meta' atom? Great - a blank slate. There won't be any jumping // through a multitude of atoms to determine referencing non_referenced_data = true; } } if (frameID_str == NULL) { remove_ID32_atom = true; } else if (strlen(frameID_str) == 0) { remove_ID32_atom = true; } // this only gets executed if a pre-existing satisfactory ID32 atom was not // found. Being able to find it by atom.path by definition means it was not // referenced. if (non_referenced_data && !remove_ID32_atom) { if (meta_atom == NULL) { if (meta_area == 0 - FILE_LEVEL_ATOM) { meta_atom = APar_FindAtom("meta", true, VERSIONED_ATOM, 0); } else if (meta_area == 0 - MOVIE_LEVEL_ATOM) { meta_atom = APar_FindAtom("moov.meta", true, VERSIONED_ATOM, 0); //} else if (meta_area = 0) { // setting id3tags for all tracks will *not* be supported; } else if (meta_area > 0) { sprintf(id32_trackpath, "moov.trak[%u].meta", meta_area); meta_atom = APar_FindAtom(id32_trackpath, true, VERSIONED_ATOM, 0); } } // create the required hdlr atom if (hdlr_atom == NULL) { if (meta_area == 0 - FILE_LEVEL_ATOM) { hdlr_atom = APar_FindAtom("meta.hdlr", true, VERSIONED_ATOM, 0); } else if (meta_area == 0 - MOVIE_LEVEL_ATOM) { hdlr_atom = APar_FindAtom("moov.meta.hdlr", true, VERSIONED_ATOM, 0); //} else if (meta_area = 0) { // setting id3tags for all tracks will *not* be supported; } else if (meta_area > 0) { sprintf(id32_trackpath, "moov.trak[%u].meta.hdlr", meta_area); hdlr_atom = APar_FindAtom(id32_trackpath, true, VERSIONED_ATOM, 0); } if (hdlr_atom == NULL) { fprintf(stdout, "Uh, problem\n"); exit(0); } APar_MetaData_atom_QuickInit(hdlr_atom->AtomicNumber, 0, 0); APar_Unified_atom_Put( hdlr_atom, "ID32", UTF8_iTunesStyle_256glyphLimited, 0, 0); APar_Unified_atom_Put( hdlr_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 32); APar_Unified_atom_Put( hdlr_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 32); APar_Unified_atom_Put( hdlr_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 32); APar_Unified_atom_Put( hdlr_atom, "AtomicParsley ID3v2 Handler", UTF8_3GP_Style, 0, 0); hdlr_atom->ancillary_data = 0x49443332; } // and finally create the ID32 atom if (meta_area == 0 - FILE_LEVEL_ATOM) { ID32_atom = APar_FindAtom("meta.ID32", true, PACKED_LANG_ATOM, id32_lang); } else if (meta_area == 0 - MOVIE_LEVEL_ATOM) { ID32_atom = APar_FindAtom("moov.meta.ID32", true, PACKED_LANG_ATOM, id32_lang); //} else if (meta_area = 0) { // setting id3tags for all tracks will *not* be supported; } else if (meta_area > 0) { sprintf(id32_trackpath, "moov.trak[%u].meta.ID32", meta_area); ID32_atom = APar_FindAtom(id32_trackpath, true, PACKED_LANG_ATOM, id32_lang); } if (id32_trackpath != NULL) { free(id32_trackpath); id32_trackpath = NULL; } ID32_atom->AtomicLength = 12; // 4bytes atom length, 4 bytes atom length, 4 // bytes version/flags (NULLs), 2 bytes lang ID32_atom->AtomicVerFlags = 0; ID32_atom->AtomicContainerState = CHILD_ATOM; ID32_atom->AtomicClassification = PACKED_LANG_ATOM; ID32_atom->AtomicLanguage = id32_lang; APar_ID3Tag_Init(ID32_atom); // search for the desired frame // add the frame to an empty frame, copy data onto the id32_atom structure // as required set modified _atoms to true return ID32_atom; } else if (remove_ID32_atom) { if (meta_area == 0 - FILE_LEVEL_ATOM) { APar_RemoveAtom("meta.ID32", PACKED_LANG_ATOM, id32_lang); } else if (meta_area == 0 - MOVIE_LEVEL_ATOM) { APar_RemoveAtom("moov.meta.ID32", PACKED_LANG_ATOM, id32_lang); //} else if (meta_area = 0) { // setting id3tags for all tracks will *not* be supported; } else if (meta_area > 0) { sprintf(id32_trackpath, "moov.trak[%u].meta.ID32", meta_area); APar_RemoveAtom(id32_trackpath, PACKED_LANG_ATOM, id32_lang); } } return NULL; } void APar_RenderAllID32Atoms() { short atom_idx = 0; short meta_idx = -1; // loop through each atom in the struct array (which holds the offset // info/data) while (true) { if (memcmp(parsedAtoms[atom_idx].AtomicName, "ID32", 4) == 0) { if (parsedAtoms[atom_idx].ID32_TagInfo != NULL) { uint64_t id32tag_max_length = APar_GetTagSize(&parsedAtoms[atom_idx]); if (id32tag_max_length > 0) { parsedAtoms[atom_idx].AtomicData = (char *)calloc( 1, sizeof(char) * id32tag_max_length + (16 * parsedAtoms[atom_idx].ID32_TagInfo->ID3v2_FrameCount)); APar_Unified_atom_Put(&parsedAtoms[atom_idx], NULL, 0, parsedAtoms[atom_idx].AtomicLanguage, 16); parsedAtoms[atom_idx].AtomicLength = APar_Render_ID32_Tag( &parsedAtoms[atom_idx], id32tag_max_length + (16 * parsedAtoms[atom_idx].ID32_TagInfo->ID3v2_FrameCount)) + 14; if (parsedAtoms[atom_idx].AtomicLength < 12 + 10) { meta_idx = APar_FindParentAtom(parsedAtoms[atom_idx].AtomicNumber, parsedAtoms[atom_idx].AtomicLevel); APar_EliminateAtom(parsedAtoms[atom_idx].AtomicNumber, parsedAtoms[atom_idx].NextAtomNumber); } else { APar_FlagTrackHeader(&parsedAtoms[atom_idx]); } } else { meta_idx = APar_FindParentAtom(parsedAtoms[atom_idx].AtomicNumber, parsedAtoms[atom_idx].AtomicLevel); APar_EliminateAtom(parsedAtoms[atom_idx].AtomicNumber, parsedAtoms[atom_idx].NextAtomNumber); } } } if (meta_idx > 0) { if (memcmp(parsedAtoms[meta_idx].AtomicName, "meta", 4) == 0) { if (APar_ReturnChildrenAtoms(meta_idx, 0) == 1) { AtomicInfo *meta_handler = &parsedAtoms[parsedAtoms[meta_idx].NextAtomNumber]; if (memcmp(meta_handler->AtomicName, "hdlr", 4) == 0 && meta_handler->ancillary_data == 0x49443332) { APar_EliminateAtom(meta_idx, meta_handler->NextAtomNumber); } } } } atom_idx = parsedAtoms[atom_idx].NextAtomNumber; if (parsedAtoms[atom_idx].AtomicNumber == 0) break; meta_idx = -1; } return; } /*---------------------- APar_TestVideoDescription video_desc_atom - the avc1 atom after stsd that contains the height/width ISObmff_file - the reopened source file read in the height, width, profile & level of an avc (non-drm) track. If the the macroblocks are between 300 & 1200, return a non-zero number to allow the ipod uuid to be written NOTE: this requires the deep scan cli flag to break out stsd from its normal monolithic form ----------------------*/ uint16_t APar_TestVideoDescription(AtomicInfo *video_desc_atom, FILE *ISObmff_file) { uint16_t video_width = 0; uint16_t video_height = 0; uint16_t video_macroblocks = 0; uint8_t video_profile = 0; uint8_t video_level = 0; AtomicInfo *avcC_atom = NULL; if (ISObmff_file == NULL) return 0; char *avc1_contents = (char *)calloc(1, sizeof(char) * (size_t)video_desc_atom->AtomicLength); if (avc1_contents == NULL) { fclose(ISObmff_file); return 0; } APar_readX( avc1_contents, ISObmff_file, video_desc_atom->AtomicStart, video_desc_atom ->AtomicLength); // actually reads in avcC as well, but is unused video_width = UInt16FromBigEndian( avc1_contents + 32); // well, iTunes only allows 640 max but the avc wiki says it *could* // go up to 720, so I won't bother to check it video_height = UInt16FromBigEndian(avc1_contents + 34); video_macroblocks = (video_width / 16) * (video_height / 16); avcC_atom = APar_FindChildAtom(video_desc_atom->AtomicNumber, "avcC"); if (avcC_atom != NULL) { uint64_t avcC_offset = avcC_atom->AtomicStart - video_desc_atom->AtomicStart; video_profile = *(avc1_contents + avcC_offset + 9); video_level = *(avc1_contents + avcC_offset + 11); } if (video_profile == 66 && video_level <= 30) { if (video_macroblocks > 300 && video_macroblocks <= 1200) { if (video_level <= 30 && avcC_atom != NULL) { avcC_atom->AtomicData = (char *)calloc(1, sizeof(char) * (size_t)avcC_atom->AtomicLength); APar_readX(avcC_atom->AtomicData, ISObmff_file, avcC_atom->AtomicStart + 8, avcC_atom->AtomicLength - 8); if (video_macroblocks > 396 && video_macroblocks <= 792) { *(avcC_atom->AtomicData + 3) = 21; } else if (video_macroblocks > 792) { *(avcC_atom->AtomicData + 3) = 22; } } fclose(ISObmff_file); return video_macroblocks; } else { fprintf(stdout, "AtomicParsley warning: the AVC track macroblocks were " "not in the required range (300-1200). Skipping.\n"); } } else { fprintf(stdout, "AtomicParsley warning: the AVC track profile/level was " "too high. The ipod hi-res uuid was not added.\n"); } fclose(ISObmff_file); return 0; } /*---------------------- APar_TestVideoDescription atom_path - pointer to the string containing the atom.path already targeted to the right track Find/Create the ipod hi-res (1200 macroblock) uuid for the avc1 track & set up its default parameters NOTE: this requires the deep scan cli flag to break out stsd from its normal monolithic form ----------------------*/ void APar_Generate_iPod_uuid(char *atom_path) { AtomicInfo *ipod_uuid_atom = NULL; ipod_uuid_atom = APar_FindAtom(atom_path, false, EXTENDED_ATOM, 0, true); if (ipod_uuid_atom == NULL) { ipod_uuid_atom = APar_FindAtom(atom_path, true, EXTENDED_ATOM, 0, true); if (ipod_uuid_atom == NULL) { fprintf(stdout, "An error occured trying to create the ipod uuid atom " "for the avc track\n"); return; } ipod_uuid_atom->AtomicData = (char *)calloc(1, sizeof(char) * 60); ipod_uuid_atom->AtomicContainerState = CHILD_ATOM; ipod_uuid_atom->AtomicClassification = EXTENDED_ATOM; ipod_uuid_atom->uuid_style = UUID_OTHER; ipod_uuid_atom->AtomicLength = 24; APar_Unified_atom_Put( ipod_uuid_atom, NULL, UTF8_iTunesStyle_Unlimited, 1, 32); modified_atoms = true; APar_FlagTrackHeader(ipod_uuid_atom); APar_FlagMovieHeader(); track_codecs.has_avc1 = true; // only used on Mac OS X when setting the ipod // uuid *only* (otherwise it gets set properly) } else { fprintf(stdout, "the ipod higher-resolution uuid is already present.\n"); } return; } /////////////////////////////////////////////////////////////////////////////////////// // offset calculations // /////////////////////////////////////////////////////////////////////////////////////// // determine if our mdat atom has moved at all... uint64_t APar_DetermineMediaData_AtomPosition() { uint64_t mdat_position = 0; short thisAtomNumber = 0; // loop through each atom in the struct array (which holds the offset // info/data) while (parsedAtoms[thisAtomNumber].NextAtomNumber != 0) { if (strncmp(parsedAtoms[thisAtomNumber].AtomicName, "mdat", 4) == 0 && parsedAtoms[thisAtomNumber].AtomicLevel == 1) { if (parsedAtoms[thisAtomNumber].AtomicLength <= 1 || parsedAtoms[thisAtomNumber].AtomicLength > 75) { break; } } else if (parsedAtoms[thisAtomNumber].AtomicLevel == 1 && parsedAtoms[thisAtomNumber].AtomicLengthExtended == 0) { mdat_position += parsedAtoms[thisAtomNumber].AtomicLength; } else { // part of the pseudo 64-bit support mdat_position += parsedAtoms[thisAtomNumber].AtomicLengthExtended; } thisAtomNumber = parsedAtoms[thisAtomNumber].NextAtomNumber; } return mdat_position; } uint32_t APar_SimpleSumAtoms(short stop_atom) { uint32_t byte_sum = 0; // first, find the first mdat after this initial 'tfhd' atom to get the sum // relative to that atom while (true) { if (strncmp(parsedAtoms[stop_atom].AtomicName, "mdat", 4) == 0) { stop_atom--; // don't include the fragment's mdat, just the atoms prior to // it break; } else { if (parsedAtoms[stop_atom].NextAtomNumber != 0) { stop_atom = parsedAtoms[stop_atom].NextAtomNumber; } else { break; } } } byte_sum += 8; // the 'tfhd' points to the byte in mdat where the fragment data is - // NOT the atom itself (should always be +8bytes with a fragment) while (true) { if (parsedAtoms[stop_atom].AtomicLevel == 1) { byte_sum += (parsedAtoms[stop_atom].AtomicLength == 1 ? parsedAtoms[stop_atom].AtomicLengthExtended : parsedAtoms[stop_atom].AtomicLength); // fprintf(stdout, "%i %s (%" PRIu64 ")\n", stop_atom, // parsedAtoms[stop_atom].AtomicName, // parsedAtoms[stop_atom].AtomicLength); } if (stop_atom == 0) { break; } else { stop_atom = APar_FindPrecedingAtom(stop_atom); } } return byte_sum; } /*---------------------- APar_QuickSumAtomicLengths target_atom - pointer to the atom that will have its position within the *new* file determined; NOTE: only level 1 or level 2 atoms in moov fill ----------------------*/ uint64_t APar_QuickSumAtomicLengths(AtomicInfo *target_atom) { uint64_t atom_pos = 0; short atom_idx = 0; short current_level = 0; if (target_atom == NULL) return atom_pos; if (target_atom->AtomicLevel > 2) return atom_pos; atom_idx = APar_FindPrecedingAtom(target_atom->AtomicNumber); current_level = target_atom->AtomicLevel; while (true) { if (parsedAtoms[atom_idx].AtomicLevel <= target_atom->AtomicLevel) { if (parsedAtoms[atom_idx].AtomicContainerState >= DUAL_STATE_ATOM || parsedAtoms[atom_idx].AtomicLevel == 2) { atom_pos += (parsedAtoms[atom_idx].AtomicLength == 1 ? parsedAtoms[atom_idx].AtomicLengthExtended : parsedAtoms[atom_idx].AtomicLength); } else if (parsedAtoms[atom_idx].AtomicContainerState <= SIMPLE_PARENT_ATOM) { if (target_atom->AtomicLevel == 1) { atom_pos += (parsedAtoms[atom_idx].AtomicLength == 1 ? parsedAtoms[atom_idx].AtomicLengthExtended : parsedAtoms[atom_idx].AtomicLength); } else { atom_pos += 8; } } } if (atom_idx == 0) break; atom_idx = APar_FindPrecedingAtom(atom_idx); } return atom_pos; } /*---------------------- APar_Constituent_mdat_data desired_data_pos - the position in the file where the desired data begins desired_data_len - the length of the desired data contained within the file fill ----------------------*/ AtomicInfo *APar_Constituent_mdat_data(uint64_t desired_data_pos, uint64_t desired_data_len) { AtomicInfo *target_mdat = NULL; short eval_atom = 0; while (parsedAtoms[eval_atom].NextAtomNumber != 0) { if (memcmp(parsedAtoms[eval_atom].AtomicName, "mdat", 4) == 0 && parsedAtoms[eval_atom].AtomicLevel == 1) { if (parsedAtoms[eval_atom].AtomicLength == 1) { if ((parsedAtoms[eval_atom].AtomicStart + parsedAtoms[eval_atom].AtomicLengthExtended >= desired_data_pos + desired_data_len) && parsedAtoms[eval_atom].AtomicStart < desired_data_pos) { target_mdat = &parsedAtoms[eval_atom]; break; } } else { if ((parsedAtoms[eval_atom].AtomicStart + parsedAtoms[eval_atom].AtomicLength >= desired_data_pos + desired_data_len) && parsedAtoms[eval_atom].AtomicStart < desired_data_pos) { target_mdat = &parsedAtoms[eval_atom]; break; } } } eval_atom = parsedAtoms[eval_atom].NextAtomNumber; } return target_mdat; } /*---------------------- APar_Readjust_iloc_atom iloc_number - the target iloc atom index in parsedAtoms fill ----------------------*/ bool APar_Readjust_iloc_atom(short iloc_number) { bool iloc_changed = false; parsedAtoms[iloc_number].AtomicData = (char *)calloc( 1, sizeof(char) * (size_t)(parsedAtoms[iloc_number].AtomicLength)); APar_readX(parsedAtoms[iloc_number].AtomicData, source_file, parsedAtoms[iloc_number].AtomicStart + 12, parsedAtoms[iloc_number].AtomicLength - 12); uint8_t offset_size = (*parsedAtoms[iloc_number].AtomicData >> 4) & 0x0F; uint8_t length_size = *parsedAtoms[iloc_number].AtomicData & 0x0F; uint8_t base_offset_size = (*(parsedAtoms[iloc_number].AtomicData + 1) >> 4) & 0x0F; uint16_t item_count = UInt16FromBigEndian(parsedAtoms[iloc_number].AtomicData + 2); uint64_t aggregate_offset = 4; char *base_offset_ptr = NULL; #if defined(DEBUG_V) fprintf(stdout, "debug: AP_Readjust_iloc_atom Offset %X, len %X, base %X, item " "count %u\n", offset_size, length_size, base_offset_size, item_count); #endif for (uint16_t an_item = 1; an_item <= item_count; an_item++) { #ifdef DEBUG_V uint16_t an_item_ID = #endif UInt16FromBigEndian(parsedAtoms[iloc_number].AtomicData + aggregate_offset); uint16_t a_data_ref_idx = UInt16FromBigEndian( parsedAtoms[iloc_number].AtomicData + aggregate_offset + 2); uint64_t base_offset = 0; uint64_t curr_container_pos = 0; uint64_t extent_len_sum = 0; aggregate_offset += 4; if (a_data_ref_idx != 0) { continue; } if (base_offset_size == 4 || base_offset_size == 8) { base_offset_ptr = parsedAtoms[iloc_number].AtomicData + aggregate_offset; if (base_offset_size == 4) { base_offset = UInt32FromBigEndian(parsedAtoms[iloc_number].AtomicData + aggregate_offset); aggregate_offset += 4; } else { base_offset = UInt32FromBigEndian(parsedAtoms[iloc_number].AtomicData + aggregate_offset); aggregate_offset += 8; } } if (base_offset > 0) { uint16_t this_item_extent_count = UInt16FromBigEndian( parsedAtoms[iloc_number].AtomicData + aggregate_offset); aggregate_offset += 2; for (uint16_t an_extent = 1; an_extent <= this_item_extent_count; an_extent++) { uint64_t this_extent_offset = 0; uint64_t this_extent_length = 0; if (offset_size == 4 || offset_size == 8) { if (offset_size == 4) { this_extent_offset = UInt32FromBigEndian( parsedAtoms[iloc_number].AtomicData + aggregate_offset); aggregate_offset += 4; } else { this_extent_offset = UInt32FromBigEndian( parsedAtoms[iloc_number].AtomicData + aggregate_offset); aggregate_offset += 8; } } if (length_size == 4 || length_size == 8) { if (length_size == 4) { this_extent_length = UInt32FromBigEndian( parsedAtoms[iloc_number].AtomicData + aggregate_offset); aggregate_offset += 4; } else { this_extent_length = UInt32FromBigEndian( parsedAtoms[iloc_number].AtomicData + aggregate_offset); aggregate_offset += 8; } extent_len_sum += this_extent_length; } } // for loop extent #if defined(DEBUG_V) fprintf(stdout, "debug: AP_Readjust_iloc_atom iloc's %u index at base offset: " "%" PRIu64 ", total bytes %" PRIu64 "\n", an_item_ID, base_offset, extent_len_sum); #endif AtomicInfo *container_atom = APar_Constituent_mdat_data(base_offset, 0x013077); if (container_atom != NULL) { curr_container_pos = APar_QuickSumAtomicLengths(container_atom); uint64_t exisiting_offset_into_atom = base_offset - container_atom->AtomicStart; uint64_t new_item_offset = curr_container_pos + exisiting_offset_into_atom; #if defined(DEBUG_V) fprintf(stdout, "debug: AP_Readjust_iloc_atom item is contained on mdat " "started @ %" PRIu64 " (now at %" PRIu64 ")\n", container_atom->AtomicStart, curr_container_pos); fprintf(stdout, "debug: AP_Readjust_iloc_atom item is %" PRIu64 " bytes offset into atom (was %" PRIu64 ", now %" PRIu64 ")\n", exisiting_offset_into_atom, base_offset, new_item_offset); #endif if (base_offset_size == 4) { UInt32_TO_String4(new_item_offset, base_offset_ptr); } else { UInt64_TO_String8(new_item_offset, base_offset_ptr); } iloc_changed = true; } } } return iloc_changed; } bool APar_Readjust_CO64_atom(uint64_t mdat_position, short co64_number) { bool co64_changed = false; if (parsedAtoms[co64_number].ancillary_data != 1) { return co64_changed; } parsedAtoms[co64_number].AtomicData = (char *)calloc( 1, sizeof(char) * (size_t)(parsedAtoms[co64_number].AtomicLength)); APar_readX(parsedAtoms[co64_number].AtomicData, source_file, parsedAtoms[co64_number].AtomicStart + 12, parsedAtoms[co64_number].AtomicLength - 12); parsedAtoms[co64_number].AtomicVerFlags = 0; bool deduct = false; // readjust char *co64_entries = (char *)malloc(sizeof(char) * 4 + 1); memset(co64_entries, 0, sizeof(char) * 4 + 1); memcpy(co64_entries, parsedAtoms[co64_number].AtomicData, 4); uint32_t entries = UInt32FromBigEndian(co64_entries); char *a_64bit_entry = (char *)malloc(sizeof(char) * 8 + 1); memset(a_64bit_entry, 0, sizeof(char) * 8 + 1); for (uint32_t i = 1; i <= entries; i++) { // read 8 bytes of the atom into a 8 char uint64_t a_64bit_entry to eval it for (int c = 0; c <= 7; c++) { // first co64 entry (32-bit uint32_t) is the number of entries; every // other one is an actual offset value a_64bit_entry[c] = parsedAtoms[co64_number].AtomicData[4 + (i - 1) * 8 + c]; } uint64_t this_entry = UInt64FromBigEndian(a_64bit_entry); if (i == 1 && mdat_supplemental_offset == 0) { // for the first chunk, and only for the first *ever* // entry, make the global mdat supplemental offset if (this_entry - removed_bytes_tally > mdat_position) { mdat_supplemental_offset = (uint64_t)mdat_position - ((uint64_t)this_entry - (uint64_t)removed_bytes_tally); bytes_into_mdat = this_entry - bytes_before_mdat - removed_bytes_tally; deduct = true; } else { mdat_supplemental_offset = mdat_position - (this_entry - removed_bytes_tally); bytes_into_mdat = this_entry - bytes_before_mdat - removed_bytes_tally; } if (mdat_supplemental_offset == 0) { break; } } if (mdat_supplemental_offset != 0) { co64_changed = true; } if (deduct) { // crap, uint32_t's were so nice to flip over by themselves to // subtract nicely. going from 32-bit to 64-bit prevents that // flipping this_entry += mdat_supplemental_offset - (bytes_into_mdat * -1); // + bytes_into_mdat; } else { this_entry += mdat_supplemental_offset + bytes_into_mdat; // this is where we add our new mdat offset // difference } UInt64_TO_String8(this_entry, a_64bit_entry); // and put the data back into AtomicData... for (int d = 0; d <= 7; d++) { // first stco entry is the number of entries; every other one is an actual // offset value parsedAtoms[co64_number].AtomicData[4 + (i - 1) * 8 + d] = a_64bit_entry[d]; } } free(a_64bit_entry); free(co64_entries); a_64bit_entry = NULL; co64_entries = NULL; // end readjustment return co64_changed; } bool APar_Readjust_TFHD_fragment_atom(uint64_t mdat_position, short tfhd_number) { static bool tfhd_changed = false; static bool determined_offset = false; static uint64_t base_offset = 0; parsedAtoms[tfhd_number].AtomicData = (char *)calloc( 1, sizeof(char) * (size_t)(parsedAtoms[tfhd_number].AtomicLength)); APar_readX(parsedAtoms[tfhd_number].AtomicData, source_file, parsedAtoms[tfhd_number].AtomicStart + 12, parsedAtoms[tfhd_number].AtomicLength - 12); char tfhd_atomFlags_scrap[10]; memset(tfhd_atomFlags_scrap, 0, sizeof(tfhd_atomFlags_scrap)); // parsedAtoms[tfhd_number].AtomicVerFlags = APar_read32(tfhd_atomFlags_scrap, // source_file, parsedAtoms[tfhd_number].AtomicStart+8); if (parsedAtoms[tfhd_number].AtomicVerFlags & 0x01) { // seems the atomflags suggest bitpacking, but the spec doesn't specify it; // if the 1st bit is set... #if 0 /* not used */ memset(tfhd_atomFlags_scrap, 0, 10); memcpy(tfhd_atomFlags_scrap, parsedAtoms[tfhd_number].AtomicData, 4); uint32_t track_ID = UInt32FromBigEndian(tfhd_atomFlags_scrap); //unused #endif uint64_t tfhd_offset = UInt64FromBigEndian(parsedAtoms[tfhd_number].AtomicData + 4); if (!determined_offset) { determined_offset = true; base_offset = APar_SimpleSumAtoms(tfhd_number) - tfhd_offset; if (base_offset != 0) { tfhd_changed = true; } } tfhd_offset += base_offset; UInt64_TO_String8(tfhd_offset, parsedAtoms[tfhd_number].AtomicData + 4); } return tfhd_changed; } bool APar_Readjust_STCO_atom(uint64_t mdat_position, short stco_number) { bool stco_changed = false; if (parsedAtoms[stco_number].ancillary_data != 1) { return stco_changed; } parsedAtoms[stco_number].AtomicData = (char *)calloc( 1, sizeof(char) * (size_t)(parsedAtoms[stco_number].AtomicLength)); APar_readX(parsedAtoms[stco_number].AtomicData, source_file, parsedAtoms[stco_number].AtomicStart + 12, parsedAtoms[stco_number].AtomicLength - 12); parsedAtoms[stco_number].AtomicVerFlags = 0; // readjust char *stco_entries = (char *)malloc(sizeof(char) * 4 + 1); memset(stco_entries, 0, sizeof(char) * 4 + 1); memcpy(stco_entries, parsedAtoms[stco_number].AtomicData, 4); uint32_t entries = UInt32FromBigEndian(stco_entries); char *an_entry = (char *)malloc(sizeof(char) * 4 + 1); memset(an_entry, 0, sizeof(char) * 4 + 1); for (uint32_t i = 1; i <= entries; i++) { // read 4 bytes of the atom into a 4 char uint32_t an_entry to eval it for (int c = 0; c <= 3; c++) { // first stco entry is the number of entries; every other one is an actual // offset value an_entry[c] = parsedAtoms[stco_number].AtomicData[i * 4 + c]; } uint32_t this_entry = UInt32FromBigEndian(an_entry); if (i == 1 && mdat_supplemental_offset == 0) { // for the first chunk, and only for the first *ever* // entry, make the global mdat supplemental offset mdat_supplemental_offset = (uint64_t)(mdat_position - (this_entry - removed_bytes_tally)); bytes_into_mdat = this_entry - bytes_before_mdat - removed_bytes_tally; if (mdat_supplemental_offset == 0) { break; } } if (mdat_supplemental_offset != 0) { stco_changed = true; } this_entry += mdat_supplemental_offset + bytes_into_mdat; UInt32_TO_String4(this_entry, an_entry); // and put the data back into AtomicData... for (int d = 0; d <= 3; d++) { // first stco entry is the number of entries; every other one is an actual // offset value parsedAtoms[stco_number].AtomicData[i * 4 + d] = an_entry[d]; } } free(an_entry); free(stco_entries); an_entry = NULL; stco_entries = NULL; // end readjustment return stco_changed; } /////////////////////////////////////////////////////////////////////////////////////// // Reorder / Padding // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_CreatePadding padding_length - the new length of padding Create a 'free' atom at a pre-determined area of a given length & set that atom as the global padding store atom ----------------------*/ void APar_CreatePadding(uint64_t padding_length) { AtomicInfo *next_atom = &parsedAtoms[parsedAtoms[dynUpd.consolidated_padding_insertion] .NextAtomNumber]; if (padding_length > 2000 && next_atom->AtomicLevel > 1) { short padding_atom = APar_InterjectNewAtom( "free", CHILD_ATOM, SIMPLE_ATOM, 2000, 0, 0, (next_atom->AtomicLevel == 1 ? 1 : parsedAtoms[dynUpd.consolidated_padding_insertion].AtomicLevel), dynUpd.consolidated_padding_insertion); dynUpd.padding_store = &parsedAtoms[padding_atom]; if (dynUpd.first_mdat_atom != NULL && padding_length - 2000 >= 8) { short padding_res_atom = APar_InterjectNewAtom( "free", CHILD_ATOM, SIMPLE_ATOM, padding_length - 2000, 0, 0, 1, APar_FindPrecedingAtom(dynUpd.first_mdat_atom->AtomicNumber)); dynUpd.padding_resevoir = &parsedAtoms[padding_res_atom]; } } else { short padding_atom = APar_InterjectNewAtom( "free", CHILD_ATOM, SIMPLE_ATOM, padding_length, 0, 0, (next_atom->AtomicLevel == 1 ? 1 : parsedAtoms[dynUpd.consolidated_padding_insertion].AtomicLevel), dynUpd.consolidated_padding_insertion); dynUpd.padding_store = &parsedAtoms[padding_atom]; } return; } /*---------------------- APar_AdjustPadding new_padding_length - the new length of padding Adjust the consolidated padding store atom to the new size - creating&splitting if necessary. ----------------------*/ void APar_AdjustPadding(uint64_t new_padding_length) { uint64_t avail_padding = 0; if ((psp_brand || force_existing_hierarchy) && (dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV)) return; if (dynUpd.padding_store == NULL) { if (new_padding_length >= 8) { APar_CreatePadding(new_padding_length); } else { return; } } if (new_padding_length < 8) { APar_EliminateAtom(dynUpd.padding_store->AtomicNumber, dynUpd.padding_store->NextAtomNumber); dynUpd.updage_by_padding = false; } if (dynUpd.padding_store != NULL) avail_padding += dynUpd.padding_store->AtomicLength; if (dynUpd.padding_resevoir != NULL) avail_padding += dynUpd.padding_resevoir->AtomicLength; if ((new_padding_length > avail_padding && new_padding_length < 2000) || dynUpd.padding_store->AtomicLevel == 1) { free(dynUpd.padding_store->AtomicData); dynUpd.padding_store->AtomicData = (char *)calloc(1, sizeof(char) * new_padding_length); dynUpd.padding_store->AtomicLength = new_padding_length; } else { free(dynUpd.padding_store->AtomicData); dynUpd.padding_store->AtomicData = (char *)calloc(1, sizeof(char) * 2007); dynUpd.padding_store->AtomicLength = 2000; /* since new_padding_length is an unsigned value, if its value is less than * 1992, subtraction of 2000 from it will result in "underflow". That is * what is causing the reported issue of atom detected larger than * filesize. */ if (new_padding_length < 2008) { dynUpd.padding_store->AtomicLength = new_padding_length; return; } if (dynUpd.padding_resevoir == NULL && dynUpd.first_mdat_atom != NULL) { short pad_res_atom = APar_InterjectNewAtom( "free", CHILD_ATOM, SIMPLE_ATOM, new_padding_length - 2000, 0, 0, 1, APar_FindPrecedingAtom(dynUpd.first_mdat_atom->AtomicNumber)); dynUpd.padding_resevoir = &parsedAtoms[pad_res_atom]; dynUpd.padding_resevoir->AtomicLength = new_padding_length - 2000; } else if (dynUpd.padding_resevoir != NULL) { free(dynUpd.padding_resevoir->AtomicData); dynUpd.padding_resevoir->AtomicData = (char *)calloc(1, sizeof(char) * (new_padding_length - 2000)); dynUpd.padding_resevoir->AtomicLength = new_padding_length - 2000; } } return; } /*---------------------- APar_PaddingAmount target_amount - the amount of padding that is requested limit_by_prefs - whether to limit the amount of padding to the environmental preferences When a file will completely rewritten, limit the amount of padding according to the environmental prefs: min & max with a default amount substitued when padding is found to fall outside those values. When a file will be updated by dynamic updating, the consolidated padding will be initially set to the amount that was present before any modifications to tags. This amount of padding will in all likelyhood change when the final determination of whether a dynamic update can occur. ----------------------*/ uint64_t APar_PaddingAmount(uint64_t target_amount, bool limit_by_prefs) { uint64_t padding_allowance = 0; if (limit_by_prefs) { if (pad_prefs.maximum_present_padding_size == 0) { return 0; } else if (target_amount >= pad_prefs.maximum_present_padding_size) { padding_allowance = pad_prefs.maximum_present_padding_size; } else if (target_amount <= pad_prefs.minimum_required_padding_size) { padding_allowance = pad_prefs.default_padding_size; } else { padding_allowance = target_amount; } } else { padding_allowance = target_amount; } if (padding_allowance < 8) return 0; if (!alter_original) return pad_prefs.default_padding_size; return padding_allowance; } /*---------------------- APar_DetermineDynamicUpdate Find where the first 'mdat' atom will eventually wind up in any new file. If this movement is within the bounds of padding prefs, allow a dynamic update. Adjust the amount of padding to accommodate the difference in positions (persuant to padding prefs). Otherwise prevent it, and make sure the default amount of padding exists for the file for a full rewrite. ----------------------*/ void APar_DetermineDynamicUpdate() { if (!force_existing_hierarchy && moov_atom_was_mooved) { dynUpd.prevent_dynamic_update = true; return; } uint64_t mdat_pos = 0; // uint64_t moov_udta_pos = APar_QuickSumAtomicLengths(dynUpd.moov_udta_atom); // uint64_t moov_last_trak_pos = // APar_QuickSumAtomicLengths(dynUpd.last_trak_child_atom); uint64_t // moov_meta_pos = APar_QuickSumAtomicLengths(dynUpd.moov_meta_atom); uint64_t moov_pos = APar_QuickSumAtomicLengths(dynUpd.moov_atom); uint64_t root_meta_pos = APar_QuickSumAtomicLengths(dynUpd.file_meta_atom); if (root_meta_pos > 0 && root_meta_pos != dynUpd.file_meta_atom->AtomicStart) { } else if (moov_pos > 0 && moov_pos != dynUpd.moov_atom ->AtomicStart) { // this could reflect either a // root meta being moved, or moov // came after mdat & was rearranged dynUpd.initial_update_atom = &parsedAtoms[0]; } else if (moov_pos > 0) { dynUpd.initial_update_atom = dynUpd.moov_atom; } if (dynUpd.first_mdat_atom == NULL && alter_original) { // work this out later } else if (dynUpd.first_mdat_atom != NULL) { mdat_pos = APar_QuickSumAtomicLengths(dynUpd.first_mdat_atom); if (mdat_pos >= dynUpd.first_mdat_atom->AtomicStart) { uint64_t offset_increase = mdat_pos - dynUpd.first_mdat_atom->AtomicStart; if (offset_increase > dynUpd.padding_bytes) { if ((psp_brand || force_existing_hierarchy) && (dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV)) { dynUpd.updage_by_padding = true; } else { dynUpd.prevent_dynamic_update = true; } } else { uint64_t padding_remaining = dynUpd.padding_bytes - offset_increase; uint64_t padding_allowed = APar_PaddingAmount(padding_remaining, true); if (padding_remaining == padding_allowed) { if (alter_original) { dynUpd.updage_by_padding = true; } APar_AdjustPadding(padding_allowed); } else { dynUpd.updage_by_padding = false; APar_AdjustPadding(padding_allowed); } } } else { uint64_t padding_replenishment = dynUpd.first_mdat_atom->AtomicStart - mdat_pos; uint64_t padding_allowed = APar_PaddingAmount(padding_replenishment, true); if (padding_replenishment == padding_allowed) { if (alter_original) { dynUpd.updage_by_padding = true; } APar_AdjustPadding(padding_allowed); } else { dynUpd.updage_by_padding = false; APar_AdjustPadding(padding_allowed); } } } if (!dynUpd.updage_by_padding && dynUpd.padding_bytes < pad_prefs.default_padding_size) { APar_AdjustPadding( pad_prefs.default_padding_size); // if there is a full rewrite, add a // default amount of padding } APar_DetermineAtomLengths(); return; } /*---------------------- APar_ConsolidatePadding Work through all the atoms that were determined to be 'padding' in APar_FindPadding, sum up their lengths and remove them from the hieararchy. No bytes are added or removed from the file because the total length of the padding is reformed as a single consolidated 'free' atom. This free atom will either form after the iTunes handler or (probably) at level 1 (probably before mdat). When doing a dynamic update in situ, the length of this consolidated padding atom will change (later) if metadata is altered. ----------------------*/ void APar_ConsolidatePadding() { uint64_t bytes_o_padding = 0; if (dynUpd.consolidated_padding_insertion == 0) return; FreeAtomListing *padding_entry = dynUpd.first_padding_atom; while (true) { if (padding_entry == NULL) break; APar_EliminateAtom(padding_entry->free_atom->AtomicNumber, padding_entry->free_atom->NextAtomNumber); FreeAtomListing *next_entry = padding_entry->next_free_listing; free(padding_entry); padding_entry = next_entry; } bytes_o_padding = APar_PaddingAmount(dynUpd.padding_bytes, !alter_original); if (bytes_o_padding >= 8) { if (!(psp_brand || force_existing_hierarchy) && (dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV)) { APar_CreatePadding(bytes_o_padding); } } return; } /*---------------------- APar_FindPadding listing_purposes_only - controls whether to just track free/skip atoms, or actually consolidate This is called before all the lengths of all atoms gets redetermined. The atoms at this point are already moved via optimization, and so the location of where to place padding can be ascertained. It is not known howevever where the ultimate positions of these landmark atoms will be - which can change according to padding prefs (which gets applied to all the padding in APar_ConsolidatePadding(). Any free/skip atoms found to exist between 'moov' & the first 'mdat' atom will be be considered padding (excepting anything under 'stsd' which will be monolithing during *setting* of metadata tags). Each padding atom will be noted. A place where padding can reside will also be found - the presence of iTunes tags will ultimately deposit all accumulated padding in in the iTunes hierarchy. ----------------------*/ void APar_FindPadding(bool listing_purposes_only) { AtomicInfo *DynamicUpdateRangeStart = NULL; AtomicInfo *DynamicUpdateRangeEnd = dynUpd.first_mdat_atom; // could be NULL (a missing 'mdat' would mean // externally referenced media that would not be // self-contained) short eval_atom = 0; if (dynUpd.moov_atom != NULL) { DynamicUpdateRangeStart = &parsedAtoms[dynUpd.moov_atom->NextAtomNumber]; } if (DynamicUpdateRangeStart == NULL) return; eval_atom = DynamicUpdateRangeStart->AtomicNumber; while (true) { if (memcmp(parsedAtoms[eval_atom].AtomicName, "free", 4) == 0 || memcmp(parsedAtoms[eval_atom].AtomicName, "skip", 4) == 0) { if (dynUpd.first_padding_atom == NULL) { dynUpd.first_padding_atom = (FreeAtomListing *)malloc(sizeof(FreeAtomListing)); dynUpd.first_padding_atom->free_atom = &parsedAtoms[eval_atom]; dynUpd.first_padding_atom->next_free_listing = NULL; } else { FreeAtomListing *free_listing = (FreeAtomListing *)malloc(sizeof(FreeAtomListing)); free_listing->free_atom = &parsedAtoms[eval_atom]; free_listing->next_free_listing = NULL; if (dynUpd.first_padding_atom->next_free_listing == NULL) { dynUpd.first_padding_atom->next_free_listing = free_listing; } else if (dynUpd.last_padding_atom != NULL) { dynUpd.last_padding_atom->next_free_listing = free_listing; } dynUpd.last_padding_atom = free_listing; } dynUpd.padding_bytes += (parsedAtoms[eval_atom].AtomicLength == 1 ? parsedAtoms[eval_atom].AtomicLengthExtended : parsedAtoms[eval_atom].AtomicLength); } eval_atom = parsedAtoms[eval_atom].NextAtomNumber; if (eval_atom == 0) break; if (DynamicUpdateRangeEnd != NULL) { if (DynamicUpdateRangeEnd->AtomicNumber == eval_atom) break; } } // search for a suitable location where padding can accumulate if (dynUpd.moov_udta_atom != NULL) { if (dynUpd.iTunes_list_handler_atom == NULL) { dynUpd.iTunes_list_handler_atom = APar_FindAtom("moov.udta.meta.hdlr", false, VERSIONED_ATOM, 0); if (dynUpd.iTunes_list_handler_atom != NULL) { if (parsedAtoms[dynUpd.iTunes_list_handler_atom->NextAtomNumber] .AtomicLevel >= dynUpd.iTunes_list_handler_atom->AtomicLevel) { dynUpd.consolidated_padding_insertion = dynUpd.iTunes_list_handler_atom->AtomicNumber; } } } } if (dynUpd.consolidated_padding_insertion == 0) { if (dynUpd.first_mdat_atom != NULL && !(dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV)) { dynUpd.consolidated_padding_insertion = APar_FindPrecedingAtom(dynUpd.first_mdat_atom->AtomicNumber); } else { dynUpd.consolidated_padding_insertion = APar_FindLastAtom(); dynUpd.optimization_flags |= PADDING_AT_EOF; } } // if the place where padding will eventually wind up is just before a padding // atom, that padding atom will be erased by APar_ConsolidatePadding when its // AtomicNumber becomes -1; so work back through the hierarchy for the first // non-padding atom while (true) { if (memcmp(parsedAtoms[dynUpd.consolidated_padding_insertion].AtomicName, "free", 4) == 0 || memcmp(parsedAtoms[dynUpd.consolidated_padding_insertion].AtomicName, "skip", 4) == 0) { dynUpd.consolidated_padding_insertion = APar_FindPrecedingAtom(dynUpd.consolidated_padding_insertion); } else { break; } } return; } void APar_LocateAtomLandmarks() { short total_file_level_atoms = APar_ReturnChildrenAtoms(0, 0); short eval_atom = 0; dynUpd.optimization_flags = 0; dynUpd.padding_bytes = 0; // this *won't* get filled here; it is only tracked // for the purposes of dynamic updating dynUpd.consolidated_padding_insertion = 0; // this will eventually hold the point where to insert a new atom that // will accumulate padding dynUpd.last_trak_child_atom = NULL; dynUpd.moov_atom = NULL; dynUpd.moov_udta_atom = NULL; dynUpd.iTunes_list_handler_atom = NULL; // this *won't* get filled here; it is only tracked for the purposes // of dynamic updating dynUpd.moov_meta_atom = NULL; dynUpd.file_meta_atom = NULL; dynUpd.first_mdat_atom = NULL; dynUpd.first_movielevel_metadata_tagging_atom = NULL; dynUpd.initial_update_atom = NULL; dynUpd.first_otiose_freespace_atom = NULL; dynUpd.first_padding_atom = NULL; // this *won't* get filled here; it is only tracked for the purposes // of dynamic updating dynUpd.last_padding_atom = NULL; // this *won't* get filled here; it is only tracked for the purposes // of dynamic updating dynUpd.padding_store = NULL; // this *won't* get filled here; it gets filled // in APar_ConsolidatePadding dynUpd.padding_resevoir = NULL; // scan through all top level atoms; fragmented files won't be optimized for (uint8_t iii = 1; iii <= total_file_level_atoms; iii++) { eval_atom = APar_ReturnChildrenAtoms(0, iii); // fprintf(stdout, "file level children - %s\n", // parsedAtoms[eval_atom].AtomicName); if (memcmp(parsedAtoms[eval_atom].AtomicName, "moof", 4) == 0 || memcmp(parsedAtoms[eval_atom].AtomicName, "mfra", 4) == 0) { move_moov_atom = false; // moov reordering won't be occurring on fragmented files, but // it should have moov first anyway (QuickTime does at least) } if (memcmp(parsedAtoms[eval_atom].AtomicName, "mdat", 4) == 0) { if (dynUpd.first_mdat_atom == NULL) { dynUpd.first_mdat_atom = &parsedAtoms[eval_atom]; } } if (dynUpd.first_otiose_freespace_atom == NULL) { if ((memcmp(parsedAtoms[eval_atom].AtomicName, "free", 4) == 0 || memcmp(parsedAtoms[eval_atom].AtomicName, "skip", 4) == 0) && dynUpd.first_mdat_atom == NULL && dynUpd.moov_atom == NULL) { dynUpd.first_otiose_freespace_atom = &parsedAtoms[eval_atom]; // the scourge of libmp4v2 } } if (memcmp(parsedAtoms[eval_atom].AtomicName, "moov", 4) == 0) { dynUpd.moov_atom = &parsedAtoms[eval_atom]; if (dynUpd.first_mdat_atom != NULL) { dynUpd.optimization_flags |= MEDIADATA__PRECEDES__MOOV; // or mdat could be entirely missing as // well; check later } } if (memcmp(parsedAtoms[eval_atom].AtomicName, "meta", 4) == 0) { dynUpd.file_meta_atom = &parsedAtoms[eval_atom]; if (dynUpd.moov_atom != NULL) { dynUpd.optimization_flags |= ROOT_META__PRECEDES__MOOV; } // meta before moov would require more rewrite of the portion of the // file than I want to do } // but it wouldn't be all that difficult to accommodate rewriting // everything from ftyp down to the first mdat, but currently its limited // to last 'trak' child to mdat } return; } /*---------------------- APar_Optimize mdat_test_only - the only info desired (if true, when printing the tree) is to know whether mdat precedes moov (and nullifing the concept of padding) The visual: ftyp moov trak trak trak udta meta hdlr free (functions as padding store when there are iTunes tags present) ilst meta hdlr meta hdlr free (functions as padding store when there are *no* iTunes tags present) mdat ----------------------*/ void APar_Optimize(bool mdat_test_only) { short last_child_of_moov = 0; short eval_atom = 0; APar_LocateAtomLandmarks(); /* -----------move moov to precede any media data (mdat)--------- */ if (move_moov_atom && (dynUpd.first_mdat_atom != NULL && (dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV))) { // first_mdat_atom > 0 && moov_atom > 0 // && moov_atom > first_mdat_atom) { if (mdat_test_only) { moov_atom_was_mooved = true; // this is all the interesting info required // (during APar_PrintAtomicTree) APar_FindPadding(mdat_test_only); return; } else { APar_MoveAtom(dynUpd.moov_atom->AtomicNumber, dynUpd.first_mdat_atom->AtomicNumber); moov_atom_was_mooved = true; } } /* -----------move a file/root level 'meta' to follow 'moov', but precede any * media data(mdat)--------- */ if (dynUpd.file_meta_atom != NULL && (dynUpd.optimization_flags & ROOT_META__PRECEDES__MOOV)) { last_child_of_moov = APar_FindLastChild_of_ParentAtom(dynUpd.moov_atom->AtomicNumber); APar_MoveAtom(dynUpd.file_meta_atom->AtomicNumber, parsedAtoms[last_child_of_moov].NextAtomNumber); } /* -----------optiizing the layout of movie-level atoms--------- */ if (dynUpd.moov_atom != NULL) { // it can't be null, but just in case... uint8_t extra_atom_count = 0; AtomicInfo *last_trak_atom = NULL; short total_moov_child_atoms = APar_ReturnChildrenAtoms(dynUpd.moov_atom->AtomicNumber, 0); AtomicInfo **other_track_level_atom = (AtomicInfo **)malloc(total_moov_child_atoms * sizeof(AtomicInfo *)); for (uint8_t xi = 0; xi < total_moov_child_atoms; xi++) { other_track_level_atom[xi] = NULL; } for (uint8_t moov_i = 1; moov_i <= total_moov_child_atoms; moov_i++) { eval_atom = APar_ReturnChildrenAtoms(dynUpd.moov_atom->AtomicNumber, moov_i); if (memcmp(parsedAtoms[eval_atom].AtomicName, "udta", 4) == 0 && parsedAtoms[eval_atom].AtomicLevel == 2) { dynUpd.moov_udta_atom = &parsedAtoms[eval_atom]; if (dynUpd.first_movielevel_metadata_tagging_atom == NULL) dynUpd.first_movielevel_metadata_tagging_atom = &parsedAtoms[eval_atom]; } else if (memcmp(parsedAtoms[eval_atom].AtomicName, "meta", 4) == 0 && parsedAtoms[eval_atom].AtomicLevel == 2) { dynUpd.moov_meta_atom = &parsedAtoms[eval_atom]; if (dynUpd.first_movielevel_metadata_tagging_atom == NULL) dynUpd.first_movielevel_metadata_tagging_atom = &parsedAtoms[eval_atom]; } else if (memcmp(parsedAtoms[eval_atom].AtomicName, "trak", 4) == 0) { last_trak_atom = &parsedAtoms[eval_atom]; if (dynUpd.first_movielevel_metadata_tagging_atom != NULL) { if (dynUpd.moov_meta_atom != NULL) dynUpd.optimization_flags |= MOOV_META__PRECEDES__TRACKS; else if (dynUpd.moov_udta_atom != NULL) dynUpd.optimization_flags |= MOOV_UDTA__PRECEDES__TRACKS; } } else if (!(memcmp(parsedAtoms[eval_atom].AtomicName, "free", 4) == 0 || memcmp(parsedAtoms[eval_atom].AtomicName, "skip", 4) == 0)) { if (dynUpd.moov_meta_atom != NULL || dynUpd.moov_udta_atom != NULL) { other_track_level_atom[extra_atom_count] = &parsedAtoms[eval_atom]; // anything else gets noted because it // *follows* moov.meta or moov.udta and // needs to precede it extra_atom_count++; } } } if (last_trak_atom != NULL) { short last_child_of_last_track = APar_FindLastChild_of_ParentAtom(last_trak_atom->AtomicNumber); if (last_child_of_last_track > 0) { dynUpd.last_trak_child_atom = &parsedAtoms[last_child_of_last_track]; } } /* -----------moving extra movie-level atoms (![trak,free,skip,meta,udta]) * to precede the first metadata tagging hierarchy (moov.meta or * moov.udta)--------- */ if (extra_atom_count > 0 && dynUpd.first_movielevel_metadata_tagging_atom != NULL) { for (uint8_t xxi = 0; xxi < extra_atom_count; xxi++) { APar_MoveAtom( (*other_track_level_atom + xxi)->AtomicNumber, dynUpd.first_movielevel_metadata_tagging_atom->AtomicNumber); } } /* -----------moving udta or meta to follow the trak atom--------- */ if (dynUpd.optimization_flags & MOOV_META__PRECEDES__TRACKS) { if (last_child_of_moov == 0) last_child_of_moov = APar_FindLastChild_of_ParentAtom(dynUpd.moov_atom->AtomicNumber); APar_MoveAtom(dynUpd.moov_meta_atom->AtomicNumber, parsedAtoms[last_child_of_moov].NextAtomNumber); } else if (dynUpd.optimization_flags & MOOV_UDTA__PRECEDES__TRACKS) { if (last_child_of_moov == 0) last_child_of_moov = APar_FindLastChild_of_ParentAtom(dynUpd.moov_atom->AtomicNumber); APar_MoveAtom(dynUpd.moov_udta_atom->AtomicNumber, parsedAtoms[last_child_of_moov].NextAtomNumber); } free(other_track_level_atom); other_track_level_atom = NULL; } if (mdat_test_only) { APar_FindPadding(mdat_test_only); return; } /* -----------delete free/skip atoms that precede media data or meta * data--------- */ if (dynUpd.first_otiose_freespace_atom != NULL && !alter_original) { // as a courtesty, for a full rewrite, eliminate L1 // pre-mdat free/skip atoms AtomicInfo *free_space = dynUpd.first_otiose_freespace_atom; while (true) { if (free_space->AtomicLevel > 1) break; if (memcmp(free_space->AtomicName, "free", 4) != 0) break; // only get the consecutive 'free' space AtomicInfo *nextatom = &parsedAtoms[free_space->NextAtomNumber]; APar_EliminateAtom(free_space->AtomicNumber, free_space->NextAtomNumber); free_space = nextatom; } } return; } /////////////////////////////////////////////////////////////////////////////////////// // Determine Atom Length // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_DetermineNewFileLength Sum up level 1 atoms (excludes any extranous EOF null bytes that iTunes occasionally writes - or used to) ----------------------*/ void APar_DetermineNewFileLength() { new_file_size = 0; short thisAtomNumber = 0; while (true) { if (parsedAtoms[thisAtomNumber].AtomicLevel == 1) { if (parsedAtoms[thisAtomNumber].AtomicLengthExtended == 0) { // normal 32-bit number when AtomicLengthExtended == 0 (for // run-o-the-mill mdat & mdat.length=0) new_file_size += parsedAtoms[thisAtomNumber].AtomicLength; // used in progressbar } else { // pseudo 64-bit mdat length new_file_size += parsedAtoms[thisAtomNumber] .AtomicLengthExtended; // used in progressbar } if (parsedAtoms[thisAtomNumber].AtomicLength == 0) { new_file_size += file_size - parsedAtoms[thisAtomNumber] .AtomicStart; // used in progressbar; mdat.length = 1 } } if (parsedAtoms[thisAtomNumber].NextAtomNumber == 0) { break; } thisAtomNumber = parsedAtoms[thisAtomNumber].NextAtomNumber; } return; } /*---------------------- APar_DetermineAtomLengths Working backwards from the last atom in the tree, for each parent atom, add the lengths of all the children atoms. All atom lengths for all parent atoms are recalculated from the lenghts of children atoms directly under the parent. Even atoms in hieararchies that were not touched are recalculated. Accommodations are made for certain dual-state atoms that are parents & contain data/versioning - the other dual-state atoms do not need to be listed because they exist within the stsd hierarchy which is only parsed when viewing the tree (for setting tags, it remains monolithic). ----------------------*/ void APar_DetermineAtomLengths() { short rev_atom_loop = APar_FindLastAtom(); // fprintf(stdout, "Last atom is named %s, num:%i\n", // parsedAtoms[last_atom].AtomicName, parsedAtoms[last_atom].AtomicNumber); while (true) { short next_atom = 0; uint64_t atom_size = 0; short previous_atom = 0; // only gets used in testing for atom under stsd // fprintf(stdout, "current atom is named %s, num:%i\n", // parsedAtoms[rev_atom_loop].AtomicName, // parsedAtoms[rev_atom_loop].AtomicNumber); if (rev_atom_loop == 0) { break; // we seem to have hit the first atom } else { previous_atom = APar_FindPrecedingAtom(rev_atom_loop); } uint32_t _atom_ = UInt32FromBigEndian(parsedAtoms[rev_atom_loop].AtomicName); switch (_atom_) { // the enumerated atoms here are all of DUAL_STATE_ATOM type case 0x6D657461: //'meta' atom_size += 12; break; case 0x73747364: //'stsd' atom_size += 16; break; case 0x64726566: //'dref' atom_size += 16; break; case 0x69696E66: //'iinf' atom_size += 14; break; // accommodate parsing of atoms under stsd when required case 0x6D703473: { // mp4s if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM && deep_atom_scan) { atom_size += 16; } else { atom_size += 8; } break; } case 0x73727470: // srtp case 0x72747020: { //'rtp ' if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM && deep_atom_scan) { atom_size += 24; } else { atom_size += 8; } break; } case 0x616C6163: // alac case 0x6D703461: // mp4a case 0x73616D72: // samr case 0x73617762: // sawb case 0x73617770: // sawp case 0x73657663: // sevc case 0x73716370: // sqcp case 0x73736D76: // ssmv case 0x64726D73: { // drms if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM && deep_atom_scan) { atom_size += 36; } else { atom_size += 8; } break; } case 0x74783367: { // tx3g if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM && deep_atom_scan) { atom_size += 46; } else { atom_size += 8; } break; } case 0x6D6A7032: // mjp2 case 0x6D703476: // mp4v case 0x61766331: // avc1 case 0x6A706567: // jpeg case 0x73323633: // s263 case 0x64726D69: { // drmi if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM && deep_atom_scan) { atom_size += 86; } else { atom_size += 8; } break; } default: if (parsedAtoms[rev_atom_loop].AtomicLevel == 7 && parsedAtoms[rev_atom_loop].AtomicContainerState == DUAL_STATE_ATOM) { atom_size += parsedAtoms[rev_atom_loop].AtomicLength; } else { atom_size += 8; // all atoms have *at least* 4bytes length & 4 bytes // name } break; } if (parsedAtoms[rev_atom_loop].NextAtomNumber != 0) { next_atom = parsedAtoms[rev_atom_loop].NextAtomNumber; } while (parsedAtoms[next_atom].AtomicLevel > parsedAtoms[rev_atom_loop].AtomicLevel) { // eval all child atoms.... // fprintf(stdout, "\ttest child atom %s, level:%i (sum %" PRIu64 ")\n", // parsedAtoms[next_atom].AtomicName, parsedAtoms[next_atom].AtomicLevel, // atom_size); if (parsedAtoms[rev_atom_loop].AtomicLevel == (parsedAtoms[next_atom].AtomicLevel - 1)) { // only child atoms 1 level down atom_size += parsedAtoms[next_atom].AtomicLength; // fprintf(stdout, "\t\teval child atom %s, level:%i (sum %" PRIu64 // ")\n", parsedAtoms[next_atom].AtomicName, // parsedAtoms[next_atom].AtomicLevel, atom_size); fprintf(stdout, // "\t\teval %s's child atom %s, level:%i (sum %" PRIu64 ", added %" // PRIu64 ")\n", parsedAtoms[previous_atom].AtomicName, // parsedAtoms[next_atom].AtomicName, // parsedAtoms[next_atom].AtomicLevel, atom_size, // parsedAtoms[next_atom].AtomicLength); } else if (parsedAtoms[next_atom].AtomicLevel < parsedAtoms[rev_atom_loop].AtomicLevel) { break; } next_atom = parsedAtoms[next_atom].NextAtomNumber; // increment to eval next atom parsedAtoms[rev_atom_loop].AtomicLength = atom_size; } if (_atom_ == 0x75647461 && parsedAtoms[rev_atom_loop].AtomicLevel > parsedAtoms[parsedAtoms[rev_atom_loop].NextAtomNumber] .AtomicLevel) { // udta with no child atoms; get here by erasing // the last asset in a 3gp file, and it won't // quite erase because udta thinks its the // former AtomicLength parsedAtoms[rev_atom_loop].AtomicLength = 8; } if (_atom_ == 0x6D657461 && parsedAtoms[rev_atom_loop].AtomicLevel != parsedAtoms[parsedAtoms[rev_atom_loop].NextAtomNumber].AtomicLevel - 1) { // meta with no child atoms; get here by erasing the last // existing uuid atom. parsedAtoms[rev_atom_loop].AtomicLength = 12; } if (_atom_ == 0x696C7374 && parsedAtoms[rev_atom_loop].AtomicLevel != parsedAtoms[parsedAtoms[rev_atom_loop].NextAtomNumber].AtomicLevel - 1) { // ilst with no child atoms; get here by erasing the last // piece of iTunes style metadata parsedAtoms[rev_atom_loop].AtomicLength = 8; } rev_atom_loop = APar_FindPrecedingAtom(parsedAtoms[rev_atom_loop].AtomicNumber); } APar_DetermineNewFileLength(); // APar_SimpleAtomPrintout(); // APar_PrintAtomicTree(); return; } /////////////////////////////////////////////////////////////////////////////////////// // Atom Writing Functions // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_ValidateAtoms A gaggle of tests go on here - to TRY to make sure that files are not corrupted. 1. because there is a limit to the number of atoms, test to make sure we haven't hit MAX_ATOMS (probably only likely on a 300MB fragmented file ever 2 secs) 2. test that the atom name is at least 4 letters long. So far, only quicktime atoms have NULLs in their names. 3. For files over 300k, make sure that no atom can present larger than the filesize (which would be bad); handy for when the file isn't parsed correctly 4. Test to make sure 'mdat' is at file-level. That is the only place it should ever be. 5. If its is a child atom that was set (and resides in memory), then its AtomicData should != NULL. 6. (A crude) Test to see if 'trak' atoms have only a 'udta' child. If setting a copyright notice on a track at index built with some compilers faux 'trak's are made 7. If the file shunk below 90% (after accounting for additions or removals), error out - something went awry. ----------------------*/ void APar_ValidateAtoms() { bool atom_name_with_4_characters = true; short iter = 0; uint64_t simple_tally = 0; uint8_t atom_ftyp_count = 0; uint16_t external_data = 0; // test1 if (atom_number > MAX_ATOMS) { fprintf(stderr, "AtomicParsley error: amount of atoms exceeds internal " "limit. Aborting.\n"); exit(1); } while (true) { // test2 // there are valid atom names that are 0x00000001 - but I haven't seen them // in MPEG-4 files, but they could show up, so this isn't a hard error if (strlen(parsedAtoms[iter].AtomicName) < 4 && parsedAtoms[iter].AtomicClassification != EXTENDED_ATOM) { atom_name_with_4_characters = false; } // test3 // test for atoms that are going to be greater than out current file size; // problem is we could be adding a 1MB pix to a 200k 3gp file; only fail for // a file > 300k file; otherwise there would have to be more checks (like // artwork present, but a zealous tagger could make moov.lengt > filzesize) if (parsedAtoms[iter].AtomicLength > file_size && file_size > 300000) { if (parsedAtoms[iter].AtomicData == NULL) { fprintf(stderr, "AtomicParsley error: an atom was detected that presents as " "larger than filesize. Aborting. %c\n", '\a'); fprintf(stderr, "atom %s is %" PRIu64 " bytes long which is greater than the filesize of %" PRIu64 "\n", parsedAtoms[iter].AtomicName, parsedAtoms[iter].AtomicLength, file_size); exit( 1); // its conceivable to repair such an off length by the // surrounding atoms constrained by file_size - just not anytime // soon; probly would catch a foobar2000 0.9 tagged file } } if (parsedAtoms[iter].AtomicLevel == 1) { if (parsedAtoms[iter].AtomicLength == 0 && strncmp(parsedAtoms[iter].AtomicName, "mdat", 4) == 0) { simple_tally = file_size - parsedAtoms[iter].AtomicStart; } else { simple_tally += parsedAtoms[iter].AtomicLength == 1 ? parsedAtoms[iter].AtomicLengthExtended : parsedAtoms[iter].AtomicLength; } } // test4 if (strncmp(parsedAtoms[iter].AtomicName, "mdat", 4) == 0 && parsedAtoms[iter].AtomicLevel != 1) { fprintf(stderr, "AtomicParsley error: mdat atom was found not at file level. " "Aborting. %c\n", '\a'); exit(1); // the error which forced this was some bad atom length // redetermination; probably won't be fixed } // test5 if (parsedAtoms[iter].AtomicStart == 0 && parsedAtoms[iter].AtomicData == NULL && parsedAtoms[iter].AtomicNumber > 0 && parsedAtoms[iter].AtomicContainerState == CHILD_ATOM) { fprintf(stderr, "AtomicParsley error: a '%s' atom was rendered to NULL length. " "Aborting. %c\n", parsedAtoms[iter].AtomicName, '\a'); exit(1); // data was not written to AtomicData for this new atom. } // test6 if (memcmp(parsedAtoms[iter].AtomicName, "trak", 4) == 0 && parsedAtoms[iter + 1].NextAtomNumber != 0) { // prevent writing any malformed tracks if (!(memcmp(parsedAtoms[parsedAtoms[iter].NextAtomNumber].AtomicName, "tkhd", 4) == 0 || memcmp(parsedAtoms[parsedAtoms[iter].NextAtomNumber].AtomicName, "tref", 4) == 0 || memcmp(parsedAtoms[parsedAtoms[iter].NextAtomNumber].AtomicName, "mdia", 4) == 0 || memcmp(parsedAtoms[parsedAtoms[iter].NextAtomNumber].AtomicName, "edts", 4) == 0 || memcmp(parsedAtoms[parsedAtoms[iter].NextAtomNumber].AtomicName, "meta", 4) == 0 || memcmp(parsedAtoms[parsedAtoms[iter].NextAtomNumber].AtomicName, "tapt", 4) == 0)) { if (APar_ReturnChildrenAtoms(iter, 0) < 2) { // a 'trak' must contain 'tkhd' & 'mdia' at the very least fprintf(stderr, "AtomicParsley error: incorrect track structure containing " "atom %s. %c\n", parsedAtoms[parsedAtoms[iter].NextAtomNumber].AtomicName, '\a'); exit(1); } } } if (memcmp(parsedAtoms[iter].AtomicName, "ftyp", 4) == 0) { atom_ftyp_count++; } if ((memcmp(parsedAtoms[iter].AtomicName, "stco", 4) == 0 || memcmp(parsedAtoms[iter].AtomicName, "co64", 4) == 0) && parsedAtoms[iter].ancillary_data != 1) { external_data++; } iter = parsedAtoms[iter].NextAtomNumber; if (iter == 0) { break; } } // test7 double perdiff = (double)((double)(simple_tally)*100.0 / (double)(file_size - removed_bytes_tally)); int percentage_difference = (int)lroundf(perdiff); if (percentage_difference < 90 && file_size > 300000) { // only kick in when files are over 300k & 90% of the size fprintf(stderr, "AtomicParsley error: total existing atoms present as larger than " "filesize. Aborting. %c\n", '\a'); // APar_PrintAtomicTree(); fprintf(stdout, "%i %" PRIu64 "\n", percentage_difference, simple_tally); exit(1); } if (atom_ftyp_count != 1) { fprintf(stdout, "AtomicParsley error: unresolved looping of atoms. Aborting. %c\n", '\a'); exit(1); } if (!atom_name_with_4_characters) { fprintf(stdout, "AtomicParsley warning: atom(s) were detected with " "atypical names containing NULLs\n"); } if (external_data > 0) { fprintf(stdout, "AtomicParsley warning: externally referenced data found."); } return; } void APar_DeriveNewPath(const char *filePath, char *temp_path, int output_type, const char *file_kind, const char *forced_suffix, bool random_filename = true) { const char *suffix = NULL; const char *file_name = NULL; size_t file_name_len = 0; bool relative_path = false; if (forced_suffix == NULL) { suffix = strrchr(filePath, '.'); } else { suffix = forced_suffix; } size_t filepath_len = strlen(filePath); size_t base_len = filepath_len - strlen(suffix); if (output_type >= 0) { memcpy(temp_path, filePath, base_len); memcpy(temp_path + base_len, file_kind, strlen(file_kind)); } else if (output_type == -1) { // make the output file invisible by prefacing // the filename with '.' #if defined(_WIN32) && !defined(__CYGWIN__) memcpy(temp_path, filePath, base_len); memcpy(temp_path + base_len, file_kind, strlen(file_kind)); #else file_name = strrchr(filePath, '/'); if (file_name != NULL) { file_name_len = strlen(file_name); memcpy(temp_path, filePath, filepath_len - file_name_len + 1); } else { if (getcwd(temp_path, MAXPATHLEN) == NULL) { printf("Error getting working directory: %s\n", strerror(errno)); exit(1); } file_name = (char *)filePath; file_name_len = strlen(file_name); memcpy(temp_path + strlen(temp_path), "/", 1); relative_path = true; } memcpy(temp_path + strlen(temp_path), ".", 1); memcpy(temp_path + strlen(temp_path), (relative_path ? file_name : file_name + 1), file_name_len - strlen(suffix) - 1); memcpy(temp_path + strlen(temp_path), file_kind, strlen(file_kind)); #endif } if (random_filename) { char randstring[6]; srand((int)time(NULL)); // Seeds rand() int randNum = rand() % 100000; sprintf(randstring, "%i", randNum); memcpy(temp_path + strlen(temp_path), randstring, strlen(randstring)); } if (forced_suffix_type == FORCE_M4B_TYPE) { memcpy(temp_path + strlen(temp_path), ".m4b", 4); } else { memcpy(temp_path + strlen(temp_path), suffix, strlen(suffix)); } return; } void APar_MetadataFileDump(const char *ISObasemediafile) { char *dump_file_name = (char *)malloc(sizeof(char) * (strlen(ISObasemediafile) + 12 + 1)); memset(dump_file_name, 0, sizeof(char) * (strlen(ISObasemediafile) + 12 + 1)); FILE *dump_file; AtomicInfo *userdata_atom = APar_FindAtom("moov.udta", false, SIMPLE_ATOM, 0); // make sure that the atom really exists if (userdata_atom != NULL) { char *dump_buffer = (char *)malloc(sizeof(char) * userdata_atom->AtomicLength + 1); memset(dump_buffer, 0, sizeof(char) * userdata_atom->AtomicLength + 1); APar_DeriveNewPath(ISObasemediafile, dump_file_name, 1, "-dump-", ".raw"); dump_file = APar_OpenFile(dump_file_name, "wb"); if (dump_file != NULL) { // body of atom writing here APar_readX(dump_buffer, source_file, userdata_atom->AtomicStart, (size_t)userdata_atom->AtomicLength); fwrite(dump_buffer, (size_t)userdata_atom->AtomicLength, 1, dump_file); fclose(dump_file); fprintf(stdout, " Metadata dumped to %s\n", dump_file_name); } free(dump_buffer); dump_buffer = NULL; } else { fprintf(stdout, "AtomicParsley error: no moov.udta atom was found to dump " "out to file.\n"); } return; } void APar_UpdateModTime(AtomicInfo *container_header_atom) { container_header_atom->AtomicData = (char *)calloc( 1, sizeof(char) * (size_t)container_header_atom->AtomicLength); APar_readX(container_header_atom->AtomicData, source_file, container_header_atom->AtomicStart + 12, container_header_atom->AtomicLength - 12); uint32_t current_time = APar_get_mpeg4_time(); if ((container_header_atom->AtomicVerFlags & 0xFFFFFF) == 1) { UInt64_TO_String8(current_time, container_header_atom->AtomicData + 8); } else { UInt32_TO_String4(current_time, container_header_atom->AtomicData + 4); } return; } void APar_ShellProgressBar(uint64_t bytes_written) { if (dynUpd.updage_by_padding) { return; } static int update_count = 0; if (update_count++ < 5) { return; } update_count = 0; double dispprog = (double)bytes_written / (double)new_file_size * max_display_width; int display_progress = (int)lroundf(dispprog); double percomp = 100.0 * (double)bytes_written / (double)new_file_size; int percentage_complete = (int)lroundf(percomp); char *p = file_progress_buffer; strcpy(p, " Progress: "); p += strlen(p); memset(p, '=', display_progress); p += display_progress; sprintf(p, ">%3d%% ", percentage_complete); p += strlen(p); memset(p, '-', max_display_width - display_progress); p += max_display_width - display_progress; p[0] = '|'; p[1] = '\0'; fprintf(stdout, "%s\r", file_progress_buffer); fflush(stdout); } void APar_MergeTempFile(FILE *dest_file, FILE *src_file, uint64_t src_file_size, uint64_t dest_position, char *&buffer) { uint64_t file_pos = 0; while (file_pos <= src_file_size) { if (file_pos + max_buffer <= src_file_size) { APar_readX(buffer, src_file, file_pos, max_buffer); fseeko(dest_file, dest_position + file_pos, SEEK_SET); fwrite(buffer, max_buffer, 1, dest_file); file_pos += max_buffer; } else { APar_readX(buffer, src_file, file_pos, src_file_size - file_pos); // fprintf(stdout, "buff starts with %s\n", buffer+4); fseeko(dest_file, dest_position + file_pos, SEEK_SET); fwrite(buffer, src_file_size - file_pos, 1, dest_file); file_pos += src_file_size - file_pos; break; } } if (dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV) { #if defined(_WIN32) && !defined(__CYGWIN__) fflush(dest_file); SetEndOfFile((HANDLE)_get_osfhandle(_fileno(dest_file))); #else if (ftruncate(fileno(dest_file), src_file_size + dest_position) == -1) { perror("Failed to truncate file: "); exit(1); } #endif } return; } #ifdef __linux__ /* use kernel provided zero-copy interface to improve throughput * around the data passthru portions of our operation; no sense * copying multiple GB of data around in memory if we can avoid it */ uint64_t splice_copy(int sfd, int ofd, uint64_t block_size, uint64_t src_offset, uint64_t dest_offset, uint64_t tally) { int pfd[2]; int res; uint64_t lim = LONG_MAX; loff_t spos = src_offset; loff_t dpos = dest_offset; long didread; long didwrite; uint64_t bytes_written = 0; res = pipe(pfd); if (res < 0) { return 0; } while (block_size) { long toread = std::min(block_size, lim); /* splice source data into pipe. * This will typically be 64k at a time */ didread = splice(sfd, &spos, pfd[1], NULL, toread, SPLICE_F_MORE | SPLICE_F_MOVE); if (didread <= 0) { if (errno == EINVAL || errno == ENOSYS) { /* splice is not supported by source */ goto exit; } fprintf(stderr, "splice(read): %ld of %lu (%s)\n", didread, toread, strerror(errno)); break; } block_size -= didread; while (didread > 0) { /* splice from pipe into dest */ didwrite = splice( pfd[0], NULL, ofd, &dpos, didread, SPLICE_F_MORE | SPLICE_F_MOVE); if (didwrite <= 0) { if (errno == EINVAL || errno == ENOSYS) { /* splice is not supported by dest */ goto exit; } fprintf(stderr, "splice(write): %ld of %lu (%s)\n", didwrite, didread, strerror(errno)); break; } bytes_written += didwrite; didread -= didwrite; } APar_ShellProgressBar(tally + bytes_written); } exit: close(pfd[0]); close(pfd[1]); return bytes_written; } #endif uint64_t block_copy(FILE *source_file, FILE *out_file, char *&buffer, uint64_t tally, uint64_t block_size, uint64_t src_offset, uint64_t dest_offset) { uint64_t toread = block_size; uint64_t bytes_written = 0; size_t didread; size_t didwrite; #ifdef __linux__ if (block_size > 65536) { fflush(out_file); bytes_written = splice_copy(fileno(source_file), fileno(out_file), block_size, src_offset, dest_offset, tally); if (bytes_written != 0) { return bytes_written; } } #endif fseeko(source_file, src_offset, SEEK_SET); fseeko(out_file, dest_offset, SEEK_SET); while (toread) { char *bpos; didread = fread(buffer, 1, std::min(max_buffer, toread), source_file); if (didread == 0) { fprintf(stderr, "read: eof=%d err=%d %s\n", feof(source_file), ferror(source_file), strerror(errno)); break; } toread -= didread; bpos = buffer; while (didread) { didwrite = fwrite(bpos, 1, didread, out_file); didread -= didwrite; bpos += didwrite; bytes_written += didwrite; APar_ShellProgressBar(tally + bytes_written); } } return bytes_written; } uint64_t APar_WriteAtomically(FILE *source_file, FILE *temp_file, bool from_file, char *&buffer, uint64_t bytes_written_tally, short this_atom) { uint64_t bytes_written = 0; if (parsedAtoms[this_atom].AtomicLength > 1 && parsedAtoms[this_atom].AtomicLength < 8) { // prevents any spurious atoms from appearing return bytes_written; } // write the length of the atom first... taken from our tree in memory UInt32_TO_String4(parsedAtoms[this_atom].AtomicLength, twenty_byte_buffer); fseeko(temp_file, bytes_written_tally, SEEK_SET); fwrite(twenty_byte_buffer, 4, 1, temp_file); bytes_written += 4; // since we have already writen the length out to the file, it can be changed // now with impunity if (parsedAtoms[this_atom].AtomicLength == 0) { // the spec says if an atom has a length of 0, it extends to EOF parsedAtoms[this_atom].AtomicLength = file_size - parsedAtoms[this_atom].AtomicLength; } else if (parsedAtoms[this_atom].AtomicLength == 1) { // part of the pseudo 64-bit support parsedAtoms[this_atom].AtomicLength = parsedAtoms[this_atom].AtomicLengthExtended; } else if (parsedAtoms[this_atom].AtomicContainerState == DUAL_STATE_ATOM) { if (memcmp(parsedAtoms[this_atom].AtomicName, "dref", 4) == 0) { parsedAtoms[this_atom].AtomicLength = 16; } else if (memcmp(parsedAtoms[this_atom].AtomicName, "iinf", 4) == 0) { parsedAtoms[this_atom].AtomicLength = 14; } } if (deep_atom_scan && parsedAtoms[this_atom].AtomicContainerState == DUAL_STATE_ATOM) { uint32_t atom_val = UInt32FromBigEndian(parsedAtoms[this_atom].AtomicName); if (atom_val == 0x73747364) { // stsd parsedAtoms[this_atom].AtomicLength = 16; } else if (atom_val == 0x6D703473) { // mp4s parsedAtoms[this_atom].AtomicLength = 16; } else if (atom_val == 0x73727470) { // srtp parsedAtoms[this_atom].AtomicLength = 24; } else if (atom_val == 0x72747020 && parsedAtoms[this_atom].AtomicLevel == 7) { //'rtp ' parsedAtoms[this_atom].AtomicLength = 24; } else if (atom_val == 0x616C6163 && parsedAtoms[this_atom].AtomicLevel == 7) { // alac parsedAtoms[this_atom].AtomicLength = 36; } else if (atom_val == 0x6D703461) { // mp4a parsedAtoms[this_atom].AtomicLength = 36; } else if (atom_val == 0x73616D72) { // samr parsedAtoms[this_atom].AtomicLength = 36; } else if (atom_val == 0x73617762) { // sawb parsedAtoms[this_atom].AtomicLength = 36; } else if (atom_val == 0x73617770) { // sawp parsedAtoms[this_atom].AtomicLength = 36; } else if (atom_val == 0x73657663) { // sevc parsedAtoms[this_atom].AtomicLength = 36; } else if (atom_val == 0x73716370) { // sqcp parsedAtoms[this_atom].AtomicLength = 36; } else if (atom_val == 0x73736D76) { // ssmv parsedAtoms[this_atom].AtomicLength = 36; } else if (atom_val == 0x74783367) { // tx3g parsedAtoms[this_atom].AtomicLength = 46; } else if (atom_val == 0x6D6A7032) { // mjp2 parsedAtoms[this_atom].AtomicLength = 86; } else if (atom_val == 0x6D703476) { // mp4v parsedAtoms[this_atom].AtomicLength = 86; } else if (atom_val == 0x61766331) { // avc1 parsedAtoms[this_atom].AtomicLength = 86; } else if (atom_val == 0x6A706567) { // jpeg parsedAtoms[this_atom].AtomicLength = 86; } else if (atom_val == 0x73323633) { // s263 parsedAtoms[this_atom].AtomicLength = 86; } } if (from_file) { // here we read in the original atom into the buffer. If the length is // greater than our buffer length, we loop, reading in chunks of the // atom's data into the buffer, and immediately write it out, reusing // the buffer. // bytes_written += block_copy(source_file, temp_file, buffer, bytes_written_tally, parsedAtoms[this_atom].AtomicLength - bytes_written, bytes_written + parsedAtoms[this_atom].AtomicStart, bytes_written_tally + bytes_written); return bytes_written; } else { // we are going to be writing not from the file, but directly from // the tree (in memory). uint64_t atom_name_len = 4; // fprintf(stdout, "Writing atom %s from memory %u\n", // parsedAtoms[this_atom].AtomicName, // parsedAtoms[this_atom].AtomicClassification); fseeko(temp_file, bytes_written_tally + bytes_written, SEEK_SET); if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM) { fwrite("uuid", 4, 1, temp_file); atom_name_len = 16; // total of 20 bytes for a uuid atom // fprintf(stdout, "%" PRIu64 "\n", parsedAtoms[this_atom].AtomicLength); if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM && parsedAtoms[this_atom].uuid_style == UUID_OTHER) bytes_written += 4; } fwrite(parsedAtoms[this_atom].AtomicName, atom_name_len, 1, temp_file); bytes_written += atom_name_len; if (parsedAtoms[this_atom].AtomicClassification == VERSIONED_ATOM || parsedAtoms[this_atom].AtomicClassification == PACKED_LANG_ATOM) { UInt32_TO_String4(parsedAtoms[this_atom].AtomicVerFlags, twenty_byte_buffer); fwrite(twenty_byte_buffer, 4, 1, temp_file); bytes_written += 4; } uint64_t atom_data_size = 0; switch (parsedAtoms[this_atom].AtomicContainerState) { case PARENT_ATOM: case SIMPLE_PARENT_ATOM: { atom_data_size = 0; break; } case DUAL_STATE_ATOM: { switch (UInt32FromBigEndian(parsedAtoms[this_atom].AtomicName)) { case 0x6D657461: { // meta break; } case 0x73747364: { // stsd atom_data_size = parsedAtoms[this_atom].AtomicLength - 12; break; } case 0x73636869: { // schi (code only executes when deep_atom_scan = true; // otherwise schi is contained by the // monolithic/unparsed 'stsd') atom_data_size = parsedAtoms[this_atom].AtomicLength - 12; } } break; } case UNKNOWN_ATOM_TYPE: case CHILD_ATOM: { if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM && parsedAtoms[this_atom].uuid_style == UUID_AP_SHA1_NAMESPACE) { // 4bytes length, 4 bytes 'uuid', 4bytes name, 4bytes NULL (AP writes // its own uuid atoms - not those copied - iTunes style with atom // versioning) atom_data_size = parsedAtoms[this_atom].AtomicLength - (16 + 12); // 16 uuid; 16 = 4bytes * ('uuid', // ap_uuid_name, verflag, 4 NULL bytes) } else if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM && parsedAtoms[this_atom].uuid_style != UUID_DEPRECATED_FORM) { atom_data_size = parsedAtoms[this_atom].AtomicLength - (16 + 8); } else if (parsedAtoms[this_atom].AtomicClassification == VERSIONED_ATOM || parsedAtoms[this_atom].AtomicClassification == PACKED_LANG_ATOM) { // 4bytes legnth, 4bytes name, 4bytes flag&versioning (language would be // 2 bytes, but because its in different places, it gets stored as data) atom_data_size = parsedAtoms[this_atom].AtomicLength - 12; } else { // just 4bytes length, 4bytes name and then whatever data atom_data_size = parsedAtoms[this_atom].AtomicLength - 8; } break; } } if (parsedAtoms[this_atom].AtomicClassification == EXTENDED_ATOM && parsedAtoms[this_atom].uuid_style == UUID_AP_SHA1_NAMESPACE) { // AP writes uuid atoms much like iTunes style metadata; with // version/flags to connote what type of data is being carried // 4bytes atom length, 4 bytes 'uuid', 16bytes uuidv5, 4bytes name of uuid // in AP namespace, 4bytes versioning, 4bytes NULL, Xbytes data fwrite(parsedAtoms[this_atom].uuid_ap_atomname, 4, 1, temp_file); bytes_written += 4; UInt32_TO_String4(parsedAtoms[this_atom].AtomicVerFlags, twenty_byte_buffer); fwrite(twenty_byte_buffer, 4, 1, temp_file); bytes_written += 4; } if (atom_data_size > 0) { fwrite(parsedAtoms[this_atom].AtomicData, atom_data_size, 1, temp_file); bytes_written += atom_data_size; APar_ShellProgressBar(bytes_written_tally + bytes_written); } } return bytes_written; } /*---------------------- APar_copy_gapless_padding mp4file - destination file last_atom_pos - the last byte in the destination file that is contained by any atom (in parsedAtoms[] array) buffer - a buffer that will be used to set & write out from the NULLs used in gapless padding Add the discovered amount of already present gapless void padding at the end of the file (which is *not* contained by any atom at all) back into the destination file. Update: it would seem that this gapless void padding at the end of the file is not critical to gapless playback. In my 1 test of the thing, it seemed to work regardless of whether this NULL space was present or not, 'pgap' seemed to work. But, since Apple put it in for some reason, it will be left there unless explicity directed not to (via AP_PADDING). Although tying ordinary padding to this gapless padding may reduce flexibility - the assumption is that someone interested in squeezing out wasted space would want to eliminate this wasted space too (and so far, it does seem wasted). NOTE: Apple seems not to have seen this portion of the ISO 14496-12 Annex A, section A.2, para 6: "All the data within a conforming file is encapsulated in boxes (called atoms in predecessors of this file format). There is no data outside the box structure." And yet, Apple (donators of the file format) has caused iTunes to create non-conforming files with iTunes 7.x because of this NULL data outside of any box/atom structure. ----------------------*/ void APar_copy_gapless_padding(FILE *mp4file, uint64_t last_atom_pos, char *buffer) { uint64_t gapless_padding_bytes_written = 0; while (gapless_padding_bytes_written < gapless_void_padding) { if (gapless_padding_bytes_written + max_buffer <= gapless_void_padding) { memset(buffer, 0, max_buffer); fseeko(mp4file, last_atom_pos + gapless_padding_bytes_written, SEEK_SET); fwrite(buffer, max_buffer, 1, mp4file); gapless_padding_bytes_written += max_buffer; } else { // less then 512k of gapless padding (here's hoping we get here // always) memset(buffer, 0, gapless_void_padding - gapless_padding_bytes_written); fseeko(mp4file, last_atom_pos + gapless_padding_bytes_written, SEEK_SET); fwrite(buffer, gapless_void_padding - gapless_padding_bytes_written, 1, mp4file); gapless_padding_bytes_written += gapless_void_padding - gapless_padding_bytes_written; break; } } } void APar_WriteFile(const char *ISObasemediafile, const char *outfile, bool rewrite_original) { char *temp_file_name = (char *)calloc(1, sizeof(char) * 3500); char *file_buffer = (char *)calloc(1, sizeof(char) * max_buffer + 1); FILE *temp_file; uint64_t temp_file_bytes_written = 0; short thisAtomNumber = 0; char *originating_file = NULL; bool free_modified_name = false; APar_RenderAllID32Atoms(); if (!(psp_brand || force_existing_hierarchy)) { APar_Optimize(false); } else { APar_LocateAtomLandmarks(); } APar_FindPadding(false); APar_ConsolidatePadding(); APar_DetermineAtomLengths(); if (!complete_free_space_erasure) { APar_DetermineDynamicUpdate(); } if (!rewrite_original || dynUpd.prevent_dynamic_update) { dynUpd.updage_by_padding = false; } APar_ValidateAtoms(); // whatever atoms/space comes before mdat has to be added/removed before this // point, or chunk offsets (in stco, co64, tfhd) won't be properly determined uint64_t mdat_position = APar_DetermineMediaData_AtomPosition(); if (dynUpd.updage_by_padding) { APar_DeriveNewPath(ISObasemediafile, temp_file_name, 0, "-data-", NULL); // APar_DeriveNewPath(ISObasemediafile, // temp_file_name, -1, "-data-", NULL); temp_file = APar_OpenFile(temp_file_name, "wb"); #if defined(_WIN32) && !defined(__CYGWIN__) char *invisi_command = (char *)malloc(sizeof(char) * 2 * MAXPATHLEN); sprintf(invisi_command, "ATTRIB +S +H \"%s\"", temp_file_name); if (IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { wchar_t *invisi_command_long = Convert_multibyteUTF8_to_wchar(invisi_command); _wsystem(invisi_command_long); free(invisi_command_long); invisi_command_long = NULL; } else { system(invisi_command); } free(invisi_command); invisi_command = NULL; #endif } else if (!outfile) { APar_DeriveNewPath(ISObasemediafile, temp_file_name, 0, "-temp-", NULL); temp_file = APar_OpenFile(temp_file_name, "wb"); #if defined(__APPLE__) APar_SupplySelectiveTypeCreatorCodes( ISObasemediafile, temp_file_name, forced_suffix_type); // provide type/creator codes for ".mp4" for // randomly named temp files #endif } else { // case-sensitive compare means "The.m4a" is different from "THe.m4a"; on // certiain Mac OS X filesystems a case-preservative but case-insensitive FS // exists & AP probably will have a problem there. Output to a uniquely // named file as I'm not going to poll the OS for the type of FS employed on // the target drive. if (strcmp(ISObasemediafile, outfile) == 0) { // er, nice try but you were trying to ouput to the exactly named file of // the original. Y'all ain't so slick APar_DeriveNewPath(ISObasemediafile, temp_file_name, 0, "-temp-", NULL); temp_file = APar_OpenFile(temp_file_name, "wb"); #if defined(__APPLE__) APar_SupplySelectiveTypeCreatorCodes( ISObasemediafile, temp_file_name, forced_suffix_type); // provide type/creator codes for ".mp4" for a // fall-through randomly named temp files #endif } else { temp_file = APar_OpenFile(outfile, "wb"); #if defined(__APPLE__) APar_SupplySelectiveTypeCreatorCodes( ISObasemediafile, outfile, forced_suffix_type); // provide type/creator codes for ".mp4" for a // user-defined output file #endif } } if (temp_file != NULL) { // body of atom writing here if (dynUpd.updage_by_padding) { thisAtomNumber = dynUpd.initial_update_atom->AtomicNumber; fprintf(stdout, "\n Updating metadata... "); } else { fprintf(stdout, "\n Started writing to %s.\n", outfile ? outfile : "temp file"); } while (true) { AtomicInfo *thisAtom = &parsedAtoms[thisAtomNumber]; if (thisAtom->AtomicNumber == -1) break; // the loop where the critical determination is made if (memcmp(thisAtom->AtomicName, "mdat", 4) == 0 && dynUpd.updage_by_padding) break; if (thisAtom->ancillary_data == 0x666C6167) { //'flag' APar_UpdateModTime(thisAtom); } if (memcmp(thisAtom->AtomicName, "stco", 4) == 0) { bool readjusted_stco = APar_Readjust_STCO_atom(mdat_position, thisAtomNumber); temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, !readjusted_stco, file_buffer, temp_file_bytes_written, thisAtomNumber); } else if (memcmp(thisAtom->AtomicName, "co64", 4) == 0) { bool readjusted_co64 = APar_Readjust_CO64_atom(mdat_position, thisAtomNumber); temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, !readjusted_co64, file_buffer, temp_file_bytes_written, thisAtomNumber); } else if (memcmp(thisAtom->AtomicName, "tfhd", 4) == 0) { bool readjusted_tfhd = APar_Readjust_TFHD_fragment_atom(mdat_position, thisAtomNumber); temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, !readjusted_tfhd, file_buffer, temp_file_bytes_written, thisAtomNumber); } else if (memcmp(thisAtom->AtomicName, "iloc", 4) == 0) { bool readjusted_iloc = APar_Readjust_iloc_atom(thisAtomNumber); temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, !readjusted_iloc, file_buffer, temp_file_bytes_written, thisAtomNumber); } else if (thisAtom->AtomicData != NULL || memcmp(thisAtom->AtomicName, "meta", 4) == 0) { temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, false, file_buffer, temp_file_bytes_written, thisAtomNumber); } else { // write out parent atoms (the standard kind that are only offset & name // from the tree in memory (total: 8bytes) if (thisAtom->AtomicContainerState <= SIMPLE_PARENT_ATOM) { temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, false, file_buffer, temp_file_bytes_written, thisAtomNumber); // or its a child (they invariably contain some sort of data. } else { temp_file_bytes_written += APar_WriteAtomically(source_file, temp_file, true, file_buffer, temp_file_bytes_written, thisAtomNumber); } } if (thisAtom->NextAtomNumber == 0) { // if (parsedAtoms[thisAtomNumber].NextAtomNumber == 0) { // fprintf(stdout, "The loop is buh-rokin\n"); break; } // prevent any looping back to atoms already written thisAtom->AtomicNumber = -1; thisAtomNumber = thisAtom->NextAtomNumber; } if (!dynUpd.updage_by_padding) { if (gapless_void_padding > 0 && pad_prefs.default_padding_size > 0) { // only when some sort of padding is wanted will the gapless // null padding be copied APar_copy_gapless_padding( temp_file, temp_file_bytes_written, file_buffer); } fprintf(stdout, "\n Finished writing to %s.\n", outfile ? outfile : "temp file"); fclose(temp_file); } } else { fprintf(stdout, "AtomicParsley error: an error occurred while trying to " "create a temp file.\n"); exit(1); } if (dynUpd.updage_by_padding && rewrite_original) { fclose(temp_file); uint64_t metadata_len = findFileSize(temp_file_name); temp_file = APar_OpenFile(temp_file_name, "rb"); fclose(source_file); source_file = APar_OpenFile(ISObasemediafile, "r+b"); if (source_file == NULL) { fclose(temp_file); remove(temp_file_name); fprintf(stdout, "AtomicParsley error: the original file was no longer " "found.\nExiting.\n"); exit(1); } else if (!(dynUpd.optimization_flags & MEDIADATA__PRECEDES__MOOV) && metadata_len != (dynUpd.first_mdat_atom->AtomicStart - dynUpd.initial_update_atom->AtomicStart)) { fclose(temp_file); remove(temp_file_name); fprintf(stdout, "AtomicParsley error: the insufficient space to retag the source " "file (%" PRIu64 "!=%" PRIu64 ").\nExiting.\n", metadata_len, dynUpd.first_mdat_atom->AtomicStart - dynUpd.initial_update_atom->AtomicStart); exit(1); } APar_MergeTempFile(source_file, temp_file, temp_file_bytes_written, dynUpd.initial_update_atom->AtomicStart, file_buffer); fclose(source_file); fclose(temp_file); remove(temp_file_name); } else if (rewrite_original && !outfile) { // disable overWrite when writing out to a specifically // named file; presumably the enumerated output file // was meant to be the final destination fclose(source_file); #if defined(_WIN32) && \ !defined(__CYGWIN__) /* native Windows requires removing the file first; \ rename() on POSIX does the removing automatically \ as needed */ if (IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { wchar_t *utf16_filepath = Convert_multibyteUTF8_to_wchar(ISObasemediafile); _wremove(utf16_filepath); free(utf16_filepath); utf16_filepath = NULL; } else { remove(ISObasemediafile); } #endif int err = 0; if (forced_suffix_type != NO_TYPE_FORCING) { originating_file = (char *)calloc(1, sizeof(char) * 3500); free_modified_name = true; if (forced_suffix_type == FORCE_M4B_TYPE) { // using --stik Audiobook with --overWrite will // change the original file's extension uint16_t filename_len = strlen(ISObasemediafile); const char *suffix = strrchr(ISObasemediafile, '.'); memcpy(originating_file, ISObasemediafile, filename_len + 1); memcpy(originating_file + (filename_len - strlen(suffix)), ".m4b", 5); } } else { originating_file = (char *)ISObasemediafile; } #if defined(_WIN32) && !defined(__CYGWIN__) if (IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { wchar_t *utf16_filepath = Convert_multibyteUTF8_to_wchar(originating_file); wchar_t *temp_utf16_filepath = Convert_multibyteUTF8_to_wchar(temp_file_name); err = _wrename(temp_utf16_filepath, utf16_filepath); free(utf16_filepath); free(temp_utf16_filepath); utf16_filepath = NULL; temp_utf16_filepath = NULL; } else #endif { err = rename(temp_file_name, originating_file); } if (err != 0) { switch (errno) { case ENAMETOOLONG: { fprintf(stdout, "Some or all of the orginal path was too long."); exit(-1); } case ENOENT: { fprintf(stdout, "Some part of the original path was missing."); exit(-1); } case EACCES: { fprintf(stdout, "Unable to write to a directory lacking write permission."); exit(-1); } case ENOSPC: { fprintf(stdout, "Out of space."); exit(-1); } } } } free(temp_file_name); if (free_modified_name) free(originating_file); temp_file_name = NULL; free(file_buffer); file_buffer = NULL; return; } // vim:ts=2:sw=2:noet: atomicparsley-20240608.083822.1ed9031/src/sha1.cpp000066400000000000000000000323571463107535600205420ustar00rootroot00000000000000/* sha1.cpp - Functions to compute SHA1 message digest of files or memory blocks according to the NIST specification FIPS-180-1. Copyright (C) 2000, 2001, 2003, 2004, 2005 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Written by Scott G. Miller Credits: Robert Klep -- Expansion function fix */ /* This file has been modified from the original found in http://www.gnu.org/software/coreutils/ coreutils-5.97 for use within AtomicParsley. Modifications are : endian detection change a cast for compiling under g++ file renaming eliminated SWAP in favor of swap32 & swap16 in util.h alignment macros (for msvc) */ #include "AtomicParsley.h" /* SWAP does an endian swap on architectures that are little-endian, as SHA1 needs some data in a big-endian form. */ /* #if defined (__ppc__) || defined (__ppc64__) # define SWAP(n) (n) #else # define SWAP(n) \ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) #endif */ #define BLOCKSIZE 4096 #if BLOCKSIZE % 64 != 0 #error "invalid BLOCKSIZE" #endif /* This array contains the bytes used to pad the buffer to the next 64-byte boundary. (RFC 1321, 3.1: Step 1) */ static const unsigned char fillbuf[64] = {0x80, 0 /* , 0, 0, ... */}; /* Takes a pointer to a 160 bit block of data (five 32 bit ints) and intializes it to the start constants of the SHA1 algorithm. This must be called before using hash in the call to sha1_hash. */ void sha1_init_ctx(struct sha1_ctx *ctx) { ctx->A = 0x67452301; ctx->B = 0xefcdab89; ctx->C = 0x98badcfe; ctx->D = 0x10325476; ctx->E = 0xc3d2e1f0; ctx->total[0] = ctx->total[1] = 0; ctx->buflen = 0; } /* Put result from CTX in first 20 bytes following RESBUF. The result must be in little endian byte order. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ void *sha1_read_ctx(const struct sha1_ctx *ctx, void *resbuf) { ((md5_uint32 *)resbuf)[0] = SWAP32(ctx->A); ((md5_uint32 *)resbuf)[1] = SWAP32(ctx->B); ((md5_uint32 *)resbuf)[2] = SWAP32(ctx->C); ((md5_uint32 *)resbuf)[3] = SWAP32(ctx->D); ((md5_uint32 *)resbuf)[4] = SWAP32(ctx->E); return resbuf; } /* Process the remaining bytes in the internal buffer and the usual prolog according to the standard and write the result to RESBUF. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ void *sha1_finish_ctx(struct sha1_ctx *ctx, void *resbuf) { /* Take yet unprocessed bytes into account. */ md5_uint32 bytes = ctx->buflen; size_t pad; /* Now count remaining bytes. */ ctx->total[0] += bytes; if (ctx->total[0] < bytes) ++ctx->total[1]; pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; memcpy(&ctx->buffer[bytes], fillbuf, pad); /* Put the 64-bit file length in *bits* at the end of the buffer. */ *(md5_uint32 *)&ctx->buffer[bytes + pad + 4] = SWAP32(ctx->total[0] << 3); *(md5_uint32 *)&ctx->buffer[bytes + pad] = SWAP32((ctx->total[1] << 3) | (ctx->total[0] >> 29)); /* Process last bytes. */ sha1_process_block(ctx->buffer, bytes + pad + 8, ctx); return sha1_read_ctx(ctx, resbuf); } /* Compute SHA1 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ int sha1_stream(FILE *stream, void *resblock) { struct sha1_ctx ctx; char buffer[BLOCKSIZE + 72]; size_t sum; /* Initialize the computation context. */ sha1_init_ctx(&ctx); /* Iterate over full file contents. */ while (1) { /* We read the file in blocks of BLOCKSIZE bytes. One call of the computation function processes the whole buffer so that with the next round of the loop another block can be read. */ size_t n; sum = 0; /* Read block. Take care for partial reads. */ while (1) { n = fread(buffer + sum, 1, BLOCKSIZE - sum, stream); sum += n; if (sum == BLOCKSIZE) break; if (n == 0) { /* Check for the error flag IFF N == 0, so that we don't exit the loop after a partial read due to e.g., EAGAIN or EWOULDBLOCK. */ if (ferror(stream)) return 1; goto process_partial_block; } /* We've read at least one byte, so ignore errors. But always check for EOF, since feof may be true even though N > 0. Otherwise, we could end up calling fread after EOF. */ if (feof(stream)) goto process_partial_block; } /* Process buffer with BLOCKSIZE bytes. Note that BLOCKSIZE % 64 == 0 */ sha1_process_block(buffer, BLOCKSIZE, &ctx); } process_partial_block:; /* Process any remaining bytes. */ if (sum > 0) sha1_process_bytes(buffer, sum, &ctx); /* Construct result in desired memory. */ sha1_finish_ctx(&ctx, resblock); return 0; } /* Compute MD5 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ void *sha1_buffer(const char *buffer, size_t len, void *resblock) { struct sha1_ctx ctx; /* Initialize the computation context. */ sha1_init_ctx(&ctx); /* Process whole buffer but last len % 64 bytes. */ sha1_process_bytes(buffer, len, &ctx); /* Put result in desired memory area. */ return sha1_finish_ctx(&ctx, resblock); } void sha1_process_bytes(const void *buffer, size_t len, struct sha1_ctx *ctx) { /* When we already have some bits in our internal buffer concatenate both inputs first. */ if (ctx->buflen != 0) { size_t left_over = ctx->buflen; size_t add = 128 - left_over > len ? len : 128 - left_over; memcpy(&ctx->buffer[left_over], buffer, add); ctx->buflen += add; if (ctx->buflen > 64) { sha1_process_block(ctx->buffer, ctx->buflen & ~63, ctx); ctx->buflen &= 63; /* The regions in the following copy operation cannot overlap. */ memcpy(ctx->buffer, &ctx->buffer[(left_over + add) & ~63], ctx->buflen); } buffer = (const char *)buffer + add; len -= add; } /* Process available complete blocks. */ if (len >= 64) { #if !_STRING_ARCH_unaligned #define alignof(type) \ offsetof( \ struct { \ char c; \ type x; \ }, \ x) #define UNALIGNED_P(p) \ (((size_t)p) % 4 != \ 0) //# define UNALIGNED_P(p) (((size_t) p) % alignof (md5_uint32) != 0) if (UNALIGNED_P(buffer)) while (len > 64) { sha1_process_block(memcpy(ctx->buffer, buffer, 64), 64, ctx); buffer = (const char *)buffer + 64; len -= 64; } else #endif { sha1_process_block(buffer, len & ~63, ctx); buffer = (const char *)buffer + (len & ~63); len &= 63; } } /* Move remaining bytes in internal buffer. */ if (len > 0) { size_t left_over = ctx->buflen; memcpy(&ctx->buffer[left_over], buffer, len); left_over += len; if (left_over >= 64) { sha1_process_block(ctx->buffer, 64, ctx); left_over -= 64; memcpy(ctx->buffer, &ctx->buffer[64], left_over); } ctx->buflen = left_over; } } /* --- Code below is the primary difference between md5.c and sha1.c --- */ /* SHA1 round constants */ #define K1 0x5a827999L #define K2 0x6ed9eba1L #define K3 0x8f1bbcdcL #define K4 0xca62c1d6L /* Round functions. Note that F2 is the same as F4. */ #define F1(B, C, D) (D ^ (B & (C ^ D))) #define F2(B, C, D) (B ^ C ^ D) #define F3(B, C, D) ((B & C) | (D & (B | C))) #define F4(B, C, D) (B ^ C ^ D) /* Process LEN bytes of BUFFER, accumulating context into CTX. It is assumed that LEN % 64 == 0. Most of this code comes from GnuPG's cipher/sha1.c. */ void sha1_process_block(const void *buffer, size_t len, struct sha1_ctx *ctx) { const md5_uint32 *words = (md5_uint32 *)buffer; size_t nwords = len / sizeof(md5_uint32); const md5_uint32 *endp = words + nwords; md5_uint32 x[16]; md5_uint32 a = ctx->A; md5_uint32 b = ctx->B; md5_uint32 c = ctx->C; md5_uint32 d = ctx->D; md5_uint32 e = ctx->E; /* First increment the byte count. RFC 1321 specifies the possible length of the file up to 2^64 bits. Here we only compute the number of bytes. Do a double word increment. */ ctx->total[0] += len; if (ctx->total[0] < len) ++ctx->total[1]; #define rol(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) #define M(I) \ (tm = x[I & 0x0f] ^ x[(I - 14) & 0x0f] ^ x[(I - 8) & 0x0f] ^ \ x[(I - 3) & 0x0f], \ (x[I & 0x0f] = rol(tm, 1))) #define R(A, B, C, D, E, F, K, M) \ do { \ E += rol(A, 5) + F(B, C, D) + K + M; \ B = rol(B, 30); \ } while (0) while (words < endp) { md5_uint32 tm; int t; for (t = 0; t < 16; t++) { x[t] = SWAP32(*words); words++; } R(a, b, c, d, e, F1, K1, x[0]); R(e, a, b, c, d, F1, K1, x[1]); R(d, e, a, b, c, F1, K1, x[2]); R(c, d, e, a, b, F1, K1, x[3]); R(b, c, d, e, a, F1, K1, x[4]); R(a, b, c, d, e, F1, K1, x[5]); R(e, a, b, c, d, F1, K1, x[6]); R(d, e, a, b, c, F1, K1, x[7]); R(c, d, e, a, b, F1, K1, x[8]); R(b, c, d, e, a, F1, K1, x[9]); R(a, b, c, d, e, F1, K1, x[10]); R(e, a, b, c, d, F1, K1, x[11]); R(d, e, a, b, c, F1, K1, x[12]); R(c, d, e, a, b, F1, K1, x[13]); R(b, c, d, e, a, F1, K1, x[14]); R(a, b, c, d, e, F1, K1, x[15]); R(e, a, b, c, d, F1, K1, M(16)); R(d, e, a, b, c, F1, K1, M(17)); R(c, d, e, a, b, F1, K1, M(18)); R(b, c, d, e, a, F1, K1, M(19)); R(a, b, c, d, e, F2, K2, M(20)); R(e, a, b, c, d, F2, K2, M(21)); R(d, e, a, b, c, F2, K2, M(22)); R(c, d, e, a, b, F2, K2, M(23)); R(b, c, d, e, a, F2, K2, M(24)); R(a, b, c, d, e, F2, K2, M(25)); R(e, a, b, c, d, F2, K2, M(26)); R(d, e, a, b, c, F2, K2, M(27)); R(c, d, e, a, b, F2, K2, M(28)); R(b, c, d, e, a, F2, K2, M(29)); R(a, b, c, d, e, F2, K2, M(30)); R(e, a, b, c, d, F2, K2, M(31)); R(d, e, a, b, c, F2, K2, M(32)); R(c, d, e, a, b, F2, K2, M(33)); R(b, c, d, e, a, F2, K2, M(34)); R(a, b, c, d, e, F2, K2, M(35)); R(e, a, b, c, d, F2, K2, M(36)); R(d, e, a, b, c, F2, K2, M(37)); R(c, d, e, a, b, F2, K2, M(38)); R(b, c, d, e, a, F2, K2, M(39)); R(a, b, c, d, e, F3, K3, M(40)); R(e, a, b, c, d, F3, K3, M(41)); R(d, e, a, b, c, F3, K3, M(42)); R(c, d, e, a, b, F3, K3, M(43)); R(b, c, d, e, a, F3, K3, M(44)); R(a, b, c, d, e, F3, K3, M(45)); R(e, a, b, c, d, F3, K3, M(46)); R(d, e, a, b, c, F3, K3, M(47)); R(c, d, e, a, b, F3, K3, M(48)); R(b, c, d, e, a, F3, K3, M(49)); R(a, b, c, d, e, F3, K3, M(50)); R(e, a, b, c, d, F3, K3, M(51)); R(d, e, a, b, c, F3, K3, M(52)); R(c, d, e, a, b, F3, K3, M(53)); R(b, c, d, e, a, F3, K3, M(54)); R(a, b, c, d, e, F3, K3, M(55)); R(e, a, b, c, d, F3, K3, M(56)); R(d, e, a, b, c, F3, K3, M(57)); R(c, d, e, a, b, F3, K3, M(58)); R(b, c, d, e, a, F3, K3, M(59)); R(a, b, c, d, e, F4, K4, M(60)); R(e, a, b, c, d, F4, K4, M(61)); R(d, e, a, b, c, F4, K4, M(62)); R(c, d, e, a, b, F4, K4, M(63)); R(b, c, d, e, a, F4, K4, M(64)); R(a, b, c, d, e, F4, K4, M(65)); R(e, a, b, c, d, F4, K4, M(66)); R(d, e, a, b, c, F4, K4, M(67)); R(c, d, e, a, b, F4, K4, M(68)); R(b, c, d, e, a, F4, K4, M(69)); R(a, b, c, d, e, F4, K4, M(70)); R(e, a, b, c, d, F4, K4, M(71)); R(d, e, a, b, c, F4, K4, M(72)); R(c, d, e, a, b, F4, K4, M(73)); R(b, c, d, e, a, F4, K4, M(74)); R(a, b, c, d, e, F4, K4, M(75)); R(e, a, b, c, d, F4, K4, M(76)); R(d, e, a, b, c, F4, K4, M(77)); R(c, d, e, a, b, F4, K4, M(78)); R(b, c, d, e, a, F4, K4, M(79)); a = ctx->A += a; b = ctx->B += b; c = ctx->C += c; d = ctx->D += d; e = ctx->E += e; } } atomicparsley-20240608.083822.1ed9031/src/util.cpp000066400000000000000000001057011463107535600206550ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - util.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C) 2006-2007 puck_lock with contributions from others; see the CREDITS file ---------------------- Code Contributions by: * SLarew - prevent writing past array in Convert_multibyteUTF16_to_wchar bugfix */ //==================================================================// #include "AtomicParsley.h" /////////////////////////////////////////////////////////////////////////////////////// // Filesytem routines // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- findFileSize utf8_filepath - a pointer to a string (possibly utf8) of the full path to the file take an ascii/utf8 filepath (which if under a unicode enabled Win32 OS was already converted from utf16le to utf8 at program start) and test if AP is running on a unicode enabled Win32 OS. If it is and converted to utf8 (rather than just stripped), convert the utf8 filepath to a utf16 (native-endian) filepath & pass that to a wide stat. Or stat it with a utf8 filepath on Unixen & win32 (stripped utf8). ----------------------*/ uint64_t findFileSize(const char *utf8_filepath) { #if defined(_WIN32) && !defined(__CYGWIN__) if (IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { wchar_t *utf16_filepath = Convert_multibyteUTF8_to_wchar(utf8_filepath); struct _stati64 fileStats; _wstati64(utf16_filepath, &fileStats); free(utf16_filepath); utf16_filepath = NULL; return fileStats.st_size; } else #endif { struct stat fileStats; stat(utf8_filepath, &fileStats); return fileStats.st_size; } return 0; // won't ever get here.... unless this is win32, set to utf8 and the // folder/file had unicode.... TODO (? use isUTF8() for high ascii?) } /*---------------------- APar_OpenFile utf8_filepath - a pointer to a string (possibly utf8) of the full path to the file file_flags - 3 bytes max for the flags to open the file with (read, write, binary mode....) take an ascii/utf8 filepath (which if under a unicode enabled Win32 OS was already converted from utf16le to utf8 at program start) and test if AP is running on a unicode enabled Win32 OS. If it is, convert the utf8 filepath to a utf16 (native-endian) filepath & pass that to a wide fopen with the 8-bit file flags changed to 16-bit file flags. Or open a utf8 file with vanilla fopen on Unixen. ----------------------*/ FILE *APar_OpenFile(const char *utf8_filepath, const char *file_flags) { FILE *aFile = NULL; #if defined(_WIN32) && !defined(__CYGWIN__) if (IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { wchar_t *Lfile_flags = (wchar_t *)malloc(sizeof(wchar_t) * 4); memset(Lfile_flags, 0, sizeof(wchar_t) * 4); mbstowcs(Lfile_flags, file_flags, strlen(file_flags)); wchar_t *utf16_filepath = Convert_multibyteUTF8_to_wchar(utf8_filepath); aFile = _wfopen(utf16_filepath, Lfile_flags); free(Lfile_flags); Lfile_flags = NULL; free(utf16_filepath); utf16_filepath = NULL; } else #endif { aFile = fopen(utf8_filepath, file_flags); } if (!aFile) { fprintf(stdout, "AP error trying to fopen %s: %s\n", utf8_filepath, strerror(errno)); } return aFile; } /*---------------------- openSomeFile utf8_filepath - a pointer to a string (possibly utf8) of the full path to the file open - flag to either open or close (function does both) take an ascii/utf8 filepath and either open or close it; used for the main ISO Base Media File; store the resulting FILE* in a global source_file ----------------------*/ FILE *APar_OpenISOBaseMediaFile(const char *utf8file, bool open) { if (open && !file_opened) { source_file = APar_OpenFile(utf8file, "rb"); if (source_file != nullptr) { file_opened = true; } } else if (file_opened) { fclose(source_file); file_opened = false; source_file = nullptr; } return source_file; } void TestFileExistence(const char *filePath, bool errorOut) { FILE *a_file = NULL; a_file = APar_OpenFile(filePath, "rb"); if ((a_file == NULL) && errorOut) { fprintf(stderr, "AtomicParsley error: can't open %s for reading: %s\n", filePath, strerror(errno)); exit(1); } else { if (a_file == NULL) { fprintf(stderr, "AtomicParsley warning: can't open %s for reading but continuing " "anyway: %s\n", filePath, strerror(errno)); } else { fclose(a_file); } } } #ifndef HAVE_FSEEKO #ifdef _WIN32 int fseeko(FILE *stream, off_t pos, int whence) { return _fseeki64(stream, pos, whence); } off_t ftello(FILE *stream) { return _ftelli64(stream); } #else int fseeko(FILE *stream, off_t pos, int whence) { return fseek(stream, pos, whence); } off_t ftello(FILE *stream) { return ftell(stream); } #endif #endif #if defined(_WIN32) /////////////////////////////////////////////////////////////////////////////////////// // Win32 functions // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_OpenFileWin32 utf8_filepath - a pointer to a string (possibly utf8) of the full path to the file ... - passed on to the CreateFile function take an ascii/utf8 filepath (which if under a unicode enabled Win32 OS was already converted from utf16le to utf8 at program start) and test if AP is running on a unicode enabled Win32 OS. If it is, convert the utf8 filepath to a utf16 (native-endian) filepath & pass that to a wide CreateFile with the 8-bit file flags changed to 16-bit file flags, otherwise pass the utf8 filepath to an ANSI CreateFile ----------------------*/ HANDLE APar_OpenFileWin32(const char *utf8_filepath, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { if (IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { HANDLE hFile = NULL; wchar_t *utf16_filepath = Convert_multibyteUTF8_to_wchar(utf8_filepath); hFile = CreateFileW(utf16_filepath, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); free(utf16_filepath); return hFile; } else { return CreateFileA(utf8_filepath, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } } #endif // http://www.flipcode.com/articles/article_advstrings01.shtml bool IsUnicodeWinOS() { #if defined(_WIN32) OSVERSIONINFOW os; memset(&os, 0, sizeof(OSVERSIONINFOW)); os.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); return (GetVersionExW(&os) != 0); #else return false; #endif } /////////////////////////////////////////////////////////////////////////////////////// // File reading routines // /////////////////////////////////////////////////////////////////////////////////////// const char *APar_strferror(FILE *f) { if (feof(f) && ferror(f)) return "error and end of file"; else if (feof(f)) return "end of file"; else if (ferror(f)) return "error"; else return "neither error nor end of file"; } uint8_t APar_read8(FILE *ISObasemediafile, uint64_t pos) { uint8_t a_byte = 0; size_t size; fseeko(ISObasemediafile, pos, SEEK_SET); size = fread(&a_byte, 1, 1, ISObasemediafile); if (size != 1) { printf("%s read failed, expect 1, got %u: %s\n", __FUNCTION__, (unsigned int)size, APar_strferror(ISObasemediafile)); exit(1); } return a_byte; } uint16_t APar_read16(char *buffer, FILE *ISObasemediafile, uint64_t pos) { size_t size; fseeko(ISObasemediafile, pos, SEEK_SET); size = fread(buffer, 1, 2, ISObasemediafile); if (size != 2) { printf("%s read failed, expect 2, got %u: %s\n", __FUNCTION__, (unsigned int)size, APar_strferror(ISObasemediafile)); exit(1); } return UInt16FromBigEndian(buffer); } uint32_t APar_read32(char *buffer, FILE *ISObasemediafile, uint64_t pos) { size_t size; fseeko(ISObasemediafile, pos, SEEK_SET); size = fread(buffer, 1, 4, ISObasemediafile); if (size != 4) { printf("%s read failed, expect 4, got %u: %s\n", __FUNCTION__, (unsigned int)size, APar_strferror(ISObasemediafile)); exit(1); } return UInt32FromBigEndian(buffer); } uint64_t APar_read64(char *buffer, FILE *ISObasemediafile, uint64_t pos) { size_t size; fseeko(ISObasemediafile, pos, SEEK_SET); size = fread(buffer, 1, 8, ISObasemediafile); if (size != 8) { printf("%s read failed, expect 8, got %u: %s\n", __FUNCTION__, (unsigned int)size, APar_strferror(ISObasemediafile)); exit(1); } return UInt64FromBigEndian(buffer); } void APar_readX_noseek(char *buffer, FILE *ISObasemediafile, uint32_t length) { size_t size; size = fread(buffer, 1, length, ISObasemediafile); if (size != length) { printf("%s read failed, expect %" PRIu32 ", got %" PRIu32 ": %s\n", __FUNCTION__, length, (uint32_t)size, APar_strferror(ISObasemediafile)); exit(1); } return; } void APar_readX(char *buffer, FILE *ISObasemediafile, uint64_t pos, uint32_t length) { size_t size; fseeko(ISObasemediafile, pos, SEEK_SET); size = fread(buffer, 1, length, ISObasemediafile); if (size != length) { printf("%s read failed, expect %" PRIu32 ", got %" PRIu32 ": %s\n", __FUNCTION__, length, (uint32_t)size, APar_strferror(ISObasemediafile)); exit(1); } return; } uint32_t APar_ReadFile(char *destination_buffer, FILE *a_file, uint32_t bytes_to_read) { uint32_t bytes_read = 0; if (destination_buffer != NULL) { fseeko(a_file, 0, SEEK_SET); // not that 2gb support is required - malloc // would probably have a few issues bytes_read = fread(destination_buffer, 1, bytes_to_read, a_file); file_size += bytes_read; // accommodate huge files embedded within small // files for APar_Validate } return bytes_read; } uint32_t APar_FindValueInAtom(char *uint32_buffer, FILE *ISObasemediafile, short an_atom, uint64_t start_position, uint32_t eval_number) { uint64_t current_pos = start_position; memset(uint32_buffer, 0, 5); while (current_pos <= parsedAtoms[an_atom].AtomicLength) { current_pos++; if (eval_number > 65535) { // current_pos +=4; if (APar_read32(uint32_buffer, ISObasemediafile, parsedAtoms[an_atom].AtomicStart + current_pos) == eval_number) { break; } } else { // current_pos +=2; if (APar_read16(uint32_buffer, ISObasemediafile, parsedAtoms[an_atom].AtomicStart + current_pos) == (uint16_t)eval_number) { break; } } if (current_pos >= parsedAtoms[an_atom].AtomicLength) { current_pos = 0; break; } } return (uint32_t)current_pos; } /////////////////////////////////////////////////////////////////////////////////////// // Language specifics // /////////////////////////////////////////////////////////////////////////////////////// void APar_UnpackLanguage(unsigned char lang_code[], uint16_t packed_language) { lang_code[3] = 0; lang_code[2] = (packed_language & 0x1F) + 0x60; lang_code[1] = ((packed_language >> 5) & 0x1F) + 0x60; lang_code[0] = ((packed_language >> 10) & 0x1F) + 0x60; return; } uint16_t PackLanguage( const char *language_code, uint8_t lang_offset) { //?? is there a problem here? und does't work // http://www.w3.org/WAI/ER/IG/ert/iso639.htm // I think Apple's 3gp asses decoder is a little off. First, it doesn't // support a lot of those 3 letter language codes above on that page. for // example 'zul' blocks *all* metadata from showing up. 'fre' is a no-no, but // 'fra' is fine. then, the spec calls for all strings to be null terminated. // So then why does a ' 2005' (with a NULL at the end) show up as ' 2005' in // 'pol', but ' 2005 ?' in 'fas' Farsi? Must be Apple's implementation, // because the files are identical except for the uint16_t lang setting. uint16_t packed_language = 0; // fprintf(stdout, "%i, %i, %i\n", language_code[0+lang_offset], // language_code[1+lang_offset], language_code[2+lang_offset]); if (language_code[0 + lang_offset] < 97 || language_code[0 + lang_offset] > 122 || language_code[1 + lang_offset] < 97 || language_code[1 + lang_offset] > 122 || language_code[2 + lang_offset] < 97 || language_code[2 + lang_offset] > 122) { return packed_language; } packed_language = (((language_code[0 + lang_offset] - 0x60) & 0x1F) << 10) | (((language_code[1 + lang_offset] - 0x60) & 0x1F) << 5) | ((language_code[2 + lang_offset] - 0x60) & 0x1F); return packed_language; } /////////////////////////////////////////////////////////////////////////////////////// // platform specifics // /////////////////////////////////////////////////////////////////////////////////////// #ifndef HAVE_STRSEP // use glibc's strsep only on windows when cygwin & libc are undefined; // otherwise the internal strsep will be used This marks the point where a // ./configure & makefile combo would make this easier /* Copyright (C) 1992, 93, 96, 97, 98, 99, 2004 Free Software Foundation, Inc. This strsep function is part of the GNU C Library - v2.3.5; LGPL. */ char *strsep(char **stringp, const char *delim) { char *begin, *end; begin = *stringp; if (begin == NULL) return NULL; // A frequent case is when the delimiter string contains only one character. // Here we don't need to call the expensive `strpbrk' function and instead // work using `strchr'. if (delim[0] == '\0' || delim[1] == '\0') { char ch = delim[0]; if (ch == '\0') end = NULL; else { if (*begin == ch) end = begin; else if (*begin == '\0') end = NULL; else end = strchr(begin + 1, ch); } } else end = strpbrk(begin, delim); // Find the end of the token. if (end) { *end++ = '\0'; // Terminate the token and set *STRINGP past NUL character. *stringp = end; } else *stringp = NULL; // No more delimiters; this is the last token. return begin; } #endif void determine_MonthDay(int literal_day, int &month, int &day) { if (literal_day <= 31) { month = 1; day = literal_day; } else if (literal_day <= 59) { month = 2; day = literal_day - 31; } else if (literal_day <= 90) { month = 3; day = literal_day - 59; } else if (literal_day <= 120) { month = 4; day = literal_day - 90; } else if (literal_day <= 151) { month = 5; day = literal_day - 120; } else if (literal_day <= 181) { month = 6; day = literal_day - 151; } else if (literal_day <= 212) { month = 7; day = literal_day - 181; } else if (literal_day <= 243) { month = 8; day = literal_day - 212; } else if (literal_day <= 273) { month = 9; day = literal_day - 243; } else if (literal_day <= 304) { month = 10; day = literal_day - 273; } else if (literal_day <= 334) { month = 11; day = literal_day - 304; } else if (literal_day <= 365) { month = 12; day = literal_day - 334; } return; } char *APar_gmtime64(uint64_t total_secs, char *utc_time) { // this will probably be off between Jan 1 & Feb 28 on a leap year by a // day.... I'll somehow cope & deal. struct tm timeinfo = {0, 0, 0, 0, 0}; int offset_year = (int)(total_secs / 31536000); // 60 * 60 * 24 * 365 (ordinary year in // seconds; doesn't account for leap year) int literal_year = 1904 + offset_year; int literal_days_into_year = ((total_secs % 31536000) / 86400) - (offset_year / 4); // accounts for the leap year uint32_t literal_seconds_into_day = total_secs % 86400; int month = 0; int days = 0; determine_MonthDay(literal_days_into_year, month, days); if (literal_days_into_year < 0) { literal_year -= 1; literal_days_into_year = 31 + literal_days_into_year; month = 12; days = literal_days_into_year; } int hours = literal_seconds_into_day / 3600; timeinfo.tm_year = literal_year - 1900; timeinfo.tm_yday = literal_days_into_year; timeinfo.tm_mon = month - 1; timeinfo.tm_mday = days; timeinfo.tm_wday = (((total_secs / 86400) - (offset_year / 4)) - 5) % 7; timeinfo.tm_hour = hours; timeinfo.tm_min = (literal_seconds_into_day - (hours * 3600)) / 60; timeinfo.tm_sec = (int)(literal_seconds_into_day % 60); strftime(utc_time, 50, "%a %b %d %H:%M:%S %Y", &timeinfo); return utc_time; } /*---------------------- ExtractUTC total_secs - the time in seconds (from Jan 1, 1904) Convert the seconds to a calendar date with seconds. ----------------------*/ char *APar_extract_UTC(uint64_t total_secs) { // 2082844800 seconds between 01/01/1904 & 01/01/1970 // 2,081,376,000 (60 seconds * 60 minutes * 24 hours * 365 days * 66 years) // + 1,468,800 (60 * 60 * 24 * 17 leap days in 01/01/1904 to 01/01/1970 // duration) //= 2,082,844,800 static char utc_time[50]; memset(utc_time, 0, 50); if (total_secs > MAXTIME_32) { return APar_gmtime64(total_secs, utc_time); } else { if (total_secs < 2082844800) { return APar_gmtime64(total_secs, utc_time); // less than Unix epoch } else { total_secs -= 2082844800; time_t reduced_seconds = (time_t)total_secs; strftime( *&utc_time, 50, "%a %b %d %H:%M:%S %Y", gmtime(&reduced_seconds)); return *&utc_time; } } return *&utc_time; } uint32_t APar_get_mpeg4_time() { #if defined(_WIN32) && !defined(__CYGWIN__) FILETIME file_time; uint64_t wintime = 0; GetSystemTimeAsFileTime(&file_time); wintime = (((uint64_t)file_time.dwHighDateTime << 32) | file_time.dwLowDateTime) / 10000000; wintime -= 9561628800ULL; return (uint32_t)wintime; #else uint32_t current_time_in_seconds = 0; struct timeval tv; gettimeofday(&tv, NULL); current_time_in_seconds = tv.tv_sec; return current_time_in_seconds + 2082844800; #endif return 0; } /*---------------------- APar_StandardTime formed_time - the destination string Print the ISO 8601 Coordinated Universal Time (UTC) timestamp (in YYYY-MM-DDTHH:MM:SSZ form) ----------------------*/ void APar_StandardTime(char *&formed_time) { time_t rawtime; struct tm *timeinfo; time(&rawtime); timeinfo = gmtime(&rawtime); strftime(formed_time, 100, "%Y-%m-%dT%H:%M:%SZ", timeinfo); // that hanging Z is there; denotes the UTC return; } /////////////////////////////////////////////////////////////////////////////////////// // strings // /////////////////////////////////////////////////////////////////////////////////////// wchar_t * Convert_multibyteUTF16_to_wchar(char *input_unicode, size_t glyph_length, bool skip_BOM) { // TODO: is this like wcstombs? int BOM_mark_bytes = 0; if (skip_BOM) { BOM_mark_bytes = 2; } wchar_t *utf16_data = (wchar_t *)malloc( sizeof(wchar_t) * (glyph_length + 1)); // just to be sure there will be a trailing NULL wmemset(utf16_data, 0, glyph_length + 1); for (size_t i = 0; i < glyph_length; i++) { #if defined(__ppc__) || defined(__ppc64__) utf16_data[i] = (input_unicode[2 * i + BOM_mark_bytes] & 0x00ff) << 8 | (input_unicode[2 * i + 1 + BOM_mark_bytes]) << 0; //+2 & +3 to skip over the BOM #else utf16_data[i] = (input_unicode[2 * i + BOM_mark_bytes] << 8) | ((input_unicode[2 * i + 1 + BOM_mark_bytes]) & 0x00ff) << 0; //+2 & +3 to skip over the BOM #endif } return utf16_data; } unsigned char *Convert_multibyteUTF16_to_UTF8(char *input_utf16, size_t glyph_length, size_t byte_count) { unsigned char *utf8_data = (unsigned char *)malloc(sizeof(unsigned char) * glyph_length); memset(utf8_data, 0, glyph_length); UTF16BEToUTF8( utf8_data, glyph_length, (unsigned char *)input_utf16 + 2, byte_count); return utf8_data; } wchar_t *Convert_multibyteUTF8_to_wchar( const char *input_utf8) { // TODO: is this like mbstowcs? wchar_t *return_val = NULL; size_t string_length = strlen(input_utf8) + 1; // account for terminating NULL size_t char_glyphs = mbstowcs( NULL, input_utf8, string_length); // passing NULL pre-calculates the size of wchar_t needed unsigned char *utf16_conversion = (unsigned char *)malloc(sizeof(unsigned char) * string_length * 2); memset(utf16_conversion, 0, string_length * 2); int utf_16_glyphs = UTF8ToUTF16BE(utf16_conversion, char_glyphs * 2, (unsigned char *)input_utf8, string_length) / 2; // returned value is in bytes return_val = Convert_multibyteUTF16_to_wchar( (char *)utf16_conversion, (size_t)utf_16_glyphs, false); free(utf16_conversion); utf16_conversion = NULL; return (return_val); } // these flags from id3v2 2.4 // 0x00 = ISO-8859-1 & terminate with 0x00. // 0x01 = UTF-16 with BOM. All frames have same encoding & terminate with // 0x0000. 0x02 = UTF-16BE without BOM & terminate with 0x0000. 0x03 = UTF-8 & // terminated with 0x00. buffer can hold either ut8 or utf16 carried on 8-bit // char which requires a cast /*---------------------- findstringNULLterm in_string - pointer to location of a string (can be either 8859-1, utf8 or utf16be/utf16be needing a cast to wchar) encodingFlag - used to denote the encoding of instring (derived from id3v2 2.4 encoding flags) max_len - the length of given string - there may be no NULL terminaiton, in which case it will only count to max_len Either find the NULL if it exists and return how many bytes into in_string that NULL exists, or it won't find a NULL and return max_len ----------------------*/ uint32_t findstringNULLterm(char *in_string, uint8_t encodingFlag, uint32_t max_len) { uint32_t byte_count = 0; if (encodingFlag == 0x00 || encodingFlag == 0x03) { char *bufptr = in_string; while (bufptr <= in_string + max_len) { if (*bufptr == 0x00) { break; } bufptr++; byte_count++; } } else if ((encodingFlag == 0x01 || encodingFlag == 0x02) && max_len >= 2) { short wbufptr; while (byte_count <= max_len) { wbufptr = (*(in_string + byte_count) << 8) | *(in_string + byte_count + 1); if (wbufptr == 0x0000) { break; } byte_count += 2; } } if (byte_count > max_len) return max_len; return byte_count; } uint32_t skipNULLterm(char *in_string, uint8_t encodingFlag, uint32_t max_len) { uint32_t byte_count = 0; if (encodingFlag == 0x00 || encodingFlag == 0x03) { char *bufptr = in_string; while (bufptr <= in_string + max_len) { if (*bufptr == 0x00) { byte_count++; break; } bufptr++; } } else if ((encodingFlag == 0x01 || encodingFlag == 0x02) && max_len >= 2) { short wbufptr; while (byte_count <= max_len) { wbufptr = (*(in_string + byte_count) << 8) | *(in_string + byte_count + 1); if (wbufptr == 0x0000) { byte_count += 2; break; } } } return byte_count; } /////////////////////////////////////////////////////////////////////////////////////// // generics // /////////////////////////////////////////////////////////////////////////////////////// uint16_t UInt16FromBigEndian(const char *string) { #if defined(__ppc__) || defined(__ppc64__) uint16_t test; memcpy(&test, string, 2); return test; #else return (((string[0] & 0xff) << 8) | (string[1] & 0xff) << 0); #endif } uint32_t UInt32FromBigEndian(const char *string) { #if defined(__ppc__) || defined(__ppc64__) uint32_t test; memcpy(&test, string, 4); return test; #else return (((string[0] & 0xff) << 24) | ((string[1] & 0xff) << 16) | ((string[2] & 0xff) << 8) | (string[3] & 0xff) << 0); #endif } uint64_t UInt64FromBigEndian(const char *string) { #if defined(__ppc__) || defined(__ppc64__) uint64_t test; memcpy(&test, string, 8); return test; #else return (uint64_t)(string[0] & 0xff) << 54 | (uint64_t)(string[1] & 0xff) << 48 | (uint64_t)(string[2] & 0xff) << 40 | (uint64_t)(string[3] & 0xff) << 32 | (uint64_t)(string[4] & 0xff) << 24 | (uint64_t)(string[5] & 0xff) << 16 | (uint64_t)(string[6] & 0xff) << 8 | (uint64_t)(string[7] & 0xff) << 0; #endif } void UInt16_TO_String2(uint16_t snum, char *data) { data[0] = (snum >> 8) & 0xff; data[1] = (snum >> 0) & 0xff; return; } void UInt32_TO_String4(uint32_t lnum, char *data) { data[0] = (lnum >> 24) & 0xff; data[1] = (lnum >> 16) & 0xff; data[2] = (lnum >> 8) & 0xff; data[3] = (lnum >> 0) & 0xff; return; } void UInt64_TO_String8(uint64_t ullnum, char *data) { data[0] = (ullnum >> 56) & 0xff; data[1] = (ullnum >> 48) & 0xff; data[2] = (ullnum >> 40) & 0xff; data[3] = (ullnum >> 32) & 0xff; data[4] = (ullnum >> 24) & 0xff; data[5] = (ullnum >> 16) & 0xff; data[6] = (ullnum >> 8) & 0xff; data[7] = (ullnum >> 0) & 0xff; return; } /////////////////////////////////////////////////////////////////////////////////////// // 3gp asset support (for 'loci') // /////////////////////////////////////////////////////////////////////////////////////// uint32_t float_to_16x16bit_fixed_point(double floating_val) { uint32_t fixedpoint_16bit = 0; int16_t long_integer = (int16_t)floating_val; // to get a fixed 16-bit decimal, work on the decimal part along; multiply by // (2^8 * 2) which moves the decimal over 16 bits to create our int16_t now // while the degrees can be negative (requiring a int16_6), the decimal // portion is always positive (and thus requiring a uint16_t) uint16_t long_decimal = (int16_t)((floating_val - long_integer) * (double)(65536)); fixedpoint_16bit = long_integer * 65536 + long_decimal; // same as bitshifting, less headache doing it return fixedpoint_16bit; } double fixed_point_16x16bit_to_double(uint32_t fixed_point) { double return_val = 0.0; int16_t long_integer = fixed_point / 65536; uint16_t long_decimal = fixed_point - (long_integer * 65536); return_val = long_integer + ((double)long_decimal / 65536); if (return_val < 0.0) { return_val -= 1.0; } return return_val; } uint32_t widechar_len(char *instring, uint32_t _bytes_) { uint32_t wstring_len = 0; for (uint32_t i = 0; i <= _bytes_ / 2; i++) { if (instring[0] == 0 && instring[1] == 0) { break; } else { instring += 2; wstring_len++; } } return wstring_len; } bool APar_assert(bool expression, int error_msg, const char *supplemental_info) { bool force_break = true; if (!expression) { force_break = false; switch (error_msg) { case 1: { // trying to set an iTunes-style metadata tag on an // 3GP/MobileMPEG-4 fprintf(stdout, "AP warning:\n\tSetting the %s tag is for ordinary MPEG-4 " "files.\n\tIt is not supported on 3gp/amc files.\nSkipping\n", supplemental_info); break; } case 2: { // trying to set a 3gp asset on an mpeg-4 file with the improper // brand fprintf(stdout, "AP warning:\n\tSetting the %s asset is only available on 3GPP " "files branded 3gp6 or later.\nSkipping\n", supplemental_info); break; } case 3: { // trying to set 'meta' on a file without a iso2 or mp42 // compatible brand. fprintf(stdout, "AtomicParsley warning: ID3 tags requires a v2 " "compatible file, which was not found.\nSkipping.\n"); break; } case 4: { // trying to set a 3gp album asset on an early 3gp file that only // came into being with 3gp6 fprintf(stdout, "Major brand of given file: %s\n", supplemental_info); break; } case 5: { // trying to set metadata on track 33 when there are only 3 tracks fprintf(stdout, "AP warning: skipping non-existing track number setting user " "data atom: %s.\n", supplemental_info); break; } case 6: { // trying to set id3 metadata on track 33 when there are only 3 // tracks fprintf(stdout, "AP error: skipping non-existing track number setting frame %s " "for ID32 atom.\n", supplemental_info); break; } case 7: { // trying to set id3 metadata on track 33 when there are only 3 // tracks fprintf(stdout, "AP warning: the 'meta' atom is being hangled by a %s handler.\n " " Remove the 'meta' atom and its contents and try again.\n", supplemental_info); break; } case 8: { // trying to create an ID32 atom when there is a primary item atom // present signaling referenced data (local or external) fprintf(stdout, "AP warning: unsupported external or referenced items were " "detected. Skipping this frame: %s\n", supplemental_info); break; } case 9: { // trying to eliminate an id3 frame that doesn't exist fprintf(stdout, "AP warning: id3 frame %s cannot be deleted because it does not " "exist.\n", supplemental_info); break; } case 10: { // trying to eliminate an id3 frame that doesn't exist fprintf(stdout, "AP warning: skipping setting unknown %s frame\n", supplemental_info); break; } case 11: { // insuffient memory to malloc an id3 field (probably picture or // encapuslated object) fprintf(stdout, "AP error: memory was not alloctated for frame %s. Exiting.\n", supplemental_info); break; } } } return force_break; } /* http://wwwmaths.anu.edu.au/~brent/random.html */ /* xorgens.c version 3.04, R. P. Brent, 20060628. */ /* For type definitions see xorgens.h */ typedef unsigned long xorgenUINT; unsigned long xor4096i() { /* 32-bit or 64-bit integer random number generator with period at least 2**4096-1. It is assumed that "xorgenUINT" is a 32-bit or 64-bit integer (see typedef statements in xorgens.h). xor4096i should be called exactly once with nonzero seed, and thereafter with zero seed. One random number uniformly distributed in [0..2**wlen) is returned, where wlen = 8*sizeof(xorgenUINT) = 32 or 64. R. P. Brent, 20060628. */ /* UINT64 is TRUE if 64-bit xorgenUINT, UINT32 is TRUE otherwise (assumed to be 32-bit xorgenUINT). */ #define UINT64 (sizeof(xorgenUINT) >> 3) #define UINT32 (1 - UINT64) #define wlen (64 * UINT64 + 32 * UINT32) #define r (64 * UINT64 + 128 * UINT32) #define s (53 * UINT64 + 95 * UINT32) #define a (33 * UINT64 + 17 * UINT32) #define b (26 * UINT64 + 12 * UINT32) #define c (27 * UINT64 + 13 * UINT32) #define d (29 * UINT64 + 15 * UINT32) #define ws (27 * UINT64 + 16 * UINT32) xorgenUINT seed = 0; static xorgenUINT w, weyl, zero = 0, x[r]; xorgenUINT t, v; static int i = -1; /* i < 0 indicates first call */ int k; if (i < 0) { #if defined HAVE_SRANDDEV sranddev(); #else srand((int)time(NULL)); #endif double doubleseed = ((double)rand() / ((double)(RAND_MAX) + (double)(1))); seed = (xorgenUINT)(doubleseed * rand()); } if ((i < 0) || (seed != zero)) { /* Initialisation necessary */ /* weyl = odd approximation to 2**wlen*(sqrt(5)-1)/2. */ if (UINT32) weyl = 0x61c88647; else weyl = ((((xorgenUINT)0x61c88646) << 16) << 16) + (xorgenUINT)0x80b583eb; v = (seed != zero) ? seed : ~seed; /* v must be nonzero */ for (k = wlen; k > 0; k--) { /* Avoid correlations for close seeds */ v ^= v << 10; v ^= v >> 15; /* Recurrence has period 2**wlen-1 */ v ^= v << 4; v ^= v >> 13; /* for wlen = 32 or 64 */ } for (w = v, k = 0; (xorgenUINT)k < r; k++) { /* Initialise circular array */ v ^= v << 10; v ^= v >> 15; v ^= v << 4; v ^= v >> 13; x[k] = v + (w += weyl); } for (i = r - 1, k = 4 * r; k > 0; k--) { /* Discard first 4*r results */ t = x[i = (i + 1) & (r - 1)]; t ^= t << a; t ^= t >> b; v = x[(i + (r - s)) & (r - 1)]; v ^= v << c; v ^= v >> d; x[i] = t ^ v; } } /* Apart from initialisation (above), this is the generator */ t = x[i = (i + 1) & (r - 1)]; /* Assumes that r is a power of two */ v = x[(i + (r - s)) & (r - 1)]; /* Index is (i-s) mod r */ t ^= t << a; t ^= t >> b; /* (I + L^a)(I + R^b) */ v ^= v << c; v ^= v >> d; /* (I + L^c)(I + R^d) */ x[i] = (v ^= t); /* Update circular array */ w += weyl; /* Update Weyl generator */ return (v + (w ^ (w >> ws))); /* Return combination */ #undef UINT64 #undef UINT32 #undef wlen #undef r #undef s #undef a #undef b #undef c #undef d #undef ws } atomicparsley-20240608.083822.1ed9031/src/util.h000066400000000000000000000117261463107535600203250ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - util.h AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #include "ap_types.h" #if defined(__ppc__) || defined(__ppc64__) #define SWAP16(x) (x) #define SWAP32(x) (x) #else #define SWAP16(x) ((((x)&0xFF) << 8) | (((x) >> 8) & 0xFF)) #define SWAP32(x) \ ((((x)&0xFF) << 24) | (((x) >> 24) & 0xFF) | (((x)&0x0000FF00) << 8) | \ (((x)&0x00FF0000) >> 8)) #endif #if defined(_WIN32) && defined(_MSC_VER) #undef HAVE_GETOPT_H #undef HAVE_LROUNDF #undef HAVE_STRSEP //#undef HAVE_ZLIB_H //comment this IN when compiling on win32 withOUT zlib // present #define HAVE_ZLIB_H 1 //and comment this OUT #undef HAVE_SRANDDEV #endif #define MAXTIME_32 6377812095ULL uint64_t findFileSize(const char *utf8_filepath); FILE *APar_OpenFile(const char *utf8_filepath, const char *file_flags); FILE *APar_OpenISOBaseMediaFile(const char *file, bool open); // openSomeFile void TestFileExistence(const char *filePath, bool errorOut); #ifndef HAVE_FSEEKO int fseeko(FILE *stream, off_t pos, int whence); off_t ftello(FILE *stream); #endif #if defined(_WIN32) HANDLE APar_OpenFileWin32(const char *utf8_filepath, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); #endif bool IsUnicodeWinOS(); const char *APar_strferror(FILE *f); uint8_t APar_read8(FILE *ISObasemediafile, uint64_t pos); uint16_t APar_read16(char *buffer, FILE *ISObasemediafile, uint64_t pos); uint32_t APar_read32(char *buffer, FILE *ISObasemediafile, uint64_t pos); uint64_t APar_read64(char *buffer, FILE *ISObasemediafile, uint64_t pos); void APar_readX_noseek(char *buffer, FILE *ISObasemediafile, uint32_t length); void APar_readX(char *buffer, FILE *ISObasemediafile, uint64_t pos, uint32_t length); uint32_t APar_ReadFile(char *destination_buffer, FILE *a_file, uint32_t bytes_to_read); uint32_t APar_FindValueInAtom(char *uint32_buffer, FILE *ISObasemediafile, short an_atom, uint64_t start_position, uint32_t eval_number); void APar_UnpackLanguage(unsigned char lang_code[], uint16_t packed_language); uint16_t PackLanguage(const char *language_code, uint8_t lang_offset); #ifndef HAVE_STRSEP char *strsep(char **stringp, const char *delim); #endif char *APar_extract_UTC(uint64_t total_secs); uint32_t APar_get_mpeg4_time(); void APar_StandardTime(char *&formed_time); wchar_t *Convert_multibyteUTF16_to_wchar(char *input_unicode, size_t glyph_length, bool skip_BOM); unsigned char *Convert_multibyteUTF16_to_UTF8(char *input_utf8, size_t glyph_length, size_t byte_count); wchar_t *Convert_multibyteUTF8_to_wchar(const char *input_utf8); uint32_t findstringNULLterm(char *in_string, uint8_t encodingFlag, uint32_t max_len); uint32_t skipNULLterm(char *in_string, uint8_t encodingFlag, uint32_t max_len); uint16_t UInt16FromBigEndian(const char *string); uint32_t UInt32FromBigEndian(const char *string); uint64_t UInt64FromBigEndian(const char *string); void UInt16_TO_String2(uint16_t snum, char *data); void UInt32_TO_String4(uint32_t lnum, char *data); void UInt64_TO_String8(uint64_t ullnum, char *data); uint32_t float_to_16x16bit_fixed_point(double floating_val); double fixed_point_16x16bit_to_double(uint32_t fixed_point); uint32_t widechar_len(char *instring, uint32_t _bytes_); bool APar_assert(bool expression, int error_msg, const char *supplemental_info); unsigned long xor4096i(); atomicparsley-20240608.083822.1ed9031/src/uuid.cpp000066400000000000000000000401321463107535600206420ustar00rootroot00000000000000//==================================================================// /* AtomicParsley - uuid.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C) 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// //==================================================================// /* Much of AP_Create_UUID_ver5_sha1_name was derived from http://www.ietf.org/rfc/rfc4122.txt which I don't believe conflicts with or restricts the GPL. And this page: http://home.famkruithof.net/guid-uuid-namebased.html tells me I'm not on crack when I try to calculate the uuids myself. Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. Copyright (c) 1998 Microsoft. To anyone who acknowledges that this file is provided "AS IS" without any express or implied warranty: permission to use, copy, modify, and distribute this file for any purpose is hereby granted without fee, provided that the above copyright notices and this notice appears in all source code copies, and that none of the names of Open Software Foundation, Inc., Hewlett-Packard Company, Microsoft, or Digital Equipment Corporation be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. Neither Open Software Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital Equipment Corporation makes any representations about the suitability of this software for any purpose. */ //==================================================================// #include "AtomicParsley.h" /*---------------------- print_hash hash - the string array of the sha1 hash prints out the hex representation of the 16 byte hash - this relates to sha1, but its here to keep the sha1 files as close to original as possible ----------------------*/ void print_hash(char hash[]) { for (int i = 0; i < 20; i++) { fprintf(stdout, "%02x", (uint8_t)hash[i]); } fprintf(stdout, "\n"); return; } /*---------------------- Swap_Char in_str - the string to have the swap operation performed on str_len - the amount of bytes to swap in the string Make a pointer to the start & end of the string, as well as a temporary string to hold the swapped byte. As the start increments up, the end decrements down. Copy the byte at each advancing start position. Copy the byte of the diminishing end string into the start byte, then advance the start byte. Finaly, set each byte of the decrementing end pointer to the temp string byte. ----------------------*/ void Swap_Char(char *in_str, uint8_t str_len) { char *start_str, *end_str, temp_str; start_str = in_str; end_str = start_str + str_len; while (start_str < --end_str) { temp_str = *start_str; *start_str++ = *end_str; *end_str = temp_str; } return; } /*---------------------- APar_endian_uuid_bin_str_conversion raw_uuid - a binary string representation of a uuid As a string representation of a uuid, there is a 32-bit & 2 16-bit numbers in the uuid. These members need to be swapped on big endian systems. ----------------------*/ void APar_endian_uuid_bin_str_conversion(char *raw_uuid) { #if defined(__ppc__) || defined(__ppc64__) return; // we are *naturally* network byte ordered - simplicity #else Swap_Char(raw_uuid, 4); Swap_Char(raw_uuid + 4, 2); Swap_Char(raw_uuid + 4 + 2, 2); return; #endif } /*---------------------- APar_print_uuid uuid - a uuid structure containing the uuid Print out a full string representation of a uuid ----------------------*/ void APar_print_uuid(ap_uuid_t *uuid, bool new_line) { fprintf(stdout, "%08" PRIx32 "-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", uuid->time_low, uuid->time_mid, uuid->time_hi_and_version, uuid->clock_seq_hi_and_reserved, uuid->clock_seq_low, uuid->node[0], uuid->node[1], uuid->node[2], uuid->node[3], uuid->node[4], uuid->node[5]); if (new_line) fprintf(stdout, "\n"); return; } /*---------------------- APar_sprintf_uuid uuid - a uuid structure containing the uuid destination - the end result uuid will be placed here Put a binary representation of a uuid to a human-readable ordered uuid string ----------------------*/ void APar_sprintf_uuid(ap_uuid_t *uuid, char *destination) { sprintf(destination, "%08" PRIx32 "-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", uuid->time_low, uuid->time_mid, uuid->time_hi_and_version, uuid->clock_seq_hi_and_reserved, uuid->clock_seq_low, uuid->node[0], uuid->node[1], uuid->node[2], uuid->node[3], uuid->node[4], uuid->node[5]); return; } /*---------------------- APar_uuid_scanf in_formed_uuid - pointer to a string (or a place in a string) where to place a binary (hex representation) string uuid of 16 bytes raw_uuid - the string that contains a string representation of a uuid (from cli input for example). This string isn't 16 bytes - its 36 Skip past a hyphen, make any upper case characters lower (ahh, that hex 'Q') to do a manual scanf on the string. Add its hex representation as a number for 1/2 of the bits (a single byte is 2 hex characters), shift it over to the upper bits, and repeat adding the lower bits. Repeat until done. ----------------------*/ uint8_t APar_uuid_scanf(char *in_formed_uuid, const char *raw_uuid_in) { char *uuid_str, *end_uuid_str, *uuid_byte; uint8_t uuid_pos, uuid_len; uint8_t keeprap = 0; #if defined(_WIN32) && !defined(__CYGWIN__) char *raw_uuid = _strdup(raw_uuid_in); #else char *raw_uuid = strdup(raw_uuid_in); #endif uuid_len = strlen( raw_uuid); // it will be like "55534d54-21d2-4fce-bb88-695cfac9c740" uuid_str = raw_uuid; uuid_pos = 0; end_uuid_str = uuid_str + uuid_len; while (uuid_str < end_uuid_str) { uuid_byte = &in_formed_uuid[uuid_pos]; if (uuid_str[0] == '-') uuid_str++; if (uuid_str[0] >= 'A' && uuid_str[0] <= 90) uuid_str[0] += 32; if (uuid_str[1] >= 'A' && uuid_str[1] <= 90) uuid_str[0] += 32; for (int i = 0; i <= 1; i++) { switch (uuid_str[i]) { case '0': { keeprap = 0; break; } case '1': { keeprap = 1; break; } case '2': { keeprap = 2; break; } case '3': { keeprap = 3; break; } case '4': { keeprap = 4; break; } case '5': { keeprap = 5; break; } case '6': { keeprap = 6; break; } case '7': { keeprap = 7; break; } case '8': { keeprap = 8; break; } case '9': { keeprap = 9; break; } case 'a': { keeprap = 10; break; } case 'b': { keeprap = 11; break; } case 'c': { keeprap = 12; break; } case 'd': { keeprap = 13; break; } case 'e': { keeprap = 14; break; } case 'f': { keeprap = 15; break; } } if (i == 0) { *uuid_byte = keeprap << 4; } else { *uuid_byte |= keeprap; //(keeprap & 0xF0); } } uuid_str += 2; uuid_pos++; } APar_endian_uuid_bin_str_conversion(in_formed_uuid); free(raw_uuid); return uuid_pos; } /*---------------------- APar_extract_uuid_version uuid - a uuid structure containing the uuid binary_uuid_str - a binary string rep of a uuid (without dashes, just the hex) Test the 6th byte in a str and push the bits to get the version or take the 3rd member of a uuid (uint16_t) and shift bits by 12 ----------------------*/ uint8_t APar_extract_uuid_version(ap_uuid_t *uuid, char *binary_uuid_str) { uint8_t uuid_ver = 0; if (binary_uuid_str != NULL) { uuid_ver = (binary_uuid_str[6] >> 4); } else if (uuid != NULL) { uuid_ver = (uuid->time_hi_and_version >> 12); } return uuid_ver; } /*---------------------- AP_Create_UUID_ver5_sha1_name uuid - pointer to the final version 5 sha1 hash bashed uuid desired_namespace - the input uuid used as a basis for the hash; a ver5 uuid is a namespace/name uuid. This is the namespace portion name - this is the name portion used to make a v5 sha1 hash namelen - length of name (currently a strlen() value) This will create a version 5 sha1 based uuid of a name in a namespace. The desired_namespace has its endian members swapped to newtwork byte ordering. The sha1 hash algorithm is fed the reordered netord_namespace uuid to create a hash; the name is then added to the hash to create a hash of the name in the namespace. The final hash is then copied into the out_uuid, and the endian members of the ap_uuid_t are swapped to endian ordering. ----------------------*/ void AP_Create_UUID_ver5_sha1_name(ap_uuid_t *out_uuid, ap_uuid_t desired_namespace, const char *name, int namelen) { sha1_ctx sha_state; char hash[20]; ap_uuid_t networkorderd_namespace; // swap the endian members of uuid to network byte order for hash uniformity // across platforms (the NULL or AP.sf.net uuid) networkorderd_namespace = desired_namespace; networkorderd_namespace.time_low = SWAP32(networkorderd_namespace.time_low); networkorderd_namespace.time_mid = SWAP16(networkorderd_namespace.time_mid); networkorderd_namespace.time_hi_and_version = SWAP16(networkorderd_namespace.time_hi_and_version); // make a hash of the input desired_namespace (as netord_ns); add the name // (the AP.sf.net namespace as string or the atom name) sha1_init_ctx(&sha_state); sha1_process_bytes((char *)&networkorderd_namespace, sizeof networkorderd_namespace, &sha_state); sha1_process_bytes(name, namelen, &sha_state); sha1_finish_ctx(&sha_state, hash); // quasi uuid sha1hash is network byte ordered. swap the endian members of the // uuid (uint32_t & uint16_t) to local byte order this creates additional // requirements later that have to be APar_endian_uuid_bin_str_conversion() // swapped, but leave this for now for coherence memcpy(out_uuid, hash, sizeof *out_uuid); out_uuid->time_low = SWAP32(out_uuid->time_low); out_uuid->time_mid = SWAP16(out_uuid->time_mid); out_uuid->time_hi_and_version = SWAP16(out_uuid->time_hi_and_version); out_uuid->time_hi_and_version &= 0x0FFF; // mask hash octes 6&7 out_uuid->time_hi_and_version |= (5 << 12); // set bits 12-15 to the version (5 for sha1 namespace/name // hash uuids) out_uuid->clock_seq_hi_and_reserved &= 0x3F; // mask hash octet 8 out_uuid->clock_seq_hi_and_reserved |= 0x80; // Set the two most significant bits (bits 6 and 7) of the // clock_seq_hi_and_reserved to zero and one, respectively. return; } /*---------------------- AP_Create_UUID_ver3_random out_uuid - pointer to the final version 3 randomly generated uuid Use a high quality random number generator (still requiring a seed) to generate a random uuid. In 2.5 million creations, all have been unique (at least on Mac OS X with sranddev providing the initial seed). ----------------------*/ void AP_Create_UUID_ver3_random(ap_uuid_t *out_uuid) { uint32_t rand1 = 0; out_uuid->time_low = xor4096i(); rand1 = xor4096i(); out_uuid->time_mid = (rand1 >> 16) & 0xFFFF; out_uuid->node[0] = (rand1 >> 8) & 0xFF; out_uuid->node[1] = (rand1 >> 0) & 0xFF; rand1 = xor4096i(); out_uuid->node[2] = (rand1 >> 24) & 0xFF; out_uuid->node[3] = (rand1 >> 16) & 0xFF; out_uuid->node[4] = (rand1 >> 8) & 0xFF; out_uuid->node[5] = (rand1 >> 0) & 0xFF; rand1 = xor4096i(); out_uuid->time_hi_and_version = (rand1 >> 16) & 0xFFFF; out_uuid->clock_seq_low = (rand1 >> 8) & 0xFF; out_uuid->clock_seq_hi_and_reserved = (rand1 >> 0) & 0x3F; out_uuid->clock_seq_hi_and_reserved |= 0x40; // bits 6 & 7 must be 0 & 1 respectively out_uuid->time_hi_and_version &= 0x0FFF; out_uuid->time_hi_and_version |= (3 << 12); // set bits 12-15 to the version (3 for peusdo-random/random) return; } /*---------------------- APar_generate_uuid_from_atomname atom_name - the 4 character atom name to create a uuid for uuid_binary_str - the destination for the created uuid (as a hex string) This will create a 16-byte universal unique identifier for any atom name in the AtomicParsley namespace. 1. Make a namespace for all uuids to derive from (here its AP.sf.net) 2. Make a sha1 hash of the namespace string; use that hash as the basis for a v5 uuid into a NULL/blank uuid to create an AP namespace uuid 3. Make a sha2 hash of the atom_name string; use that hash as the basis for a v5 uuid into an AtomicParsley namespace uuid to create the final uuid. 4. copy the uuid structure into a binary string representation ----------------------*/ void APar_generate_uuid_from_atomname(char *atom_name, char *uuid_binary_str) { ap_uuid_t blank_namespace = {0x00000000, 0x0000, 0x0000, 0x00, 0x00, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; ap_uuid_t APar_namespace_uuid; ap_uuid_t AP_atom_uuid; AP_Create_UUID_ver5_sha1_name( &APar_namespace_uuid, blank_namespace, "AtomicParsley.sf.net", 20); AP_Create_UUID_ver5_sha1_name( &AP_atom_uuid, APar_namespace_uuid, atom_name, 4); memset(uuid_binary_str, 0, 20); memcpy(uuid_binary_str, &AP_atom_uuid, sizeof(AP_atom_uuid)); return; } void APar_generate_random_uuid(char *uuid_binary_str) { ap_uuid_t rand_uuid = {0x00000000, 0x0000, 0x0000, 0x00, 0x00, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; AP_Create_UUID_ver3_random(&rand_uuid); memcpy(uuid_binary_str, &rand_uuid, sizeof(rand_uuid)); return; } void APar_generate_test_uuid() { ap_uuid_t blank_ns = {0x00000000, 0x0000, 0x0000, 0x00, 0x00, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; ap_uuid_t APar_ns_uuid; ap_uuid_t APar_test_uuid; AP_Create_UUID_ver5_sha1_name( &APar_ns_uuid, blank_ns, "AtomicParsley.sf.net", 20); APar_print_uuid( &APar_ns_uuid); // should be aa80eaf3-1f72-5575-9faa-de9388dc2a90 fprintf(stdout, "uuid for 'cprt' in AP namespace: "); AP_Create_UUID_ver5_sha1_name(&APar_test_uuid, APar_ns_uuid, "cprt", 4); APar_print_uuid( &APar_test_uuid); //'cprt' should be 4bd39a57-e2c8-5655-a4fb-7a19620ef151 // uuid_t domain_ns_uuid = { 0x6ba7b810, 0x9dad, 0x11d1, // 0x80, // 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 }; // // 6ba7b810-9dad-11d1-80b4-00c04fd430c8 a blank representation of a blank // domain return; } atomicparsley-20240608.083822.1ed9031/tests/000077500000000000000000000000001463107535600175435ustar00rootroot00000000000000atomicparsley-20240608.083822.1ed9031/tests/issue-32.mp4000066400000000000000000000034401463107535600215400ustar00rootroot00000000000000ftypmp42*~mp42iomfreemdatxe@koԷ̲ " iKW׳>;[tiatj-ٲ.g9%GRew^ ̖i][!܊H?i>DPy% 06A j߬!L R7 #_ӢONԥ/I:,Ǜ- P6?] P 6?] 0A_&5ڦY P A  P 60 P 卿0 0A($0koڦY P  !A0 P  %A*0 >moovlvhdЋaЋa__r@trak\tkUdЋaЋa_@0fhdlr mdhdЋaba 4hdlrvideVideo MediaHandlr minfvmhd1infdref url stblstsdavc10HH; AVC CodingmoovlmvhdЋaЋa_L_7@trak\tkhdЋaЋa=Q_@0fmdia dhdЋaЋa 4hdlrvideVideo Media Handler minfvmhdbdinfdref url tblstsdavc10HH AVC Coing \$version, 'help' => \$help, 'print'=> \$print, 'write' => \$write, 'file=s' => \$file, 'copy_warning=s' => \$copy_warning, 'studio=s' => \$studio, 'cast|actors=s' => \@cast, 'directors=s' => \@directors, 'codirectors=s' => \@codirectors, 'screenwriters=s' => \@screenwriters, 'producers=s' => \@producers); #print help if ( $help ) { print << "EOF"; AtomicParsley iTunMOVI writer for Apple TV and iTunes. The options cast|actors, directors, codirectors, screenwriters, producers can take multiple of the same type. You can issue --cast "x person" --cast "y person" or --cast "x person,y person,b person" ------------------------------------------------------------------------------------------------------------- Atom options: --copy_warning: Add copy warning (displayed in iTunes summary page) --studio: Add film studio (displayed on Apple TV) --cast|actors: Add Actors (displayed on Apple TV and iTunes under long description) --directors: Add Directors (displayed on Apple TV and iTunes under long description) --producers: Add Producers (displayed on Apple TV and iTunes under long description) --codirectors: Add Co-Directors (displayed in iTunes under long description) --screenwriters: Add ScreenWriters (displayed in iTunes under long description) Write and print options: --print: Display XML data to screen --file: MP4 file to write XML information to --write: Write XML data to MP4 file with AtomicParlsey Misc options: --version: Displays version --help: Displays help ------------------------------------------------------------------------------------------------------------- If there is more than 1 entry of cast, directors, producers, codirectors, screenwriters then in iTunes the verbage changes to plural. (PRODUCER -> PRODUCERS). Apple TV display items: Actors, Directors, Producers are displayed for Movies. Actors are displayed for TV shows. Studio is displayed on all videos (while video is playing press up arrow 2x, it is located under Title). EOF exit; } # version $version_number = "1.1"; if ( $version ) { print "\n$0 v$version_number\n\n"; print "Written by HolyRoses\n\n"; exit; } # process arrays, join multiple --options of same type and join options seperated by commas @cast = split(/,/,join(',',@cast)); @directors = split(/,/,join(',',@directors)); @producers = split(/,/,join(',',@producers)); @codirectors = split(/,/,join(',',@codirectors)); @screenwriters = split(/,/,join(',',@screenwriters)); # set header $iTunMOVI_data = " "; # set copy-warning $iTunMOVI_data .= " copy-warning $copy_warning "; # set studio $iTunMOVI_data .= " studio $studio "; # assigned a default producer if ($producers[0] eq '') { @producers = ("$ripper"); } # Process cast array if ($cast[0] ne '') { $iTunMOVI_data .= " cast "; } foreach $actor (@cast) { $iTunMOVI_data .= " name $actor "; } if ($cast[0] ne '') { $iTunMOVI_data .= " "; } # Process directors array if ($directors[0] ne '') { $iTunMOVI_data .= " directors "; } foreach $director (@directors) { $iTunMOVI_data .= " name $director "; } if ($directors[0] ne '') { $iTunMOVI_data .= " "; } # Process producers array if ($producers[0] ne '') { $iTunMOVI_data .= " producers "; } foreach $producer (@producers) { $iTunMOVI_data .= " name $producer "; } if ($producers[0] ne '') { $iTunMOVI_data .= " "; } # Process codirectors array if ($codirectors[0] ne '') { $iTunMOVI_data .= " codirectors "; } foreach $codirector (@codirectors) { $iTunMOVI_data .= " name $codirector "; } if ($codirectors[0] ne '') { $iTunMOVI_data .= " "; } # Process screenwriters array if ($screenwriters[0] ne '') { $iTunMOVI_data .= " screenwriters "; } foreach $screenwriter (@screenwriters) { $iTunMOVI_data .= " name $screenwriter "; } if ($screenwriters[0] ne '') { $iTunMOVI_data .= " "; } # set footer $iTunMOVI_data .= " "; # print output if ( $print ) { print "$iTunMOVI_data\n"; } # check if file they want to write to can be opened if ( $file ) { open(FILE, "$file") or die "Cannot open file <$file>: $!"; close(FILE); } # do the AtomicParsley write if ( $file && $write ) { `AtomicParsley \"$file\" --rDNSatom \'$iTunMOVI_data\' name=iTunMOVI domain=com.apple.iTunes --overWrite`; } atomicparsley-20240608.083822.1ed9031/tools/tag-release.sh000077500000000000000000000001501463107535600222650ustar00rootroot00000000000000#!/bin/bash TAGNAME=$(git "show" "-s" "--format=%cd.%h" "--date=format:%Y%m%d.%H%M%S") git tag $TAGNAME