diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..af58e3e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,90 @@ +cmake_minimum_required(VERSION 3.17) +project(AtomicParsley) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +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() + +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() diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..ccfd6fd --- /dev/null +++ b/COPYING @@ -0,0 +1,280 @@ + 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 diff --git a/CREDITS b/CREDITS new file mode 100644 index 0000000..1ded8f8 --- /dev/null +++ b/CREDITS @@ -0,0 +1,48 @@ +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 +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) + + diff --git a/Changes.txt b/Changes.txt new file mode 100644 index 0000000..d982853 --- /dev/null +++ b/Changes.txt @@ -0,0 +1,536 @@ +v0.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. + + diff --git a/README.md b/README.md index d049dce..2c8be02 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,56 @@ -# atomicparsley +# AtomicParsley + +![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wez/atomicparsley/CI) + +## Installation + +### macOS + +* Navigate to the [latest release](https://github.com/wez/atomicparsley/releases/latest) +* Download the `AtomicParsleyMacOS.zip` file and extract `AtomicParsley` + +### Windows + +* Navigate to the [latest release](https://github.com/wez/atomicparsley/releases/latest) +* Download the `AtomicParsleyWindows.zip` file and extract `AtomicParsley.exe` + +### Building 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 +> 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 am +> unlikely to act upon them. -atomicparsley repo (used for Alpine Linux) \ No newline at end of file diff --git a/src/.clang-format b/src/.clang-format new file mode 100644 index 0000000..f8bd03d --- /dev/null +++ b/src/.clang-format @@ -0,0 +1,9 @@ +--- +BasedOnStyle: LLVM +IndentWidth: 2 +BinPackArguments: false +BinPackParameters: false +IncludeIsMainRegex: AtomicParse +--- +Language: Cpp +--- diff --git a/src/AtomDefs.h b/src/AtomDefs.h new file mode 100644 index 0000000..2b35bad --- /dev/null +++ b/src/AtomDefs.h @@ -0,0 +1,389 @@ +//==================================================================// +/* + 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 + +}; diff --git a/src/AtomicParsley.h b/src/AtomicParsley.h new file mode 100644 index 0000000..37fa886 --- /dev/null +++ b/src/AtomicParsley.h @@ -0,0 +1,458 @@ +#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" + +#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, + 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); + +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: diff --git a/src/CDtoc.cpp b/src/CDtoc.cpp new file mode 100644 index 0000000..ec4a5d4 --- /dev/null +++ b/src/CDtoc.cpp @@ -0,0 +1,589 @@ +//==================================================================// +/* + 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 +#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; +} diff --git a/src/CDtoc.h b/src/CDtoc.h new file mode 100644 index 0000000..c498706 --- /dev/null +++ b/src/CDtoc.h @@ -0,0 +1,80 @@ +//==================================================================// +/* + 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); diff --git a/src/ap_types.h b/src/ap_types.h new file mode 100644 index 0000000..2adb82d --- /dev/null +++ b/src/ap_types.h @@ -0,0 +1,384 @@ +#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: + */ diff --git a/src/arrays.cpp b/src/arrays.cpp new file mode 100644 index 0000000..2405076 --- /dev/null +++ b/src/arrays.cpp @@ -0,0 +1,945 @@ +//==================================================================// +/* + 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}, + {"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; +} diff --git a/src/compress.cpp b/src/compress.cpp new file mode 100644 index 0000000..e0a8494 --- /dev/null +++ b/src/compress.cpp @@ -0,0 +1,95 @@ +//==================================================================// +/* + 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; +} diff --git a/src/extracts.cpp b/src/extracts.cpp new file mode 100644 index 0000000..9295c7d --- /dev/null +++ b/src/extracts.cpp @@ -0,0 +1,1785 @@ +//==================================================================// +/* + 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 = (char *)calloc(1, sizeof(char) * 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); + } + free(amr_modes); + amr_modes = NULL; + + } 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); + } + } + return; +} + +/////////////////////////////////////////////////////////////////////////////////////// +// 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, 100); + APar_readX(track_info->track_hdlr_name, + isofile, + parsedAtoms[track->track_atom].AtomicStart + 32, + 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 = (char *)malloc(sizeof(char) * 5); + 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)); + } + } + } + } + return; +} + +// 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 = (char *)calloc(1, sizeof(char) * 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"); + + free(buffer); + buffer = NULL; + return; +} diff --git a/src/extras/getopt.c b/src/extras/getopt.c new file mode 100644 index 0000000..29328d8 --- /dev/null +++ b/src/extras/getopt.c @@ -0,0 +1,981 @@ +/* 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. */ diff --git a/src/extras/getopt.h b/src/extras/getopt.h new file mode 100644 index 0000000..91bef80 --- /dev/null +++ b/src/extras/getopt.h @@ -0,0 +1,127 @@ +/* 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 */ diff --git a/src/extras/getopt1.c b/src/extras/getopt1.c new file mode 100644 index 0000000..2c38073 --- /dev/null +++ b/src/extras/getopt1.c @@ -0,0 +1,87 @@ +/* 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. */ diff --git a/src/iconv.cpp b/src/iconv.cpp new file mode 100644 index 0000000..fdcb382 --- /dev/null +++ b/src/iconv.cpp @@ -0,0 +1,993 @@ +//==================================================================// +/* + 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) || + (*test_str >= 97 && *test_str <= 122) || *test_str == 95 || + (*test_str >= 48 && *test_str <= 57)) { + 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; +} diff --git a/src/id3v2.cpp b/src/id3v2.cpp new file mode 100644 index 0000000..9fa063b --- /dev/null +++ b/src/id3v2.cpp @@ -0,0 +1,3119 @@ +//==================================================================// +/* + 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 genre_str_idx[2]; + genre_str_idx[0] = 0; + genre_str_idx[1] = 0; + genre_str_idx[1] = 0; + sprintf(genre_str_idx, "%u", genre_idx); + APar_FrameDataPut( + targetFrame, genre_str_idx, 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; +} diff --git a/src/id3v2.h b/src/id3v2.h new file mode 100644 index 0000000..fa3e709 --- /dev/null +++ b/src/id3v2.h @@ -0,0 +1,68 @@ +//==================================================================// +/* + 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); diff --git a/src/id3v2defs.h b/src/id3v2defs.h new file mode 100644 index 0000000..8c5de71 --- /dev/null +++ b/src/id3v2defs.h @@ -0,0 +1,597 @@ +//==================================================================// +/* + 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"}}; diff --git a/src/id3v2types.h b/src/id3v2types.h new file mode 100644 index 0000000..afc9b5b --- /dev/null +++ b/src/id3v2types.h @@ -0,0 +1,280 @@ +//==================================================================// +/* + 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; +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..30afa33 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,4482 @@ +//==================================================================// +/* + 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" + "AtomicParlsey sets metadata into MPEG-4 files & derivatives supporting 3 " + "tag\n" + " schemes: iTunes-style, 3GPP assets & ISO defined copyright " + "notifications.\n" + "\n" + "AtomicParlsey 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: + */ diff --git a/src/metalist.cpp b/src/metalist.cpp new file mode 100644 index 0000000..94e5111 --- /dev/null +++ b/src/metalist.cpp @@ -0,0 +1,2163 @@ +//==================================================================// +/* + 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); +} diff --git a/src/nsfile.mm b/src/nsfile.mm new file mode 100644 index 0000000..d673883 --- /dev/null +++ b/src/nsfile.mm @@ -0,0 +1,182 @@ +//==================================================================// +/* + 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; +} diff --git a/src/nsimage.mm b/src/nsimage.mm new file mode 100644 index 0000000..3582287 --- /dev/null +++ b/src/nsimage.mm @@ -0,0 +1,244 @@ +//==================================================================// +/* + 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 + +bool isJPEG=false; +bool isPNG=false; + +void DetermineType(const char *picfilePath) { + char* picHeader = (char*)calloc(1, sizeof(char)*20); + u_int64_t r; + + FILE *pic_file = NULL; + pic_file = fopen(picfilePath, "rb"); + 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; + } + free(picHeader); + picHeader=NULL; + return; +} + +char* DeriveNewPath(const char *filePath, PicPrefs myPicPrefs, char* newpath) { + const char* suffix = strrchr(filePath, '.'); + + size_t filepath_len = strlen(filePath); + memset(newpath, 0, MAXPATHLEN+1); + 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); + } + + if ( (strncmp(suffix,".jpg",4) == 0) || (strncmp(suffix,".jpeg",5) == 0) || (strncmp(suffix,".JPG",4) == 0) || (strncmp(suffix,".JPEG",5) == 0) ) { + isJPEG=true; + } else if ((strncmp(suffix,".png",4) == 0) || (strncmp(suffix,".PNG",4) == 0)) { + isPNG=true; + } + + free(randstring); + randstring=NULL; + return newpath; +} + +bool ResizeGivenImage(const char* filePath, PicPrefs myPicPrefs, char* resized_path) { + bool resize = false; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSImage* source = [ [NSImage alloc] initWithContentsOfFile: [NSString stringWithUTF8String: filePath] ]; + [source setScalesWhenResized: YES]; + 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; + } + + DetermineType(filePath); + 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) { + [NSApplication sharedApplication]; + [[NSGraphicsContext currentContext] setImageInterpolation: NSImageInterpolationHigh]; + + [source setSize: size]; + + NSImage* image = [[NSImage alloc] initWithSize:size]; + [image lockFocus]; + + NSEraseRect( destinationRect ); + [source drawInRect: destinationRect + fromRect: destinationRect + operation: NSCompositeCopy fraction: 1.0]; + + NSBitmapImageRep* bitmap = [ [NSBitmapImageRep alloc] + initWithFocusedViewRect: destinationRect ]; + NSBitmapImageFileType filetype; + NSDictionary *props; + + if ( (isPNG && !myPicPrefs.allJPEG) || myPicPrefs.allPNG) { + filetype = NSPNGFileType; + props = nil; + + } else { + filetype = NSJPEGFileType; + 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 == NSJPEGFileType) ) { + 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++; + } + } + + [bitmap release]; + NSString *outFile= [NSString stringWithUTF8String: DeriveNewPath(filePath, myPicPrefs, resized_path)]; + //NSLog(outFile); + [[NSFileManager defaultManager] + createFileAtPath: outFile + contents: data + attributes: nil ]; + + [image unlockFocus]; + [image release]; + isJPEG=false; + isPNG=false; + memcpy(resized_path, [outFile cStringUsingEncoding: NSUTF8StringEncoding], [outFile lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); + } + [source release]; + [pool release]; + return resize; +} diff --git a/src/parsley.cpp b/src/parsley.cpp new file mode 100644 index 0000000..e9d49bd --- /dev/null +++ b/src/parsley.cpp @@ -0,0 +1,6537 @@ +//==================================================================// +/* + 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 + // http://www.mp4ra.org/filetype.html as registered brands + case 0x4D344220: //'M4B ' + case 0x4D345020: //'M4P ' + case 0x4D345620: //'M4V ' + case 0x4D345648: //'MV4H' + case 0x4D345650: //'M4VP' + 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); + } + + fseek(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) * (atom_length > 16 + ? atom_length + : 16)); // 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); + + char *resized_filepath = (char *)calloc(1, sizeof(char) * MAXPATHLEN + 1); + + if (ResizeGivenImage(artworkPath, myPicturePrefs, resized_filepath)) { + 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, + 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 = (char *)malloc(sizeof(char) * 10); + memset(tfhd_atomFlags_scrap, 0, 10); + // 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); + + // fseek(dest_file, dest_position + file_pos, SEEK_SET); +#if defined(_WIN32) + fpos_t file_offset = dest_position + file_pos; +#elif defined(__GLIBC__) + fpos_t file_offset = {0}; + file_offset.__pos = dest_position + file_pos; +#else + off_t file_offset = dest_position + file_pos; +#endif + fsetpos(dest_file, &file_offset); + 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); +#if defined(_WIN32) + fpos_t file_offset = dest_position + file_pos; +#elif defined(__GLIBC__) + fpos_t file_offset = {0}; + file_offset.__pos = dest_position + file_pos; +#else + off_t file_offset = dest_position + file_pos; +#endif + fsetpos(dest_file, &file_offset); + 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 */ + break; + } + 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 */ + break; + } + fprintf(stderr, + "splice(write): %ld of %lu (%s)\n", + didwrite, + didread, + strerror(errno)); + break; + } + + bytes_written += didwrite; + didread -= didwrite; + } + APar_ShellProgressBar(tally + bytes_written); + } + + 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: diff --git a/src/sha1.cpp b/src/sha1.cpp new file mode 100644 index 0000000..9bdb7ea --- /dev/null +++ b/src/sha1.cpp @@ -0,0 +1,402 @@ +/* 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; + } +} diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..98e302e --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,1107 @@ +//==================================================================// +/* + 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); + } + } +} + +#if defined(_WIN32) + +/////////////////////////////////////////////////////////////////////////////////////// +// Win32 functions // +/////////////////////////////////////////////////////////////////////////////////////// + +#ifndef HAVE_FSEEKO + +int fseeko(FILE *stream, uint64_t pos, int whence) { // only using SEEK_SET here + if (whence == SEEK_SET) { + fpos_t fpos = pos; + return fsetpos(stream, &fpos); + } else { + return -1; + } + return -1; +} + +#endif + +/*---------------------- +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 +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..616bbbc --- /dev/null +++ b/src/util.h @@ -0,0 +1,118 @@ +//==================================================================// +/* + 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); + +#if defined(_WIN32) +#ifndef HAVE_FSEEKO +int fseeko(FILE *stream, uint64_t pos, int whence); +#endif +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(); diff --git a/src/uuid.cpp b/src/uuid.cpp new file mode 100644 index 0000000..1439c65 --- /dev/null +++ b/src/uuid.cpp @@ -0,0 +1,467 @@ +//==================================================================// +/* + 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; +} diff --git a/tools/format-code.sh b/tools/format-code.sh new file mode 100755 index 0000000..dc82ace --- /dev/null +++ b/tools/format-code.sh @@ -0,0 +1,2 @@ +#!/bin/bash +clang-format -i src/*.cpp src/*.h diff --git a/tools/iTunMOVI-1.1.pl b/tools/iTunMOVI-1.1.pl new file mode 100755 index 0000000..ec48a69 --- /dev/null +++ b/tools/iTunMOVI-1.1.pl @@ -0,0 +1,230 @@ +#!/usr/bin/perl +# +# build --rDNSatom iTunMOVI data for AtomicParsley and write or print to file +# +# by HolyRoses +# +# example line from film "Extract" +# http://www.imdb.com/title/tt1225822/fullcredits +# +#./iTunMOVI.pl --cast "Jason Bateman" --cast "Mila Kunis,Kristen Wiig" --directors "Mike Judge" --producers "John Altschuler,Michael Flynn" --studio "Miramax Films" --copy_warning "FBI ANTI-PIRACY WARNING: UNAUTHORIZED COPYING IS PUNISHABLE UNDER FEDERAL LAW." --screenwriters "Mike Judge" --codirectors "Jasmine Alhambra,Maria Mantia" --print + +use Getopt::Long; +#Getopt::Long::Configure ("bundling"); + +# default values for some items. +$ripper = "HolyRoses"; +$copy_warning = "Help control the pet population. Have your pets spayed or neutered."; +$studio = "A $ripper Production"; + +GetOptions ( + 'version' => \$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`; +} diff --git a/tools/tag-release.sh b/tools/tag-release.sh new file mode 100755 index 0000000..c2c677a --- /dev/null +++ b/tools/tag-release.sh @@ -0,0 +1,3 @@ +#!/bin/bash +TAGNAME=$(git "show" "-s" "--format=%cd.%h" "--date=format:%Y%m%d.%H%M%S") +git tag $TAGNAME