atomicparsley-0.9.2~svn110.orig/0000755000000000000000000000000011226374277013405 5ustar atomicparsley-0.9.2~svn110.orig/COPYING0000644000000000000000000003542311226366037014442 0ustar GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS atomicparsley-0.9.2~svn110.orig/README.txt0000644000000000000000000000151711226366037015102 0ustar Basic Instructions If you are building from svn source you will need autoconf & automake (you will definitely need make), otherwise skip to step 2. 1. Create the configure script: % autoconf && autoheader 2. Run the configure script: % ./configure You can check the meager options with ./configure -h 3. Build the program % make 4. Use the program in situ or place it somewhere in your $PATH Dependencies: zlib - used to compress ID3 frames & expand already compressed frames available from http://www.zlib.net -------- Notes: For Mac OS X users: switching between a universal build and a platform dependent build should be accompanied by a "make maint-clean" and a ./configure between builds. To build a Mac OS X universal binary: % make maint-clean % ./configure --enable-universal # makeatomicparsley-0.9.2~svn110.orig/aclocal.m40000644000000000000000000000310711226366037015241 0ustar dnl aclocal.m4: macros autoconf uses when building configure from configure.ac dnl dnl Copyright (C) 2006 puck_lock dnl dnl AtomicParsley is GPL software; you can freely distribute, dnl redistribute, modify & use under the terms of the GNU General dnl Public License; either version 2 or its successor. dnl dnl AtomicParsley is distributed under the GPL "AS IS", without dnl any warranty; without the implied warranty of merchantability dnl or fitness for either an expressed or implied particular purpose. dnl dnl Please see the included GNU General Public License (GPL) for dnl your rights and further details; see the file COPYING. If you dnl cannot, write to the Free Software Foundation, 59 Temple Place dnl Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org dnl dnl dnl Checks for OS. dnl dnl AP_OS_VERSION() dnl AC_DEFUN([AC_OS_VERSION], [ AP_OS_NAME=`uname -s | cut -d '_' -f1` AP_OS_RELVER=`uname -r` AP_NATIVE_ARCH=`arch` AC_SUBST(AP_NATIVE_ARCH) if [ test $AP_NATIVE_ARCH="ppc" || test $AP_NATIVE_ARCH="ppc64" ]; then AP_CROSS_ARCH="i386" AC_SUBST(AP_CROSS_ARCH) else AP_CROSS_ARCH="ppc" AC_SUBST(AP_CROSS_ARCH) fi ]) dnl AP_DARWIN_CHECK_UNIVERSAL_SDK dnl dnl Check for Mac OS X 10.4 Univeral SDK dnl AC_DEFUN([AC_CHECK_DARWIN_UNIVERSAL_SDK], [ if test $AP_OS_NAME="Darwin"; then AC_MSG_CHECKING([for Mac OS X 10.4 Universal SDK]) if test -r /Developer/SDKs/MacOSX10.4u.sdk/; then AC_MSG_RESULT([yes]) DARWIN_U_SYSROOT="/Developer/SDKs/MacOSX10.4u.sdk" AC_SUBST(DARWIN_U_SYSROOT) else AC_MSG_RESULT([no]) fi fi ]) atomicparsley-0.9.2~svn110.orig/win32/0000755000000000000000000000000011226366037014342 5ustar atomicparsley-0.9.2~svn110.orig/win32/AtomicParsley.dsp0000755000000000000000000001774511226366037017647 0ustar # Microsoft Developer Studio Project File - Name="AtomicParsley" - Package Owner=<4> # Microsoft Developer Studio Generated Build File, Format Version 6.00 # ** DO NOT EDIT ** # TARGTYPE "Win32 (x86) Console Application" 0x0103 CFG=AtomicParsley - Win32 Debug !MESSAGE This is not a valid makefile. To build this project using NMAKE, !MESSAGE use the Export Makefile command and run !MESSAGE !MESSAGE NMAKE /f "AtomicParsley.mak". !MESSAGE !MESSAGE You can specify a configuration when running NMAKE !MESSAGE by defining the macro CFG on the command line. For example: !MESSAGE !MESSAGE NMAKE /f "AtomicParsley.mak" CFG="AtomicParsley - Win32 Debug" !MESSAGE !MESSAGE Possible choices for configuration are: !MESSAGE !MESSAGE "AtomicParsley - Win32 Release" (based on "Win32 (x86) Console Application") !MESSAGE "AtomicParsley - Win32 Debug" (based on "Win32 (x86) Console Application") !MESSAGE "AtomicParsley - Win32 zlib Release" (based on "Win32 (x86) Console Application") !MESSAGE # Begin Project # PROP AllowPerConfigDependencies 0 # PROP Scc_ProjName "" # PROP Scc_LocalPath "" CPP=cl.exe RSC=rc.exe !IF "$(CFG)" == "AtomicParsley - Win32 Release" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 0 # PROP BASE Output_Dir "Release" # PROP BASE Intermediate_Dir "Release" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 # PROP Output_Dir "Release" # PROP Intermediate_Dir "Release" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c # ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /U "HAVE_ZLIB_H" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 !ELSEIF "$(CFG)" == "AtomicParsley - Win32 Debug" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 1 # PROP BASE Output_Dir "Debug" # PROP BASE Intermediate_Dir "Debug" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 1 # PROP Output_Dir "Debug" # PROP Intermediate_Dir "Debug" # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c # ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept !ELSEIF "$(CFG)" == "AtomicParsley - Win32 zlib Release" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 0 # PROP BASE Output_Dir "AtomicParsley___Win32_zlib_Release" # PROP BASE Intermediate_Dir "AtomicParsley___Win32_zlib_Release" # PROP BASE Ignore_Export_Lib 0 # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 # PROP Output_Dir "AtomicParsley___Win32_zlib_Release" # PROP Intermediate_Dir "AtomicParsley___Win32_zlib_Release" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c # ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "HAVE_ZLIB_H" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib zdll.lib /nologo /subsystem:console /machine:I386 # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib zdll.lib /nologo /subsystem:console /machine:I386 !ENDIF # Begin Target # Name "AtomicParsley - Win32 Release" # Name "AtomicParsley - Win32 Debug" # Name "AtomicParsley - Win32 zlib Release" # Begin Group "Source Files" # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" # Begin Source File SOURCE=..\src\AP_arrays.cpp # End Source File # Begin Source File SOURCE=..\src\AP_AtomExtracts.cpp # End Source File # Begin Source File SOURCE=..\src\AP_CDTOC.cpp # End Source File # Begin Source File SOURCE=..\src\AP_commons.cpp # End Source File # Begin Source File SOURCE=..\src\AP_iconv.cpp # End Source File # Begin Source File SOURCE=..\src\AP_ID3v2_tags.cpp # End Source File # Begin Source File SOURCE=..\src\AP_MetadataListings.cpp # End Source File # Begin Source File SOURCE=..\src\APar_sha1.cpp # End Source File # Begin Source File SOURCE=..\src\APar_uuid.cpp # End Source File # Begin Source File SOURCE=..\src\APar_zlib.cpp # End Source File # Begin Source File SOURCE=..\src\AtomicParsley.cpp # End Source File # Begin Source File SOURCE=..\src\extras\getopt.c # End Source File # Begin Source File SOURCE=..\src\extras\getopt1.c # End Source File # Begin Source File SOURCE=..\src\main.cpp # End Source File # End Group # Begin Group "Header Files" # PROP Default_Filter "h;hpp;hxx;hm;inl" # Begin Source File SOURCE=..\src\AP_arrays.h # End Source File # Begin Source File SOURCE=..\src\AP_AtomDefinitions.h # End Source File # Begin Source File SOURCE=..\src\AP_AtomExtracts.h # End Source File # Begin Source File SOURCE=..\src\AP_CDTOC.h # End Source File # Begin Source File SOURCE=..\src\AP_commons.h # End Source File # Begin Source File SOURCE=..\src\AP_iconv.h # End Source File # Begin Source File SOURCE=..\src\AP_ID3v2_Definitions.h # End Source File # Begin Source File SOURCE=..\src\AP_ID3v2_FrameDefinitions.h # End Source File # Begin Source File SOURCE=..\src\AP_ID3v2_tags.h # End Source File # Begin Source File SOURCE=..\src\AP_MetadataListings.h # End Source File # Begin Source File SOURCE=..\src\APar_sha1.h # End Source File # Begin Source File SOURCE=..\src\APar_uuid.h # End Source File # Begin Source File SOURCE=..\src\APar_zlib.h # End Source File # Begin Source File SOURCE=..\src\AtomicParsley.h # End Source File # Begin Source File SOURCE=..\src\extras\getopt.h # End Source File # End Group # Begin Group "Resource Files" # PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" # End Group # End Target # End Project atomicparsley-0.9.2~svn110.orig/win32/MSVC_compiling.txt0000644000000000000000000000467611226366037017731 0ustar To compile AtomicParsley on Windows using Microsoft Visual C++ 6.0: admit that you are colluding with the evil empire by purchasing their products. Flog thyself often & repeatedly. install zlib zlib compression is optionally used in id3 tags on mpeg-4 files. Download the win32 zlib compiled dll (the last of the 4 listed) at http://www.zlib.net. Place the zlib123-dll folder inside the AtomicParsley/src/extras folder. Copy the zlib1.dll to your system32 directory. configure MSVC to use a local zlib From the Tools menu choose "Options" and select the "Directories" tab in the Options window. On the right it will say "Show directories for" and have "Include files" selected. On the lower portion, click on a new entry and locate the zlib include folder. It will be something like AtomicParsley/src/extras/zlib123-dll/include Switch from "Include files" to "Library Files" and create a new entry. The folder to add will be the lib folder - something like this: AtomicParsley/src/extras/zlib123-dll/lib Hit the ok button to close the options window. create a msvc project in MSVC choose File->New... & select "Win32 Console Application". Use the "Empty Project" setting. Name it something like AtomicParsley or "whatever". Select the tab on the left side 2/3 of the way down called "File View". Right click on the "Source Fies" section in the FileView tab. Add the *.cpp source files in AtomicParsley/src to the project. Also, add the *.c files in AtomicParsley/src/extras (the getopt sources). Right click on the "Header Files" section in the FileView tab. Add all the header files (*.h) in the AtomicParsley/src to the project. Also add the getopt.h header file in AtomicParsley/src/extras. enable zlib compression in AtomicParsley In the FileView tab, select the top level workspace (1 project). From the Project menu choose Settings. In the Link tab of that window, there is a spot labeled "C/C++". In the space labeled "Preprocessor definitions, add a comma, "HAVE_ZLIB_H" (without the quotes). It should look like this: WIN32,NDEBUG,_CONSOLE,_MBCS,HAVE_ZLIB_H Switch to the tab labeled "Object/Library modules". At the very end of this, add a space and "zdll.lib" (without the quotes). Hit the okay button. build AtomicParsley.exe From the Build menu, choose "Build whatever.exe". There will be dozens of warnings - those can be ignored. ------------------ If something went wrong, please realize this was written on a Mac by a Mac user.atomicparsley-0.9.2~svn110.orig/configure.ac0000644000000000000000000000677711226366037015707 0ustar dnl Process this file with autoconf to produce a configure script. dnl Some intro checks and defines AC_INIT(AtomicParsley,AP_VER,pu@me.com) AC_REVISION ($Id: configure.ac,v 1.14 2006/11/23 12:00:00 PU Exp $) AC_PREREQ(2.50) AC_CONFIG_SRCDIR(src/AtomicParsley.cpp) AC_CONFIG_HEADER(src/config.h) PACKAGE=atomicparsley AP_MAJOR_VERSION=0 AP_MINOR_VERSION=9 AP_MICRO_VERSION=2 AP_VER=\"$AP_MAJOR_VERSION.$AP_MINOR_VERSION.$AP_MICRO_VERSION\" AC_DEFINE_UNQUOTED(AP_VER,$AP_VER,[Define program version]) dnl--------------------------------------------------------- dnl Checks for programs. AC_PROG_CXX AC_PROG_MAKE_SET dnl--------------------------------------------------------- dnl Checks for header files. AC_HEADER_DIRENT AC_HEADER_STDC AC_HEADER_SYS_WAIT AC_CHECK_HEADERS([stdio.h string.h stdlib.h time.h math.h signal.h \ wchar.h \ stddef.h \ sys/types.h sys/stat.h errno.h \ zlib.h]) AC_CHECK_HEADERS(getopt.h, [HAVE_GETOPT_H=1; AC_SUBST(HAVE_GETOPT_H)]) AC_CHECK_HEADERS(linux/cdrom.h) dnl--------------------------------------------------------- dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST dnl--------------------------------------------------------- dnl Checks for library functions. AC_CHECK_FUNCS([fseeko fsetpos memset memcmp remove rename sranddev sscanf strdup strerror strftime strncmp strncasecmp strrchr strsep strstr strtol wmemset]) AC_FUNC_MALLOC AC_CHECK_FUNCS(lroundf) dnl--------------------------------------------------------- dnl Checks for libraries. AC_CHECK_LIB(z, deflateEnd, [HAVE_LIBZ=1; AC_SUBST(HAVE_LIBZ)]) dnl--------------------------------------------------------- dnl OS checks. dnl from aclocal.m4 AC_OS_VERSION os_name=$AP_OS_NAME AC_SUBST(os_name) AC_DEFINE_UNQUOTED(os_name, $AP_OS_NAME, [OS Platform name]) if test "$os_name" = "Darwin" ; then HAVE_DARWIN_PLATFORM="true" AC_SUBST(HAVE_DARWIN_PLATFORM) fi dnl--------------------------------------------------------- dnl Mac OS X Universal Build AC_ARG_ENABLE(universal, dnl [ --disable-universal build a universal binary on Mac OS X [default=yes]], universal=$enableval, universal=no) if test "$universal" = "yes" ; then AC_CHECK_DARWIN_UNIVERSAL_SDK AC_SUBST(universal) fi dnl--------------------------------------------------------- AC_ARG_ENABLE(debug, dnl [ --disable-debug_build do not build a debug version [default=yes]], debug=$enableval, debug=no) if test "$debug" = "yes" ; then AC_DEFINE_UNQUOTED(DEBUG, $debug, [build binary with debug output]) AC_SUBST(debug) fi dnl--------------------------------------------------------- dnl Done echo "Creating files" AC_OUTPUT([Makefile src/Makefile]) echo dnl Pretty-print status message echo "+----------------------------------------------+" echo "| SUCCESS |" echo "+----------------------------------------------+" echo " AtomicParsley has been configured, you should" echo " now type 'make' to compile AtomicParsley." echo echo "+----------------------------------------------+" echo "| YOUR CONFIGURATION |" echo "+----------------------------------------------+" echo " Version: $AP_MAJOR_VERSION.$AP_MINOR_VERSION.$AP_MICRO_VERSION" if test "$universal" = "no" ; then echo " MacOSX universal build: disabled" else echo " MacOSX universal build: enabled" fi if test "$debug" = "no" ; then echo " debug build: disabled" else echo " debug build: enabled" fi echo atomicparsley-0.9.2~svn110.orig/Makefile.in0000644000000000000000000000347711226366037015460 0ustar # Copyright (C) 1994, 1995-8, 1999 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. SHELL = @SHELL@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ VPATH = @srcdir@ prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ datadir = @datadir@ exec_prefix = @exec_prefix@ includedir = @includedir@ infodir = @infodir@ libdir = @libdir@ libexecdir = @libexecdir@ localstatedir = @localstatedir@ mandir = @mandir@ oldincludedir = @oldincludedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sysconfdir = @sysconfdir@ @SET_MAKE@ CC = @CC@ CFLAGS = @CFLAGS@ CPPFLAGS = @CPPFLAGS@ DEFS = @DEFS@ LIBS = @LIBS@ LDFLAGS = @LDFLAGS@ INSTALL = @INSTALL@ SUBDIRS = src all: @for i in $(SUBDIRS); do \ echo "Making all in $$i ..."; \ cd $$i; $(MAKE) all; \ cd ..; \ done clean: @for i in $(SUBDIRS); do \ echo "Making clean in $$i ..."; \ cd $$i; $(MAKE) clean; \ cd ..; \ done install: uninstall: maint-clean: @for i in $(SUBDIRS); do \ echo "Making maint-clean in $$i ..."; \ cd $$i; $(MAKE) maint-clean; \ cd ..; \ done rm -f config.cache config.h config.log config.status *~ Makefile rm -f src/Makefile rm -rf obj_dir rm -rf autom4te.cache rm -rf AtomicParsley AtomicParsley_ppc AtomicParsley_i386 dist-clean: .PHONY: all clean install dist-clean maint-clean # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: atomicparsley-0.9.2~svn110.orig/src/0000755000000000000000000000000011226366037014167 5ustar atomicparsley-0.9.2~svn110.orig/src/AP_commons.cpp0000644000000000000000000007122011226366037016730 0ustar //==================================================================// /* AtomicParsley - AP_commons.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 2006-2007 puck_lock ---------------------- Code Contributions by: * SLarew - prevent writing past array in Convert_multibyteUTF16_to_wchar bugfix */ //==================================================================// #include #include #include #include #include #if defined (WIN32) #include #else #include #endif #include "AP_commons.h" #include "AP_iconv.h" #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). ----------------------*/ off_t findFileSize(const char *utf8_filepath) { if ( IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { #if defined (_MSC_VER) wchar_t* utf16_filepath = Convert_multibyteUTF8_to_wchar(utf8_filepath); struct _stat fileStats; _wstat(utf16_filepath, &fileStats); free(utf16_filepath); utf16_filepath = NULL; return fileStats.st_size; #endif } else { 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 ( IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { #if defined (_MSC_VER) 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(utf16_filepath); utf16_filepath = NULL; #endif } else { aFile = fopen(utf8_filepath, file_flags); } if (!aFile) { fprintf(stdout, "AP error trying to fopen: %s\n", 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 != NULL) { file_opened = true; } } else { fclose(source_file); file_opened = false; } 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 { fclose(a_file); } } #if defined (_MSC_VER) /////////////////////////////////////////////////////////////////////////////////////// // Win32 functions // /////////////////////////////////////////////////////////////////////////////////////// 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 // http://www.flipcode.com/articles/article_advstrings01.shtml bool IsUnicodeWinOS() { #if defined (_MSC_VER) OSVERSIONINFOW os; memset(&os, 0, sizeof(OSVERSIONINFOW)); os.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); return (GetVersionExW(&os) != 0); #else return false; #endif } /////////////////////////////////////////////////////////////////////////////////////// // File reading routines // /////////////////////////////////////////////////////////////////////////////////////// uint8_t APar_read8(FILE* ISObasemediafile, uint32_t pos) { uint8_t a_byte = 0; fseeko(ISObasemediafile, pos, SEEK_SET); fread(&a_byte, 1, 1, ISObasemediafile); return a_byte; } uint16_t APar_read16(char* buffer, FILE* ISObasemediafile, uint32_t pos) { fseeko(ISObasemediafile, pos, SEEK_SET); fread(buffer, 1, 2, ISObasemediafile); return UInt16FromBigEndian(buffer); } uint32_t APar_read32(char* buffer, FILE* ISObasemediafile, uint32_t pos) { fseeko(ISObasemediafile, pos, SEEK_SET); fread(buffer, 1, 4, ISObasemediafile); return UInt32FromBigEndian(buffer); } uint64_t APar_read64(char* buffer, FILE* ISObasemediafile, uint32_t pos) { fseeko(ISObasemediafile, pos, SEEK_SET); fread(buffer, 1, 8, ISObasemediafile); return UInt64FromBigEndian(buffer); } void APar_readX(char* buffer, FILE* ISObasemediafile, uint32_t pos, uint32_t length) { fseeko(ISObasemediafile, pos, SEEK_SET); fread(buffer, 1, length, ISObasemediafile); 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 = (uint32_t)fread(destination_buffer, 1, (size_t)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, uint32_t start_position, uint32_t eval_number) { uint32_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 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 // /////////////////////////////////////////////////////////////////////////////////////// #if (!defined HAVE_LROUNDF) && (!defined (__GLIBC__)) int lroundf(float a) { return a/1; } #endif #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 = 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; uint32_t reduced_seconds = (uint32_t)total_secs; strftime(*&utc_time, 50 , "%a %b %d %H:%M:%S %Y", gmtime((time_t*)&reduced_seconds) ); return *&utc_time; } } return *&utc_time; } uint32_t APar_get_mpeg4_time() { #if defined(WIN32) FILETIME file_time; uint64_t wintime = 0; GetSystemTimeAsFileTime (&file_time); wintime = (((uint64_t) file_time.dwHighDateTime << 32) | file_time.dwLowDateTime) / 10000000; wintime -= 9561628800; 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 ); //just to be sure there will be a trailing NULL wmemset(utf16_data, 0, glyph_length); 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? 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); return Convert_multibyteUTF16_to_wchar((char*)utf16_conversion, (size_t)utf_16_glyphs, false ); } //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, 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 */ unsigned long xor4096i() { /* 32-bit or 64-bit integer random number generator with period at least 2**4096-1. It is assumed that "UINT" 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(UINT) = 32 or 64. R. P. Brent, 20060628. */ /* UINT64 is TRUE if 64-bit UINT, UINT32 is TRUE otherwise (assumed to be 32-bit UINT). */ #define UINT64 (sizeof(UINT)>>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) UINT seed = 0; static UINT w, weyl, zero = 0, x[r]; UINT 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 = (UINT)(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 = ((((UINT)0x61c88646)<<16)<<16) + (UINT)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; (UINT)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<>b; v = x[(i+(r-s))&(r-1)]; 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<>b; /* (I + L^a)(I + R^b) */ v ^= v<>d; /* (I + L^c)(I + R^d) */ x[i] = (v ^= t); /* Update circular array */ w += weyl; /* Update Weyl generator */ return (v + (w^(w>>ws))); /* Return combination */ #undef UINT64 #undef UINT32 #undef wlen #undef r #undef s #undef a #undef b #undef c #undef d #undef ws } atomicparsley-0.9.2~svn110.orig/src/AP_AtomDefinitions.h0000644000000000000000000004222111226366037020015 0ustar //==================================================================// /* AtomicParsley - AP_AtomDefinitions.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 */ //==================================================================// atomDefinition KnownAtoms[] = { //name parent atom(s) container number box_type {"<()>", {"_ANY_LEVEL"}, UNKNOWN_ATOM_TYPE, UKNOWN_REQUIREMENTS, UNKNOWN_ATOM }, //our unknown atom (self-defined) {"ftyp", {"FILE_LEVEL"}, CHILD_ATOM, REQUIRED_ONCE, SIMPLE_ATOM }, {"moov", {"FILE_LEVEL"}, PARENT_ATOM, REQUIRED_ONCE, SIMPLE_ATOM }, {"mdat", {"FILE_LEVEL"}, CHILD_ATOM, OPTIONAL_MANY, SIMPLE_ATOM }, {"pdin", {"FILE_LEVEL"}, CHILD_ATOM, OPTIONAL_ONCE, VERSIONED_ATOM }, {"moof", {"FILE_LEVEL"}, PARENT_ATOM, OPTIONAL_MANY, SIMPLE_ATOM }, {"mfhd", {"moof"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {"traf", {"moof"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"tfhd", {"traf"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {"trun", {"traf"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {"mfra", {"FILE_LEVEL"}, PARENT_ATOM, OPTIONAL_ONCE, SIMPLE_ATOM }, {"tfra", {"mfra"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"mfro", {"mfra"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {"free", {"_ANY_LEVEL"}, CHILD_ATOM, OPTIONAL_MANY, SIMPLE_ATOM }, {"skip", {"_ANY_LEVEL"}, CHILD_ATOM, OPTIONAL_MANY, SIMPLE_ATOM }, {"uuid", {"_ANY_LEVEL"}, CHILD_ATOM, REQUIRED_ONCE, EXTENDED_ATOM }, {"mvhd", {"moov"}, CHILD_ATOM, REQUIRED_ONCE, VERSIONED_ATOM }, {"iods", {"moov"}, CHILD_ATOM, OPTIONAL_ONCE, VERSIONED_ATOM }, {"drm ", {"moov"}, CHILD_ATOM, OPTIONAL_ONCE, VERSIONED_ATOM }, // 3gp/MobileMP4 {"trak", {"moov"}, PARENT_ATOM, OPTIONAL_MANY, SIMPLE_ATOM }, {"tkhd", {"trak"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM }, {"tref", {"trak"}, PARENT_ATOM, OPTIONAL_MANY, SIMPLE_ATOM }, {"mdia", {"trak"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"tapt", {"trak"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"clef", {"tapt"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"prof", {"tapt"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"enof", {"tapt"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"mdhd", {"mdia"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"minf", {"mdia"}, PARENT_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"hdlr", {"mdia", "meta", "minf"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, //minf parent present in chapterized {"vmhd", {"minf"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"smhd", {"minf"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"hmhd", {"minf"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"nmhd", {"minf"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"gmhd", {"minf"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, //present in chapterized {"dinf", {"minf", "meta"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //required in minf {"dref", {"dinf"}, DUAL_STATE_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {"url ", {"dref"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM }, {"urn ", {"dref"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM }, {"alis", {"dref"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM }, {"cios", {"dref"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM }, {"stbl", {"minf"}, PARENT_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"stts", {"stbl"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {"ctts", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"stsd", {"stbl"}, DUAL_STATE_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {"stsz", {"stbl"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"stz2", {"stbl"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"stsc", {"stbl"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {"stco", {"stbl"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"co64", {"stbl"}, CHILD_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"stss", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"stsh", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"stdp", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"padb", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"sdtp", {"stbl", "traf"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"sbgp", {"stbl", "traf"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM }, {"sbgp", {"stbl"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM }, {"stps", {"stbl"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"edts", {"trak"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"elst", {"edts"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"udta", {"moov", "trak"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"meta", {"FILE_LEVEL", "moov", "trak", "udta"}, DUAL_STATE_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, //optionally contains info {"mvex", {"moov"}, PARENT_ATOM, OPTIONAL_ONCE, SIMPLE_ATOM }, {"mehd", {"mvex"}, CHILD_ATOM, OPTIONAL_ONCE, VERSIONED_ATOM }, {"trex", {"mvex"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, //{"stsl", {"????"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, //contained by a sample entry box {"subs", {"stbl", "traf"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"xml ", {"meta"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"bxml", {"meta"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"iloc", {"meta"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"pitm", {"meta"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"ipro", {"meta"}, PARENT_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"iinf", {"meta"}, DUAL_STATE_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"infe", {"iinf"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"sinf", {"ipro", "drms", "drmi"}, PARENT_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, //parent atom is also "Protected Sample Entry" {"frma", {"sinf"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"imif", {"sinf"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"schm", {"sinf", "srpp"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"schi", {"sinf", "srpp"}, DUAL_STATE_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"skcr", {"sinf"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"user", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"key ", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, //could be required in 'drms'/'drmi' {"iviv", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"righ", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"name", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"priv", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"iKMS", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, // 'iAEC', '264b', 'iOMA', 'ICSD' {"iSFM", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"iSLT", {"schi"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //boxes with 'k***' are also here; reserved {"IKEY", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"hint", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"dpnd", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"ipir", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"mpod", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"sync", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"chap", {"tref"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //?possible versioned? {"ipmc", {"moov", "meta"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, {"tims", {"rtp "}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"tsro", {"rtp "}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"snro", {"rtp "}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"srpp", {"srtp"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {"hnti", {"udta"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"rtp ", {"hnti"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //'rtp ' is defined twice in different containers {"sdp ", {"hnti"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"hinf", {"udta"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"name", {"udta"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"trpy", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"nump", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"tpyl", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"totl", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"npck", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"maxr", {"hinf"}, CHILD_ATOM, OPTIONAL_MANY, SIMPLE_ATOM }, {"dmed", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"dimm", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"drep", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"tmin", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"tmax", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"pmax", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"dmax", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"payt", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"tpay", {"hinf"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"drms", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"drmi", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"alac", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"mp4a", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"mp4s", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"mp4v", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"avc1", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"avcp", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"text", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"jpeg", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"tx3g", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"rtp ", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, //"rtp " occurs twice; disparate meanings {"srtp", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, SIMPLE_ATOM }, {"enca", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"encv", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"enct", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"encs", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"samr", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"sawb", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"sawp", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"s263", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"sevc", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"sqcp", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"ssmv", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"tmcd", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, {"mjp2", {"stsd"}, DUAL_STATE_ATOM, REQ_FAMILIAL_ONE, VERSIONED_ATOM }, //mjpeg2000 {"alac", {"alac"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"avcC", {"avc1", "drmi"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"damr", {"samr", "sawb"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"d263", {"s263"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"dawp", {"sawp"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"devc", {"sevc"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"dqcp", {"sqcp"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"dsmv", {"ssmv"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"bitr", {"d263"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, {"btrt", {"avc1"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //found in NeroAVC {"m4ds", {"avc1"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //?possible versioned? {"ftab", {"tx3g"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, {"jp2h", {"mjp2"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //mjpeg2000 {"ihdr", {"jp2h"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //mjpeg2000 {"colr", {"jp2h"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, //mjpeg2000 {"fiel", {"mjp2"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //mjpeg2000 {"jp2p", {"mjp2"}, CHILD_ATOM, OPTIONAL_ONE, VERSIONED_ATOM }, //mjpeg2000 {"jsub", {"mjp2"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //mjpeg2000 {"orfo", {"mjp2"}, CHILD_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //mjpeg2000 {"cprt", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, //the only ISO defined metadata tag; also a 3gp asset {"titl", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, //3gp assets {"auth", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, {"perf", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, {"gnre", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, {"dscp", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, {"albm", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, {"yrrc", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM }, {"rtng", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, {"clsf", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, {"kywd", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, {"loci", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, {"ID32", {"meta"}, CHILD_ATOM, OPTIONAL_MANY, PACKED_LANG_ATOM }, //id3v2 tag {"tsel", {"udta"}, CHILD_ATOM, OPTIONAL_MANY, SIMPLE_ATOM }, //but only at track level in a 3gp file //{"chpl", {"udta"}, CHILD_ATOM, OPTIONAL_ONCE, VERSIONED_ATOM }, //Nero - seems to be versioned //{"ndrm", {"udta"}, CHILD_ATOM, OPTIONAL_ONCE, VERSIONED_ATOM }, //Nero - seems to be versioned //{"tags", {"udta"}, CHILD_ATOM, OPTIONAL_ONCE, SIMPLE_ATOM }, //Another Nero-Creation // ...so if they claim that "tags doesn't have any children", // why does nerotags.exe say "tshd atom"? If 'tags' doesn't // have any children, then tshd can't be an atom.... // Clearly, they are EternallyRight and everyone else is // always wrong. //Pish! Seems that Nero is simply unable to register any atoms. {"ilst", {"meta"}, PARENT_ATOM, OPTIONAL_ONCE, SIMPLE_ATOM }, //iTunes metadata container {"----", {"ilst"}, PARENT_ATOM, OPTIONAL_MANY, SIMPLE_ATOM }, //reverse dns metadata {"mean", {"----"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {"name", {"----"}, CHILD_ATOM, REQUIRED_ONE, VERSIONED_ATOM }, {".><.", {"dref"}, CHILD_ATOM, OPTIONAL_MANY, VERSIONED_ATOM }, //support any future named child to dref; keep 4th from end; manual return {"esds", {"SAMPLE_DESC"}, CHILD_ATOM, REQUIRED_ONE, SIMPLE_ATOM }, //multiple parents; keep 3rd from end; manual return {"(..)", {"ilst"}, PARENT_ATOM, OPTIONAL_ONE, SIMPLE_ATOM }, //multiple parents; keep 2nd from end; manual return {"data", {"ITUNES_METADATA"}, CHILD_ATOM, PARENT_SPECIFIC, VERSIONED_ATOM } //multiple parents }; atomicparsley-0.9.2~svn110.orig/src/APar_sha1.h0000644000000000000000000000703411226366037016103 0ustar /* Declarations of functions and data types used for SHA1 sum library functions. Copyright (C) 2000, 2001, 2003, 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. */ /* 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 : md5_uint32 typedef file renaming compiler attribute directives eliminated to msvc */ #include #include "AP_commons.h" 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)))); }; /* 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); atomicparsley-0.9.2~svn110.orig/src/AP_ID3v2_Definitions.h0000644000000000000000000002054611226366037020111 0ustar //==================================================================// /* AtomicParsley - AP_ID3v2_Definitions.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 */ //==================================================================// 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 { char* ID3V2p2_FrameID; char* ID3V2p3_FrameID; char* ID3V2p4_FrameID; char* ID3V2_FrameDescription; char* CLI_frameIDpreset; int ID3v2_InternalFrameID; int ID3v2_FrameType; } ID3FrameDefinition; typedef struct { char* image_mimetype; char* image_fileextn; uint8_t image_testbytes; char* image_binaryheader; } ImageFileFormatDefinition; typedef struct { uint8_t hexcode; char* hexstring; char* imagetype_str; } ID3ImageType; // Structure that defines how any ID3v2FrameType is constructed, listing an array of its constituent ID3_FieldTypes typedef struct { ID3v2FrameType ID3_FrameType; uint8_t ID3_FieldCount; ID3_FieldTypes ID3_FieldComponents[5]; //max known to be tested } ID3v2FieldDefinition; struct ID3v2Fields { int ID3v2_Field_Type; uint32_t field_length; uint32_t alloc_length; char* field_string; ID3v2Fields* next_field; }; struct ID3v2Frame { char ID3v2_Frame_Namestr[5]; uint32_t ID3v2_Frame_Length; //this is the real length, not a syncsafe int; note: does not include frame ID (like 'TIT2', 'TCO' - 3or4 bytes) or frame flags (2bytes) uint16_t ID3v2_Frame_Flags; //these next 2 values can be potentially be stored based on bitsetting in frame flags; uint8_t ID3v2_Frame_GroupingSymbol; uint32_t ID3v2_Frame_ExpandedLength; int ID3v2_Frame_ID; int ID3v2_FrameType; uint8_t ID3v2_FieldCount; uint8_t textfield_tally; ID3v2Fields* ID3v2_Frame_Fields; //malloc ID3v2Frame* ID3v2_NextFrame; bool eliminate_frame; }; struct ID3v2Tag { uint8_t ID3v2Tag_MajorVersion; uint8_t ID3v2Tag_RevisionVersion; uint8_t ID3v2Tag_Flags; uint32_t ID3v2Tag_Length; //this is a bonafide uint_32_t length, not a syncsafe int //this extended header section depends on a bitsetting in ID3v2Tag_Flags uint32_t ID3v2_Tag_ExtendedHeader_Length; //the entire extended header section is unimplemented flags & flag frames ID3v2Frame* ID3v2_FirstFrame; ID3v2Frame* ID3v2_FrameList; uint16_t ID3v2_FrameCount; bool modified_tag; }; atomicparsley-0.9.2~svn110.orig/src/AP_ID3v2_tags.cpp0000644000000000000000000030344011226366037017124 0ustar //==================================================================// /* AtomicParsley - AP_ID3v2_tags.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 2006-2007 puck_lock */ //==================================================================// #include #include #include #include "AtomicParsley.h" #include "AP_commons.h" #include "AP_ID3v2_tags.h" #include "APar_zlib.h" #include "AP_iconv.h" #include "APar_uuid.h" #include "AP_CDTOC.h" #include "AP_arrays.h" #include "AP_ID3v2_FrameDefinitions.h" #if defined HAVE_CONFIG_H #include "config.h" #endif 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 (%u>%u). 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 // /////////////////////////////////////////////////////////////////////////////////////// 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() { char* frametypestr = NULL; 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 (DARWIN_PLATFORM) " AudioCD ID frame : disk(num)\n" #elif defined (HAVE_LINUX_CDROM_H) " AudioCD ID frame : (/path)\n" #elif defined (WIN32) " AudioCD ID frame : (num)\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; } char* ConvertCLIFrameStr_TO_frameID(char* frame_str) { char* discovered_frameID = NULL; uint16_t total_known_frames = (uint16_t)(sizeof(KnownFrames)/sizeof(*KnownFrames)); uint8_t frame_str_len = strlen(frame_str) + 1; for (uint16_t i = 0; i < total_known_frames; i++) { if (memcmp(KnownFrames[i].CLI_frameIDpreset, frame_str, frame_str_len) == 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++) { 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(char* frame_str) { char* eval_framestr = NULL; int frame_type = 0; uint16_t total_known_frames = (uint16_t)(sizeof(KnownFrames)/sizeof(*KnownFrames)); uint8_t frame_str_len = strlen(frame_str) + 1; 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 (memcmp(frame_str, eval_framestr, frame_str_len) == 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, "%u, %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) { uint32_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 || 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 || 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 || 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 %u\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 %u bytes long (+%u)\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 %u 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 %u 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 uint32_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 uint32_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, 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 = (uint32_t)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, %u bytes were allocated.\n", this_field->ID3v2_Field_Type, byte_allocation); return; } void APar_FrameInit(ID3v2Frame* aFrame, char* frame_str, int frameID, uint8_t frame_comp_list, 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, 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 ((uint32_t)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 += (uint32_t)converted_bytes; bytes_used = converted_bytes; //fprintf(stdout, "string %s, %u=%u\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 * (uint32_t)string_length + thisField->field_length > thisField->alloc_length) { APar_realloc_memcpy(thisField, (2 * (uint32_t)string_length + thisField->field_length > 2000 ? 2 * (uint32_t)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 += (uint32_t)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; uint32_t bom_offset = 0; if (2 * (uint32_t)string_length + thisField->field_length > thisField->alloc_length) { //important: realloc before BOM testing!!! APar_realloc_memcpy(thisField, (2 * (uint32_t)string_length + thisField->field_length > 2000 ? 2 * (uint32_t)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 += (uint32_t)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, 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 uint32_t file_length = (uint32_t)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, 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) { char* derived_filename = NULL; #if defined (WIN32) derived_filename = strrchr(frame_payload, '\\'); #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[15]; memset(play_count_syncsafe, 0, 16); if (memcmp(frame_payload, "+1", 3) == 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, "%llu", &playcount); } if (playcount < 268435455) { convert_to_syncsafe32((uint32_t)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[15]; memset(popm_play_count_syncsafe, 0, 16); if (adjunct_payload->ratingArg != NULL) { sscanf(adjunct_payload->ratingArg, "%hhu", &popm_rating); } 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, "%llu", &popm_playcount); } } } if (popm_playcount > 0) { if (popm_playcount < 268435455) { convert_to_syncsafe32((uint32_t)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(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; 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 (memcmp(adjunct_payloads->pictypeArg, img_comparison_str, img_typlen) == 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; uint32_t utf8maxalloc = 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, 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 (memcmp(adjunct_payloads->descripArg, utf8_descrip, strlen(adjunct_payloads->descripArg)) == 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, char* frame_str, char* frame_payload, AdjunctArgs* adjunct_payloads, uint8_t str_encoding) { ID3v2Frame* targetFrame = NULL; ID3v2Frame* eval_frame = 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, "%hhu", 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->field_string != NULL ) { free( afield->field_string ); afield->field_string = NULL; } freefield = afield; afield = afield->next_field; if (afield == NULL) break; if (aframe->ID3v2_Frame_Fields+id3fld != freefield) free(freefield); } } free( aframe->ID3v2_Frame_Fields ); aframe->ID3v2_Frame_Fields = NULL; free(aframe); aframe = aframe->ID3v2_NextFrame; } return; } atomicparsley-0.9.2~svn110.orig/src/AP_MetadataListings.cpp0000644000000000000000000020426611226366037020522 0ustar //==================================================================// /* AtomicParsley - AP_MetadataListings.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 2006-2007 puck_lock */ //==================================================================// #include #include #if defined (WIN32) #include #endif #include "AP_commons.h" #include "AP_iconv.h" #include "AtomicParsley.h" #include "AP_ID3v2_tags.h" #include "AP_arrays.h" #include "APar_uuid.h" #include "AP_MetadataListings.h" bool BOM_printed = false; uint32_t APar_ProvideTallyForAtom(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 += (uint32_t)file_size - parsedAtoms[iter].AtomicStart; } else if (parsedAtoms[iter].AtomicLength == 1) { tally_for_atom += (uint32_t)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 (_MSC_VER) 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 (_MSC_VER) 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 ( wmemcmp(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(char* utf8_encoded_data) { #if defined (_MSC_VER) 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, 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 (memcmp(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 (_MSC_VER) 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; uint32_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) { 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) ); fseeko(source_file, uuid_atom->AtomicStart + 36, SEEK_SET); fread(uuid_payload, 1, uuid_atom->AtomicLength - 36, source_file); 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]; uint32_t mimetype_string = atom_offsets+1; atom_offsets+=1+mime_len; uint32_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); memset(base_outpath, 0, MAXPATHLEN +1); strcpy(base_outpath, pic_output_path); strcat(base_outpath, "_artwork"); sprintf(base_outpath, "%s_%d", base_outpath, artwork_count); 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 ); fseeko(source_file, parsedAtoms[this_atom_num].AtomicStart+16, SEEK_SET); fread(art_payload, 1, parsedAtoms[this_atom_num].AtomicLength-16, source_file); 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) { suffix = ".png"; } else if (memcmp(art_payload, "\xFF\xD8\xFF\xE0", 4) == 0 || memcmp(art_payload, "\xFF\xD8\xFF\xE1", 4) == 0) { 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); 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, char* frame_str, char* originfile, 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; uint8_t frameCompositionList = GetFrameCompositionDescription(frameType); if (destination_folder == NULL) { basepath_len = (uint32_t)(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) ); fseeko(source_file, thisAtom->AtomicStart + atom_header_size, SEEK_SET); fread(data_payload, 1, thisAtom->AtomicLength - atom_header_size, source_file); if (thisAtom->AtomicVerFlags == (uint32_t)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, "%hu of %hu\n", UInt16FromBigEndian(data_payload+2), UInt16FromBigEndian(data_payload+4) ); } else { fprintf(stdout, "%hu\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 - %hu\n", UInt16FromBigEndian(data_payload) ); } } else { fprintf(stdout," out of bound value - %hu\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 = 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: %hhu\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, "%hhu\n", data_payload[0] ); } break; } case 2 : { //tmpo fprintf(stdout, "%hu\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 (%u)\n", this_store->storefront_string, this_store->storefront_number ); } else { fprintf(stdout, "Unknown (%u)\n", UInt32FromBigEndian(data_payload) ); } } else { fprintf(stdout, "%u\n", UInt32FromBigEndian(data_payload) ); } break; } case 8 : { fprintf(stdout, "%llu\n", UInt64FromBigEndian(data_payload) ); break; } } } else if (thisAtom->AtomicClassification == EXTENDED_ATOM && thisAtom->AtomicVerFlags == AtomFlags_Data_uuid_binary && thisAtom->uuid_style == UUID_AP_SHA1_NAMESPACE) { uint32_t offset_into_uuiddata = 0; uint32_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 == (uint32_t)AtomFlags_Data_Binary || thisAtom->AtomicVerFlags == (uint32_t)AtomFlags_Data_Text || thisAtom->AtomicVerFlags == (uint32_t)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: %u\n", APar_ProvideTallyForAtom("free") ); } if (supplemental_info && 0x04) { //PRINT_PADDING_SPACE if (!moov_atom_was_mooved) { fprintf(stdout, "padding available: %u 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: %u\n", dynUpd.moov_udta_atom->AtomicLength); } if (supplemental_info && 0x10) { //PRINT_USER_DATA_SPACE fprintf(stdout, "media data space: %u\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 (memcmp(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 (_MSC_VER) 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); //fprintf(stdout, " Rating Criteria: %u%u%u%u", box_data[4], box_data[5], box_data[6], box_data[7]); 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); uint32_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, (uint32_t)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); uint32_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 ( memcmp(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 ( memcmp(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 (memcmp(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", textframe->ID3v2_Frame_Fields->field_string[0]); } if(linefeed) fprintf(stdout, "\n"); return; } char* APar_GetTextEncoding(ID3v2Frame* aframe, ID3v2Fields* textfield) { 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 (memcmp(textfield->field_string, "\xFF\xFE", 2) == 0) { text_encoding = "utf16le"; } else if (memcmp(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, "Unsyncrhonized flag set. Unsupported. No tags read. %u 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) %u 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[", (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, "]) : %u bytes (%u compressed)\n", (target_frameinfo->ID3v2_Frame_Fields+4)->field_length, target_frameinfo->ID3v2_Frame_Length); } else { fprintf(stdout, "]) : %u 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, "]) : %u bytes (%u compressed)\n", (target_frameinfo->ID3v2_Frame_Fields+4)->field_length, target_frameinfo->ID3v2_Frame_Length); } else { fprintf(stdout, "]) : %u 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, ": %u\n", syncsafe32_to_UInt32(target_frameinfo->ID3v2_Frame_Fields->field_string) ); } else if (target_frameinfo->ID3v2_Frame_Fields->field_length > 4) { fprintf(stdout, ": %llu\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, (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=%u\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=%llu\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 @ %u of size: %u (%u*), ends @ %u\n", tree_padding, twenty_byte_buffer, thisAtom->AtomicStart, ( (uint32_t)file_size - thisAtom->AtomicStart), thisAtom->AtomicLength, (uint32_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 @ %u of size: %llu (^), ends @ %llu\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, " @ %u of size: %u, ends @ %u\n", thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength) ); } else { fprintf(stdout, "%sAtom uuid=%s @ %u of size: %u, ends @ %u\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) @ %u of size: %u, ends @ %u\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, " @ %u of size: %u, ends @ %u\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] @ %u of size: %u, ends @ %u\n", unpacked_lang, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength) ); } else { fprintf(stdout, "%sAtom %s [%s] @ %u of size: %u, ends @ %u\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, " @ %u of size: %u, ends @ %u", thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength) ); } else { fprintf(stdout, "%sAtom %s @ %u of size: %u, ends @ %u", 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 = (uint32_t)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: %llu bytes; ", (uint64_t)file_size); fprintf(stdout, "%i atoms total. ", atom_number-1); ShowVersionInfo(); fprintf(stdout, "Media data: %u bytes; %u bytes all other atoms (%2.3lf%% atom overhead).\n", mdatData, (uint32_t)(file_size - mdatData), (double)(file_size - mdatData)/(double)file_size * 100.0 ); fprintf(stdout, "Total free atom space: %u 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: %u bytes.", dynUpd.padding_bytes); } } if (gapless_void_padding > 0) { fprintf(stdout, "\nGapless playback null space at end of file: %u bytes.", gapless_void_padding); } fprintf(stdout, "\n------------------------------------------------------\n"); free(tree_padding); tree_padding = NULL; return; } /*---------------------- APar_SimpleAtomPrintout print a simple flat list of atoms as they were created ----------------------*/ void APar_SimpleAtomPrintout() { //loop through each atom in the struct array (which holds the offset info/data) printBOM(); for (int i=0; i < atom_number; i++) { AtomicInfo* thisAtom = &parsedAtoms[i]; fprintf(stdout, "%i - Atom \"%s\" (level %u) has next atom at #%i\n", i, thisAtom->AtomicName, thisAtom->AtomicLevel, thisAtom->NextAtomNumber); } fprintf(stdout, "Total of %i atoms.\n", atom_number-1); } atomicparsley-0.9.2~svn110.orig/src/APar_zlib.h0000644000000000000000000000232511226366037016205 0ustar //==================================================================// /* AtomicParsley - APar_zlib.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 */ //==================================================================// #if defined (WIN32) bool APar_win32_zlib_LoadLibrary(); void APar_win32_zlib_FreeLibrary(); #endif 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); atomicparsley-0.9.2~svn110.orig/src/APar_uuid.h0000644000000000000000000000312311226366037016210 0ustar //==================================================================// /* AtomicParsley - APar_uuid.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 */ //==================================================================// 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; 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, 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); atomicparsley-0.9.2~svn110.orig/src/AP_AtomExtracts.cpp0000644000000000000000000016715111226366037017704 0ustar //==================================================================// /* AtomicParsley - AP_AtomExtracts.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 2006-2007 puck_lock */ //==================================================================// #include #include #include #include #include #include "AtomicParsley.h" #include "AP_ID3v2_tags.h" #include "AP_MetadataListings.h" #include "AP_AtomExtracts.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_len++) { if (data[str_offset] < 32 || data[str_offset] == 127) { data[str_offset] = 19; purgings++; break; } } return purgings; } void mem_append(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 (uint32_t atom_offset = 20; atom_offset < parsedAtoms[stsz_atom].AtomicLength; atom_offset +=4) { total_size += (uint64_t)APar_read32(uint32_buffer, isofile, parsedAtoms[stsz_atom].AtomicStart + atom_offset); } } else { total_size = (uint64_t)sample_size * (uint64_t)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, 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) { uint32_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) { uint32_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) { uint32_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) { uint32_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; uint32_t esds_start = parsedAtoms[track_level_atom].AtomicStart + offset_into_stsd - 4; uint32_t esds_length = APar_read32(uint32_buffer, isofile, esds_start); uint32_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) { uint32_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 = (uint64_t)APar_read32(uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 12); track_info->modified_time = (uint64_t)APar_read32(uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 16); track_info->duration = (uint64_t)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)((uint32_t)track_info->duration) / (float)((uint32_t)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 (%u 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 %llu", 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; fseek(a_file, 4, SEEK_SET); //this fseek will to.... the first 30 or so bytes; fseeko isn't required fread(buffer, 1, 4, a_file); 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 %u\n", minor_version); fprintf(stdout, " Compatible Brands:"); for (uint32_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; } atomicparsley-0.9.2~svn110.orig/src/AP_NSImage.h0000644000000000000000000000236411226366037016210 0ustar //==================================================================// /* AtomicParsley - AP_NSImage.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 */ //==================================================================// 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; }; #if defined (DARWIN_PLATFORM) bool ResizeGivenImage(const char* filePath, PicPrefs myPicPrefs, char* resized_path); #endif atomicparsley-0.9.2~svn110.orig/src/AP_MetadataListings.h0000644000000000000000000000360011226366037020154 0ustar //==================================================================// /* AtomicParsley - AP_MetadataListings.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 */ //==================================================================// void printBOM(); void APar_fprintf_UTF8_data(char* utf8_encoded_data); #if defined (_MSC_VER) void APar_unicode_win32Printout(wchar_t* unicode_out, char* utf8_out); #endif 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, char* frame_str, char* originfile, 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(); atomicparsley-0.9.2~svn110.orig/src/APar_zlib.cpp0000644000000000000000000001033411226366037016537 0ustar //==================================================================// /* AtomicParsley - APar_zlib.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 2006-2007 puck_lock */ //==================================================================// #include #include #include #include "AP_commons.h" #if defined HAVE_CONFIG_H #include "config.h" #endif #if defined HAVE_ZLIB_H #if defined (WIN32) || defined (__CYGWIN__) #include "zlib.h" //QT #else #include //QT #endif #if defined (WIN32) #include static bool zlibdll_loaded = 0; static HINSTANCE zlib_library; #define GETPROC(result, name, args)\ typedef result (__cdecl *__PROC__##name) args;\ __PROC__##name name = (__PROC__##name)GetProcAddress (zlib_library, #name); int (*inflateInit); int inflate OF((z_streamp strm, int flush)); int inflateEnd OF((z_streamp strm)); int (*deflateInit); int deflate OF((z_streamp strm, int flush)); int deflateEnd(z_streamp strm); //end global variables bool APar_win32_zlib_LoadLibrary() { HINSTANCE zlib_lib; if (zlibdll_loaded) return true; zlib_lib = LoadLibrary("zlib1.dll"); if (zlib_lib == NULL){ fprintf(stdout,"AtomicParsley warning: zlib library missing. Compression is disabled.\n"); return false; } GETPROC(int, inflateInit, (z_streamp, int)); GETPROC(int, inflate, (z_streamp, int)); GETPROC(int, inflateEnd, (z_streamp)); GETPROC(int, deflateInit, (z_streamp, int)); GETPROC(int, deflate, (z_streamp, int)); GETPROC(int, deflateEnd, (z_streamp)); if (inflateInit && inflate && inflateEnd && deflateInit && deflate && deflateEnd) { zlibdll_loaded = true; zlib_library = zlib_lib; } else { fprintf(stdout,"AtomicParsley warning: zlib library addressing failed. Compression is disabled.\n"); return false; } return true; } void APar_win32_zlib_FreeLibrary() { if (!zlibdll_loaded) return; FreeLibrary(zlib_library); zlibdll_loaded = false; return; } #endif #endif //HAVE_ZLIB_H 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; // 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; // 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 = (uint32_t)zlib.total_out; deflateEnd(&zlib); } #endif return compressed_bytes; } atomicparsley-0.9.2~svn110.orig/src/AP_arrays.cpp0000644000000000000000000006663511226366037016574 0ustar //==================================================================// /* AtomicParsley - AP_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 2005-2007 puck_lock ---------------------- Code Contributions by: * Mellow_Flow - fix genre matching/verify genre limits */ //==================================================================// //#include //#include #include #include #include #include "AP_commons.h" //just so win32/msvc can get uint8_t defined #include "AtomicParsley.h" //for stiks & sfIDs ////////////// 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[] = { { "Movie", 0 }, { "Normal", 1 }, { "Audiobook", 2 }, { "Whacked Bookmark", 5 }, { "Music Video", 6 }, { "Short Film", 9 }, { "TV Show", 10 }, { "Booklet", 11 } }; // 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, "Gwichin" }, { "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 Bokml; Bokml, 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); Provenal" }, { "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, "Provenal, 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", "Volapk" }, { "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-Y|200|", "TV-Y" }, { "us-tv|TV-Y7|100|", "TV-Y7" }, //{ "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)); uint8_t genre_length = strlen(genre_string)+1; for(uint8_t i = 0; i < total_genres; i++) { if ( memcmp(genre_string, ID3v1GenreList[i], strlen(ID3v1GenreList[i])+1 > genre_length ? strlen(ID3v1GenreList[i])+1 : genre_length ) == 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 = (uint32_t)(sizeof(stikArray)/sizeof(*stikArray)); uint8_t stik_str_length = strlen(in_stik_string) +1; for (uint8_t i = 0; i < total_known_stiks; i++) { if ( memcmp(in_stik_string, stikArray[i].stik_string, strlen(stikArray[i].stik_string)+1 > stik_str_length ? strlen(stikArray[i].stik_string)+1 : stik_str_length ) == 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 = (uint32_t)(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 = (uint32_t)(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 = (uint32_t)(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 (memcmp(in_code, known_languages[i].iso639_2_code, 3) == 0) { matching_lang = true; break; } if (strlen(known_languages[i].iso639_2_code) > 3) { if (memcmp(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; } char* Expand_cli_mediastring(char* cli_rating) { 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; uint8_t genre_length = strlen(genre_string)+1; for(uint8_t i = 0; i < total_genres; i++) { if ( memcmp(genre_string, ID3v1GenreList[i], strlen(ID3v1GenreList[i])+1 > genre_length ? strlen(ID3v1GenreList[i])+1 : genre_length ) == 0) { return i; } } if ( return_genre > total_genres ) { return_genre = 0xFF; } return return_genre; } atomicparsley-0.9.2~svn110.orig/src/main.cpp0000644000000000000000000037631111226366037015632 0ustar //==================================================================// /* 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 2005-2007 puck_lock ---------------------- Code Contributions by: * Mike Brancato - Debian patches & build support * Brian Story - porting getopt & native Win32 patches */ //==================================================================// #include #include #include #include #include #if defined HAVE_CONFIG_H #include "config.h" #endif #if defined HAVE_GETOPT_H #include #else #include "./extras/getopt.h" #endif #include "AP_commons.h" #include "AtomicParsley.h" #include "AP_AtomExtracts.h" #include "AP_iconv.h" /* for xmlInitEndianDetection used in endian utf16 conversion */ #include "AP_arrays.h" #include "APar_uuid.h" #include "AP_ID3v2_tags.h" #include "AP_MetadataListings.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_songtitle 's' #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_composer 'w' #define Meta_copyright 'x' #define Meta_grouping 'G' #define Meta_album_artist 'A' #define Meta_compilation 'C' #define Meta_BPM 'B' #define Meta_artwork 'r' #define Meta_advisory 'V' #define Meta_stik 'S' #define Meta_description 'p' #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_EncodingTool 0xB7 #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' #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 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 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" " --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" " --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" " --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" " --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" "----------------------------------------------------------------------" ; //an expansive, verbose, unconstrained (about 112 char wide) detailing of options static 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.\302ART.data\"\n" " --title , -s (str) Set the title tag: \"moov.udta.meta.ilst.\302nam.data\"\n" " --album , -b (str) Set the album tag: \"moov.udta.meta.ilst.\302alb.data\"\n" " --genre , -g (str) Set the genre tag: \"\302gen\" (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.\302cmt.data\"\n" " --year , -y (num|UTC) Set the year tag: \"moov.udta.meta.ilst.\302day.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.\302lyr.data\"\n" " --composer , -w (str) Set the composer tag: \"moov.udta.meta.ilst.\302wrt.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.\302grp.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" " --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" " --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 its 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 \"\302too\" atom\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; lyrics 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 (DARWIN_PLATFORM) " 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 char* fileLevelHelp_text = "AtomicParsley help page for general & file level options.\n" #if defined (_MSC_VER) " 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" "\n" #endif "------------------------------------------------------------------------------------------------\n" " Atom reading services:\n" "\n" " --test , -T Tests file to see if its 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" " --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 (_MSC_VER) "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 (_MSC_VER) "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 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 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 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\"inf\"\n" " --url , -u (URL) Set a URL tag on uuid atom name \"\302url\"\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 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 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 (DARWIN_PLATFORM) " 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 (HAVE_LINUX_CDROM_H) " 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 (memcmp(env_pad_prefs_ptr, "DEFAULT_PAD=", 12) == 0) { strsep(&env_pad_prefs_ptr,"="); sscanf(env_pad_prefs_ptr, "%u", &pad_prefs.default_padding_size); } if (memcmp(env_pad_prefs_ptr, "MIN_PAD=", 8) == 0) { strsep(&env_pad_prefs_ptr,"="); sscanf(env_pad_prefs_ptr, "%u", &pad_prefs.minimum_required_padding_size); } if (memcmp(env_pad_prefs_ptr, "MAX_PAD=", 8) == 0) { strsep(&env_pad_prefs_ptr,"="); sscanf(env_pad_prefs_ptr, "%u", &pad_prefs.maximum_present_padding_size); } } //fprintf(stdout, "Def %u; Min %u; Max %u\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 ( strncmp(this_char, ".", 1) == 0 ) { 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 ( memcmp(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 ( memcmp(argv[start_optindargs + i], "UTF16", 5) == 0 ) { asUTF16 = true; } else if ( memcmp(argv[optind + i], "movie", 6) == 0 ) { udta_container = MOVIE_LEVEL_ATOM; } else if ( memcmp(argv[optind + i], "track=", 6) == 0 ) { char* track_index_str = argv[optind + i]; strsep(&track_index_str, "="); sscanf(track_index_str, "%hhu", &trk_idx); udta_container = SINGLE_TRACK_ATOM; } else if ( memcmp(argv[optind + i], "track", 6) == 0 ) { udta_container = ALL_TRACKS_ATOM; } if (memcmp(argv[start_optindargs + i], "-", 1) == 0) { break; //we've hit another cli argument } } } return; } void scan_ID3_optargs(char *argv[], int start_optargs, 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 ( memcmp(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 ( memcmp(argv[start_optargs + i], "UTF16LE", 8) == 0 ) { char_encoding = TE_UTF16LE_WITH_BOM; } else if ( memcmp(argv[start_optargs + i], "UTF16BE", 8) == 0 ) { char_encoding = TE_UTF16BE_NO_BOM; } else if ( memcmp(argv[start_optargs + i], "LATIN1", 7) == 0 ) { char_encoding = TE_LATIN1; } else if ( memcmp(argv[optind + i], "root", 5) == 0 ) { *meta_container = 0-FILE_LEVEL_ATOM; } else if ( memcmp(argv[optind + i], "track=", 6) == 0 ) { char* track_index_str = argv[optind + i]; strsep(&track_index_str, "="); sscanf(track_index_str, "%hhu", meta_container); } } if (memcmp(argv[start_optargs + i], "-", 1) == 0) { break; //we've hit another cli argument or deleting some frame } i++; } return; } char* find_ID3_optarg(char *argv[], int start_optargs, char* arg_string) { 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 (memcmp(arg_string, "compressed", 11) == 0 && memcmp(argv[start_optargs + i], "compressed", 11) == 0) { return "1"; } //if (memcmp(arg_string, "text++", 7) == 0 && memcmp(argv[start_optargs + i], "text++", 7) == 0) { // return "1"; //} if (memcmp(argv[start_optargs + i], arg_string, arg_prefix_len) == 0) { ret_val = argv[start_optargs + i] + arg_prefix_len; break; } } if (memcmp(argv[start_optargs + i], "-", 1) == 0) { break; //we've hit another cli argument or deleting some frame } i++; } return ret_val; } //*********************************************** #if defined (_MSC_VER) int wmain( int argc, wchar_t *arguments[]) { uint16_t name_len = wcslen(arguments[0]); if (wmemcmp(arguments[0] + (name_len-9), L"-utf8.exe", 9) == 0 || wmemcmp(arguments[0] + (name_len-9), L"-UTF8.exe", 9) == 0) { UnicodeOutputStatus = UNIVERSAL_UTF8; } else { UnicodeOutputStatus = WIN32_UTF16; } char *argv[350]; //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. Here, EVERYTHING = 350. 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; #else int main( int argc, char *argv[]) { #endif if (argc == 1) { fprintf (stdout,"%s\n", shortHelp_text); exit(0); } else if (argc == 2 && ((strncmp(argv[1],"-v",2) == 0) || (strncmp(argv[1],"-version",2) == 0)) ) { ShowVersionInfo(); exit(0); } else if (argc == 2) { if ( (strncmp(argv[1],"-help",5) == 0) || (strncmp(argv[1],"--help",6) == 0) || (strncmp(argv[1],"-h",5) == 0 ) ) { fprintf(stdout, "%s\n", shortHelp_text); exit(0); } else if ( (strncmp(argv[1],"--longhelp", 10) == 0) || (strncmp(argv[1],"-longhelp", 9) == 0) || (strncmp(argv[1],"-Lh", 3) == 0) ) { if (UnicodeOutputStatus == WIN32_UTF16) { //convert the helptext to utf16 to preserve 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); #if defined (_MSC_VER) APar_unicode_win32Printout(Lhelp_text, longHelp_text); #endif free(Lhelp_text); } else { fprintf(stdout, "%s", longHelp_text); } exit(0); } else if ( (strncmp(argv[1],"--3gp-help", 10) == 0) || (strncmp(argv[1],"-3gp-help", 9) == 0) || (strncmp(argv[1],"--3gp-h", 7) == 0) ) { fprintf(stdout, "%s\n", _3gpHelp_text); exit(0); } else if ( (strncmp(argv[1],"--ISO-help", 10) == 0) || (strncmp(argv[1],"--iso-help", 10) == 0) || (strncmp(argv[1],"-Ih", 3) == 0) ) { fprintf(stdout, "%s\n", ISOHelp_text); exit(0); } else if ( (strncmp(argv[1],"--file-help", 11) == 0) || (strncmp(argv[1],"-file-help", 10) == 0) || (strncmp(argv[1],"-fh", 3) == 0) ) { fprintf(stdout, "%s\n", fileLevelHelp_text); exit(0); } else if ( (strncmp(argv[1],"--uuid-help", 11) == 0) || (strncmp(argv[1],"-uuid-help", 10) == 0) || (strncmp(argv[1],"-uh", 3) == 0) ) { fprintf(stdout, "%s\n", uuidHelp_text); exit(0); } else if ( (strncmp(argv[1],"--reverseDNS-help", 18) == 0) || (strncmp(argv[1],"-rDNS-help", 10) == 0) || (strncmp(argv[1],"-rh", 3) == 0) ) { fprintf(stdout, "%s\n", rDNSHelp_text); exit(0); } else if ( (strncmp(argv[1],"--ID3-help", 10) == 0) || (strncmp(argv[1],"-ID3-help", 9) == 0) || (strncmp(argv[1],"-ID3h", 4) == 0) ) { fprintf(stdout, "%s\n", ID3Help_text); exit(0); } else if ( memcmp(argv[1], "--genre-list", 12) == 0 ) { ListGenresValues(); exit(0); } else if ( memcmp(argv[1], "--stik-list", 11) == 0 ) { ListStikValues(); exit(0); } else if ( memcmp(argv[1], "--language-list", 16) == 0 || memcmp(argv[1], "--languages-list", 17) == 0 || memcmp(argv[1], "--list-language", 16) == 0 || memcmp(argv[1], "--list-languages", 17) == 0 || memcmp(argv[1], "-ll", 3) == 0) { ListLanguageCodes(); exit(0); } else if (memcmp(argv[1], "--ratings-list", 14) == 0) { ListMediaRatings(); exit(0); } else if (memcmp(argv[1], "--ID3frames-list", 17) == 0) { ListID3FrameIDstrings(); exit(0); } else if (memcmp(argv[1], "--imagetype-list", 17) == 0) { List_imagtype_strings(); exit(0); } } if ( argc == 3 && (memcmp(argv[2], "--brands", 8) == 0 || memcmp(argv[2], "-brands", 7) == 0) ) { APar_ExtractBrands(argv[1]); exit(0); } int extr = 99; total_args = argc; char* ISObasemediafile = argv[1]; TestFileExistence(ISObasemediafile, true); xmlInitEndianDetection(); 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 && memcmp(argv[2], "--DeepScan", 10) == 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 }, { "title", required_argument, NULL, Meta_songtitle }, { "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 }, { "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 }, { "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 }, { "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 }, { "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 }, { "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 }, { 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:pq::u:w:y:z:A:B:C:D:F:G:H:I:J:K:L:MN:QR:S:U:WXV:ZP 0xAA: 0xAB: 0xAC: 0xAD: 0xAE: 0xAF: 0xB0: 0xB1: 0xB2: 0xB3: 0xB4: 0xB5: 0xB6:", long_options, &option_index); if (c == -1) { if (argc < 3 && argc > 2) { APar_ScanAtoms(ISObasemediafile, true); APar_PrintAtomicTree(); } break; } signal(SIGTERM, kill_signal); #ifndef WIN32 signal(SIGKILL, kill_signal); #endif 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 (memcmp(argv[optind], "+dates", 6) == 0) { APar_ExtractDetails( APar_OpenISOBaseMediaFile(ISObasemediafile, true), SHOW_TRACK_INFO + SHOW_DATE_INFO ); } else { APar_ExtractDetails( APar_OpenISOBaseMediaFile(ISObasemediafile, true), SHOW_TRACK_INFO); } } 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 (memcmp(argv[optind], "+", 1) == 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); APar_FreeMemory(); 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.ART.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.nam.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.alb.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, "%hu", &pos_in_total); //sscanf into a an unsigned char (uint8_t is typedef'ed to a unsigned char by gcc) item_stat = strsep(&duplicate_info,"/"); sscanf(item_stat, "%hu", &the_total); } else { sscanf(optarg, "%hu", &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, "%hu", &pos_in_total); //sscanf into a an unsigned char (uint8_t is typedef'ed to a unsigned char by gcc) item_stat = strsep(&duplicate_info,"/"); sscanf(item_stat, "%hu", &the_total); } else { sscanf(optarg, "%hu", &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.cmt.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.day.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.lyr.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put(lyricsData_atom, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); 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.wrt.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.grp.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 (strncmp(optarg, "false", 5) == 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_BPM : { APar_ScanAtoms(ISObasemediafile); if ( !APar_assert(metadata_style == ITUNES_STYLE, 1, "BPM") ) { break; } if (strncmp(optarg, "0", 1) == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.tmpo.data", VERSIONED_ATOM, 0); } else { uint16_t bpm_value = 0; sscanf(optarg, "%hu", &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 (strncmp(optarg, "remove", 6) == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.rtng.data", VERSIONED_ATOM, 0); } else { uint8_t rating_value = 0; if (strncmp(optarg, "clean", 5) == 0) { rating_value = 2; //only \02 is clean } else if (strncmp(optarg, "explicit", 8) == 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 (strncmp(optarg, "remove", 6) == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.stik.data", VERSIONED_ATOM, 0); } else { uint8_t stik_value = 0; if (memcmp(optarg, "value=", 6) == 0) { char* stik_val_str_ptr = optarg; strsep(&stik_val_str_ptr,"="); sscanf(stik_val_str_ptr, "%hhu", &stik_value); } else { stiks* return_stik = MatchStikString(optarg); if (return_stik != NULL) { stik_value = return_stik->stik_number; if (memcmp(optarg, "Audiobook", 9) == 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.too.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put(encodingtoolData_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_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, "%hu", &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, "%hu", &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_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 (strncmp(optarg, "false", 5) == 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 (strncmp(optarg, "timestamp", 9) == 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 (strncmp(optarg, "false", 5) == 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 ( memcmp(optarg, "name", 5) == 0 ) { sortOrder_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.sonm.data", argv[optind], AtomFlags_Data_Text); } else if ( memcmp(optarg, "artist", 7) == 0 ) { sortOrder_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.soar.data", argv[optind], AtomFlags_Data_Text); } else if ( memcmp(optarg, "albumartist", 12) == 0 ) { sortOrder_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.soaa.data", argv[optind], AtomFlags_Data_Text); } else if ( memcmp(optarg, "album", 6) == 0 ) { sortOrder_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.soal.data", argv[optind], AtomFlags_Data_Text); } else if ( memcmp(optarg, "composer", 9) == 0 ) { sortOrder_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.soco.data", argv[optind], AtomFlags_Data_Text); } else if ( memcmp(optarg, "show", 5) == 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", "url", 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", "inf", 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 "foo" 1 'http://www.url.org' --meta-uuid "pdf1" file /some/path/pic.pdf description="My Booty, Your Booty, Djbouti" if ( memcmp(argv[optind], "text", 5) == 0 || memcmp(argv[optind], "1", 2) == 0 ) uuid_dataType = AtomFlags_Data_Text; if ( memcmp(argv[optind], "file", 5) == 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 ( memcmp(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 ( memcmp(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 (memcmp(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 uint32_t file_len = (uint32_t)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 (memcmp(optarg, "1200", 3) != 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; AtomicInfo* ipod_uuid = 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[%hhu].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[%hhu].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 (memcmp(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 (memcmp(compliant_name + (strlen(compliant_name) - 4), "data", 4) == 0) { APar_RemoveAtom(compliant_name, VERSIONED_ATOM, 0); } else { size_t string_len = strlen(compliant_name); //reverseDNS atom path if (strstr(optarg, ":[") != NULL && memcmp(compliant_name + string_len-1, "]", 1) == 0 ) { APar_RemoveAtom(compliant_name, VERSIONED_ATOM, 0); //packed language asset } else if (memcmp(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), (uint32_t)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), (uint32_t)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), (uint32_t)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), (uint32_t)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), (uint32_t)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), (uint32_t)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 ( memcmp(argv[optind + i], "trknum=", 7) == 0 ) { char* track_num = argv[optind + i]; strsep(&track_num,"="); sscanf(track_num, "%hhu", &tracknum); } if (memcmp(argv[optind + i], "-", 1) == 0) 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), (uint32_t)packed_lang, 16); if (tracknum != 0) { APar_Unified_atom_Put(album_asset, NULL, UTF8_3GP_Style, (uint32_t)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 ( memcmp(argv[optind], "movie", 6) == 0 ) { userdata_area = MOVIE_LEVEL_ATOM; } if ( memcmp(argv[optind], "track=", 6) == 0 ) { char* trak_idx = argv[optind]; strsep(&trak_idx, "="); sscanf(trak_idx, "%hhu", &selected_track); userdata_area = SINGLE_TRACK_ATOM; } else if ( memcmp(argv[optind], "track", 6) == 0 ) { userdata_area = ALL_TRACKS_ATOM; } } sscanf(optarg, "%hu", &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, (uint32_t)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 ( memcmp(argv[optind + i], "entity=", 7) == 0 ) { char* entity = argv[optind + i]; strsep(&entity,"="); memcpy(&rating_entity, entity, 4); } if ( memcmp(argv[optind + i], "criteria=", 9) == 0 ) { char* criteria = argv[optind + i]; strsep(&criteria,"="); memcpy(&rating_criteria, criteria, 4); } if (memcmp(argv[optind + i], "-", 1) == 0) 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), (uint32_t)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 ( memcmp(argv[optind + i], "entity=", 7) == 0 ) { char* cls_entity = argv[optind + i]; strsep(&cls_entity, "="); memcpy(&classification_entity, cls_entity, 4); } if ( memcmp(argv[optind + i], "index=", 6) == 0 ) { char* cls_idx = argv[optind + i]; strsep(&cls_idx, "="); sscanf(cls_idx, "%hu", &classification_index); } if (memcmp(argv[optind + i], "-", 1) == 0) 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), (uint32_t)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, (uint32_t)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; char* astronomical_body = "Earth"; 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 ( memcmp(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 ( memcmp(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 ( memcmp(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 ( memcmp(argv[optind + i], "role=", 5) == 0 ) { char* _role = argv[optind + i]; strsep(&_role,"="); if (strncmp(_role, "shooting location", 17) == 0 || strncmp(_role, "shooting", 8) == 0) { role = 0; } else if (strncmp(_role, "real location", 13) == 0 || strncmp(_role, "real", 4) == 0) { role = 1; } else if (strncmp(_role, "fictional location", 18) == 0 || strncmp(_role, "fictional", 9) == 0) { role = 2; } } if ( memcmp(argv[optind + i], "body=", 5) == 0 ) { char* _astrobody = argv[optind + i]; strsep(&_astrobody,"="); astronomical_body = _astrobody; } if ( memcmp(argv[optind + i], "notes=", 6) == 0 ) { char* _add_notes = argv[optind + i]; strsep(&_add_notes,"="); additional_notes = _add_notes; } if (memcmp(argv[optind + i], "-", 1) == 0) 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), (uint32_t)packed_lang, 16); APar_Unified_atom_Put(location_asset, NULL, false, (uint32_t)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 ( memcmp(argv[optind + i], "name=", 5) == 0 ) { reverseDNS_atomname = argv[optind + i]+5; } else if ( memcmp(argv[optind + i], "domain=", 7) == 0 ) { reverseDNS_atomdomain = argv[optind + i]+7; } else if ( memcmp(argv[optind + i], "datatype=", 9) == 0 ) { sscanf(argv[optind + i]+9, "%u", &rdns_atom_flags); } if (memcmp(argv[optind + i], "-", 1) == 0) { 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 : { 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 : { 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 (memcmp("1", find_ID3_optarg(argv, optind, "compressed"), 1) == 0) { id3args->zlibCompressed = true; } char* groupsymbol = find_ID3_optarg(argv, optind, "groupsymbol="); if (groupsymbol[0] == '0' && groupsymbol[1] == 'x') { sscanf(groupsymbol, "%hhX", &id3args->groupSymbol); 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 (memcmp(argv[optind + 0], "extract", 7) == 0 && (memcmp(target_frame_ID, "APIC", 4) == 0 || memcmp(target_frame_ID, "GEOB", 4) == 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; } case Meta_dump : { APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_MetadataFileDump(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, false); APar_FreeMemory(); #if defined (_MSC_VER) for(int zz=0; zz < argc; zz++) { if (argv[zz] > 0) { free(argv[zz]); argv[zz] = NULL; } } #endif exit(0); //das right, this is a flag that doesn't get used with other flags. } case OPT_NoOptimize : { force_existing_hierarchy = true; break; } case OPT_OutputFile : { output_file = optarg; break; } } /* end switch */ } /* end while */ //after all the modifications are enacted on the tree in memory, THEN write out the changes if (modified_atoms) { APar_DetermineAtomLengths(); APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_WriteFile(ISObasemediafile, output_file, alter_original); 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(); #if defined (_MSC_VER) for(int zz=0; zz < argc; zz++) { if (argv[zz] > 0) { free(argv[zz]); argv[zz] = NULL; } } #endif return 0; } atomicparsley-0.9.2~svn110.orig/src/AP_commons.h0000644000000000000000000001146011226366037016375 0ustar //==================================================================// /* AtomicParsley - AP_commons.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 */ //==================================================================// #include #include #include #if defined HAVE_CONFIG_H #include "config.h" #endif #ifndef _UINT8_T #define _UINT8_T typedef unsigned char uint8_t; #endif /*_UINT8_T */ #ifndef _UINT16_T #define _UINT16_T typedef unsigned short uint16_t; #endif /* _UINT16_T */ #ifndef _UINT32_T #ifndef __uint32_t_defined typedef unsigned int uint32_t; #endif #endif /*_UINT32_T */ #ifndef _UINT64_T #define _UINT64_T #if defined (_MSC_VER) typedef unsigned __int64 uint64_t; #else typedef unsigned long long uint64_t; #endif /* _MSC_VER */ #endif /* _UINT64_T */ #ifndef _INT16_T #define _INT16_T typedef short int16_t; #endif /* _INT16_T */ //part of xorgens rand #if !defined (_MSC_VER) typedef unsigned long UINT; /* Type for random 32 or 64-bit integer, e.g. unsigned long, unsigned long long, uint64_t, unsigned int or uint32_t */ #endif #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 #if defined (_WIN32) #define MAXTIME_32 (uint64_t)6377812095 #else #define MAXTIME_32 6377812095ULL #endif off_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 (_MSC_VER) int fseeko(FILE *stream, uint64_t pos, int whence); #endif bool IsUnicodeWinOS(); uint8_t APar_read8(FILE* ISObasemediafile, uint32_t pos); uint16_t APar_read16(char* buffer, FILE* ISObasemediafile, uint32_t pos); uint32_t APar_read32(char* buffer, FILE* ISObasemediafile, uint32_t pos); uint64_t APar_read64(char* buffer, FILE* ISObasemediafile, uint32_t pos); void APar_readX(char* buffer, FILE* ISObasemediafile, uint32_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, uint32_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); #if (!defined HAVE_LROUNDF) && (!defined (__GLIBC__)) int lroundf(float a); #endif #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, char* supplemental_info); unsigned long xor4096i(); atomicparsley-0.9.2~svn110.orig/src/APar_uuid.cpp0000644000000000000000000003517711226366037016561 0ustar //==================================================================// /* AtomicParsley - APar_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 2006-2007 puck_lock */ //==================================================================// //==================================================================// /* 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 #include #include #include "AP_commons.h" #include "APar_sha1.h" #include "APar_uuid.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, "%08x-%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, "%08x-%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, char* raw_uuid) { char *uuid_str, *end_uuid_str, *uuid_byte; uint8_t uuid_pos, uuid_len; uint8_t keeprap = 0; 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); 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, char *name, int namelen) { sha1_ctx sha_state; char hash[20]; ap_uuid_t networkorderd_namespace; //swap the endian members of uuid to network byte order for hash uniformity across platforms (the NULL or AP.sf.net uuid) networkorderd_namespace = desired_namespace; networkorderd_namespace.time_low = SWAP32(networkorderd_namespace.time_low); networkorderd_namespace.time_mid = SWAP16(networkorderd_namespace.time_mid); networkorderd_namespace.time_hi_and_version = SWAP16(networkorderd_namespace.time_hi_and_version); //make a hash of the input desired_namespace (as netord_ns); add the name (the AP.sf.net namespace as string or the atom name) sha1_init_ctx(&sha_state); sha1_process_bytes( (char*)&networkorderd_namespace, sizeof networkorderd_namespace, &sha_state); sha1_process_bytes(name, namelen, &sha_state); sha1_finish_ctx(&sha_state, hash); // quasi uuid sha1hash is network byte ordered. swap the endian members of the uuid (uint32_t & uint16_t) to local byte order // this creates additional requirements later that have to be APar_endian_uuid_bin_str_conversion() swapped, but leave this for now for coherence memcpy(out_uuid, hash, sizeof *out_uuid); out_uuid->time_low = SWAP32(out_uuid->time_low); out_uuid->time_mid = SWAP16(out_uuid->time_mid); out_uuid->time_hi_and_version = SWAP16(out_uuid->time_hi_and_version); out_uuid->time_hi_and_version &= 0x0FFF; //mask hash octes 6&7 out_uuid->time_hi_and_version |= (5 << 12); //set bits 12-15 to the version (5 for sha1 namespace/name hash uuids) out_uuid->clock_seq_hi_and_reserved &= 0x3F; //mask hash octet 8 out_uuid->clock_seq_hi_and_reserved |= 0x80; //Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively. return; } /*---------------------- AP_Create_UUID_ver3_random out_uuid - pointer to the final version 3 randomly generated uuid Use a high quality random number generator (still requiring a seed) to generate a random uuid. In 2.5 million creations, all have been unique (at least on Mac OS X with sranddev providing the initial seed). ----------------------*/ void AP_Create_UUID_ver3_random(ap_uuid_t* out_uuid) { uint32_t rand1 = 0; out_uuid->time_low = xor4096i(); rand1 = xor4096i(); out_uuid->time_mid = (rand1 >> 16) & 0xFFFF; out_uuid->node[0] = (rand1 >> 8) & 0xFF; out_uuid->node[1] = (rand1 >> 0) & 0xFF; rand1 = xor4096i(); out_uuid->node[2] = (rand1 >> 24) & 0xFF; out_uuid->node[3] = (rand1 >> 16) & 0xFF; out_uuid->node[4] = (rand1 >> 8) & 0xFF; out_uuid->node[5] = (rand1 >> 0) & 0xFF; rand1 = xor4096i(); out_uuid->time_hi_and_version = (rand1 >> 16) & 0xFFFF; out_uuid->clock_seq_low = (rand1 >> 8) & 0xFF; out_uuid->clock_seq_hi_and_reserved = (rand1 >> 0) & 0x3F; out_uuid->clock_seq_hi_and_reserved |= 0x40; //bits 6 & 7 must be 0 & 1 respectively out_uuid->time_hi_and_version &= 0x0FFF; out_uuid->time_hi_and_version |= (3 << 12); //set bits 12-15 to the version (3 for peusdo-random/random) return; } /*---------------------- APar_generate_uuid_from_atomname atom_name - the 4 character atom name to create a uuid for uuid_binary_str - the destination for the created uuid (as a hex string) This will create a 16-byte universal unique identifier for any atom name in the AtomicParsley namespace. 1. Make a namespace for all uuids to derive from (here its AP.sf.net) 2. Make a sha1 hash of the namespace string; use that hash as the basis for a v5 uuid into a NULL/blank uuid to create an AP namespace uuid 3. Make a sha2 hash of the atom_name string; use that hash as the basis for a v5 uuid into an AtomicParsley namespace uuid to create the final uuid. 4. copy the uuid structure into a binary string representation ----------------------*/ void APar_generate_uuid_from_atomname(char* atom_name, char* uuid_binary_str) { ap_uuid_t blank_namespace = { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; ap_uuid_t APar_namespace_uuid; ap_uuid_t AP_atom_uuid; AP_Create_UUID_ver5_sha1_name(&APar_namespace_uuid, blank_namespace, "AtomicParsley.sf.net", 20); AP_Create_UUID_ver5_sha1_name(&AP_atom_uuid, APar_namespace_uuid, atom_name, 4); memset(uuid_binary_str, 0, 20); memcpy(uuid_binary_str, &AP_atom_uuid, sizeof(AP_atom_uuid) ); return; } void APar_generate_random_uuid(char* uuid_binary_str) { ap_uuid_t rand_uuid = { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; AP_Create_UUID_ver3_random(&rand_uuid); memcpy(uuid_binary_str, &rand_uuid, sizeof(rand_uuid) ); return; } void APar_generate_test_uuid() { ap_uuid_t blank_ns = { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; ap_uuid_t APar_ns_uuid; ap_uuid_t APar_test_uuid; AP_Create_UUID_ver5_sha1_name(&APar_ns_uuid, blank_ns, "AtomicParsley.sf.net", 20); APar_print_uuid(&APar_ns_uuid); //should be aa80eaf3-1f72-5575-9faa-de9388dc2a90 fprintf(stdout, "uuid for 'cprt' in AP namespace: "); AP_Create_UUID_ver5_sha1_name(&APar_test_uuid, APar_ns_uuid, "cprt", 4); APar_print_uuid(&APar_test_uuid); //'cprt' should be 4bd39a57-e2c8-5655-a4fb-7a19620ef151 //uuid_t domain_ns_uuid = { 0x6ba7b810, 0x9dad, 0x11d1, // 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 }; // 6ba7b810-9dad-11d1-80b4-00c04fd430c8 a blank representation of a blank domain return; } atomicparsley-0.9.2~svn110.orig/src/AP_CDTOC.h0000755000000000000000000000426511226366037015566 0ustar //==================================================================// /* AtomicParsley - AP_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 */ //==================================================================// #include "AP_commons.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 #define DEVICE_TYPE ULONG #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(char* drive, char* dest_buffer); atomicparsley-0.9.2~svn110.orig/src/AP_CDTOC.cpp0000755000000000000000000004704511226366037016124 0ustar //==================================================================// /* AtomicParsley - AP_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 2006-2007 puck_lock */ //==================================================================// //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 #if defined HAVE_CONFIG_H #include "config.h" #endif #if defined (HAVE_LINUX_CDROM_H) #include #include #include #include #include #include /* From : */ //#define CDROM_LEADOUT 0xAA #elif defined (DARWIN_PLATFORM) #include #include #include #include #include #include #include #include const uint8_t MACOSX_LEADOUT_TRACK = 0xA2; #elif defined (WIN32) #include #include #endif #include "AP_CDTOC.h" 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 (DARWIN_PLATFORM) uint8_t LEADOUT_TRACK_NUMBER = MACOSX_LEADOUT_TRACK; #elif defined (HAVE_LINUX_CDROM_H) 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 (HAVE_LINUX_CDROM_H) 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->toc_length = 0; //not used anyway //cdTOC->first_session = 0; //not used anyway //cdTOC->last_session = 0; //not used anyway 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 (DARWIN_PLATFORM) 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) { uint16_t cdTOClen = 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 (memcmp(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 = CreateFile(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(char* drive, char* dest_buffer) { uint16_t mcdi_bytes = 0; #if defined (DARWIN_PLATFORM) mcdi_bytes = OSX_ProbeTargetDrive(drive, dest_buffer); #elif defined (HAVE_LINUX_CDROM_H) mcdi_bytes = Linux_ioctlProbeTargetDrive(drive, dest_buffer); #elif defined (WIN32) mcdi_bytes = Windows_ioctlProbeTargetDrive(drive, dest_buffer); #endif return mcdi_bytes; } atomicparsley-0.9.2~svn110.orig/src/AP_arrays.h0000644000000000000000000000265611226366037016232 0ustar //==================================================================// /* AtomicParsley - AP_arrays.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 */ //==================================================================// 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(); char* Expand_cli_mediastring(char* cli_rating); char* ID3GenreIntToString(int genre); uint8_t ID3StringGenreToInt(const char* genre_string); atomicparsley-0.9.2~svn110.orig/src/AP_ID3v2_FrameDefinitions.h0000644000000000000000000003553711226366037021072 0ustar //==================================================================// /* AtomicParsley - AP_ID3v2_FrameDefinitions.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 */ //==================================================================// 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", 4, "\xFF\xD8\xFF\xE0" }, { "image/jpeg", ".jpg", 4, "\xFF\xD8\xFF\xE1" }, { "image/png", ".png", 8, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" }, { "image/pdf", ".pdf", 7, "%PDF-1." }, { "image/jp2", ".jp2", 12, "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A\x00\x00\x00\x14\x66\x74\x79\x70\x6A\x70\x32\x20" }, { "image/gif", ".gif", 6, "GIF89a" }, { "image/tiff", ".tiff", 4, "\x4D\x4D\x00\x2A" }, { "image/tiff", ".tiff", 4, "\x49\x49\x2A\x00" }, { "image/bmp", ".bmp", 2, "\x42\x4D" }, { "image/bmp", ".bmp", 2, "\x42\x41" }, { "image/photoshop", ".psd", 4, "8BPS" }, { "image/other", ".img", 0, "" } }; ID3ImageType ImageTypeList[] = { { 0x00, "0x00", "Other" }, { 0x01, "0x01", "32x32 pixels 'file icon' (PNG only)" }, { 0x02, "0x02", "Other file icon" }, { 0x03, "0x03", "Cover (front)" }, { 0x04, "0x04", "Cover (back)" }, { 0x05, "0x05", "Leaflet page" }, { 0x06, "0x06", "Media (e.g. label side of CD)" }, { 0x07, "0x07", "Lead artist/lead performer/soloist" }, { 0x08, "0x08", "Artist/performer" }, { 0x09, "0x09", "Conductor" }, { 0x0A, "0x0A", "Band/Orchestra" }, { 0x0B, "0x0B", "Composer" }, { 0x0C, "0x0C", "Lyricist/text writer" }, { 0x0D, "0x0D", "Recording Location" }, { 0x0E, "0x0E", "During recording" }, { 0x0F, "0x0F", "During performance" }, { 0x10, "0x10", "Movie/video screen capture" }, { 0x11, "0x11", "A bright coloured fish" }, { 0x12, "0x12", "Illustration" }, { 0x13, "0x13", "Band/artist logotype" }, { 0x14, "0x14", "Publisher/Studio logotype" } }; atomicparsley-0.9.2~svn110.orig/src/extras/0000755000000000000000000000000011226366037015475 5ustar atomicparsley-0.9.2~svn110.orig/src/extras/getopt.h0000644000000000000000000001045511226366037017155 0ustar /* Declarations for getopt. Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _GETOPT_H #define _GETOPT_H 1 #ifdef __cplusplus extern "C" { #endif /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ extern char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ extern int optind; /* Callers store zero here to inhibit the error message `getopt' prints for unrecognized options. */ extern int opterr; /* Set to an option character which was unrecognized. */ extern int optopt; /* Describe the long-named options requested by the application. The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector of `struct option' terminated by an element containing a name which is zero. The field `has_arg' is: no_argument (or 0) if the option does not take an argument, required_argument (or 1) if the option requires an argument, optional_argument (or 2) if the option takes an optional argument. If the field `flag' is not NULL, it points to a variable that is set to the value given in the field `val' when the option is found, but left unchanged if the option is not found. To have a long-named option do something other than set an `int' to a compiled-in constant, such as set a value from `optarg', set the option's `flag' field to zero and its `val' field to a nonzero value (the equivalent single-letter option character, if there is one). For long options that have a zero `flag' field, `getopt' returns the contents of the `val' field. */ struct option { #if defined (__STDC__) && __STDC__ const char *name; #else char *name; #endif /* has_arg can't be an enum because some compilers complain about type mismatches in all the code that assumes it is an int. */ int has_arg; int *flag; int val; }; /* Names for the values of the `has_arg' field of `struct option'. */ #define no_argument 0 #define required_argument 1 #define optional_argument 2 #ifdef __GNU_LIBRARY__ /* Many other libraries have conflicting prototypes for getopt, with differences in the consts, in stdlib.h. To avoid compilation errors, only prototype getopt for the GNU C library. */ extern int getopt (int argc, char *const *argv, const char *shortopts); #else /* not __GNU_LIBRARY__ */ extern int getopt (); #endif /* __GNU_LIBRARY__ */ extern int getopt_long (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind); extern int getopt_long_only (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind); /* Internal only. Users should not call this directly. */ extern int _getopt_internal (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind, int long_only); #ifdef __cplusplus } #endif #endif /* getopt.h */ atomicparsley-0.9.2~svn110.orig/src/extras/getopt1.c0000644000000000000000000000522711226366037017232 0ustar /* getopt_long and getopt_long_only entry points for GNU getopt. Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include "getopt.h" #include /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 #include #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ #include #endif #ifndef NULL #define NULL 0 #endif int getopt_long (int argc, char *const *argv, const char *options, const struct option *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } /* Like getopt_long, but '-' as well as '--' can indicate a long option. If an option that starts with '-' (not '--') doesn't match a long option, but does match a short option, it is parsed as a short option instead. */ int getopt_long_only (int argc, char *const *argv, const char *options, const struct option *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 1); } #endif /* Not ELIDE_CODE. */ atomicparsley-0.9.2~svn110.orig/src/extras/getopt.c0000644000000000000000000007045711226366037017160 0ustar /* Getopt for GNU. NOTE: getopt is now part of the C library, so if you don't know what "Keep this file name-space clean" means, talk to drepper@gnu.org before changing it! Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This tells Alpha OSF/1 not to define a getopt prototype in . Ditto for AIX 3.2 and . */ #ifndef _NO_PROTO # define _NO_PROTO #endif #ifdef HAVE_CONFIG_H # include #endif #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ # ifndef const # define const # endif #endif #include /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 # include # if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION # define ELIDE_CODE # endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ # include # include #endif /* GNU C library. */ #ifdef VMS # include # if HAVE_STRING_H - 0 # include # endif #endif #ifndef _ /* This is for other GNU distributions with internationalized messages. When compiling libc, the _ macro is predefined. */ # ifdef HAVE_LIBINTL_H # include # define _(msgid) gettext (msgid) # else # define _(msgid) (msgid) # endif #endif /* This version of `getopt' appears to the caller like standard Unix `getopt' but it behaves differently for the user, since it allows the user to intersperse the options with the other arguments. As `getopt' works, it permutes the elements of ARGV so that, when it is done, all the options precede everything else. Thus all application programs are extended to handle flexible argument order. Setting the environment variable POSIXLY_CORRECT disables permutation. Then the behavior is completely standard. GNU application programs can use a third alternative mode in which they can distinguish the relative order of options and other arguments. */ #include "getopt.h" /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ char *optarg = NULL; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* 1003.2 says this must be 1 before any call. */ int optind = 1; /* Formerly, initialization of getopt depended on optind==0, which causes problems with re-calling getopt as programs generally don't know that. */ int __getopt_initialized = 0; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ int optopt = '?'; /* Describe how to deal with options that follow non-option ARGV-elements. If the caller did not specify anything, the default is REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise. REQUIRE_ORDER means don't recognize them as options; stop option processing when the first non-option is seen. This is what Unix does. This mode of operation is selected by either setting the environment variable POSIXLY_CORRECT, or using `+' as the first character of the list of option characters. PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. RETURN_IN_ORDER is an option available to programs that were written to expect options and other ARGV-elements in any order and that care about the ordering of the two. We describe each non-option ARGV-element as if it were the argument of an option with character code 1. Using `-' as the first character of the list of option characters selects this mode of operation. The special argument `--' forces an end of option-scanning regardless of the value of `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return -1 with `optind' != ARGC. */ static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; /* Value of POSIXLY_CORRECT environment variable. */ static char *posixly_correct; #ifdef __GNU_LIBRARY__ /* We want to avoid inclusion of string.h with non-GNU libraries because there are many ways it can cause trouble. On some systems, it contains special magic macros that don't work in GCC. */ # include # define my_index strchr #else #define HAVE_STRING_H 1 # if HAVE_STRING_H # include # else # include # endif /* Avoid depending on library functions or files whose names are inconsistent. */ #ifndef getenv extern char *getenv (); #endif static char * my_index (str, chr) const char *str; int chr; { while (*str) { if (*str == chr) return (char *) str; str++; } return 0; } /* If using GCC, we can safely declare strlen this way. If not using GCC, it is ok not to declare it. */ #ifdef __GNUC__ /* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. That was relevant to code that was here before. */ # if (!defined __STDC__ || !__STDC__) && !defined strlen /* gcc with -traditional declares the built-in strlen to return int, and has done so at least since version 2.4.5. -- rms. */ extern int strlen (const char *); # endif /* not __STDC__ */ #endif /* __GNUC__ */ #endif /* not __GNU_LIBRARY__ */ /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; #ifdef _LIBC /* Bash 2.0 gives us an environment variable containing flags indicating ARGV elements that should not be considered arguments. */ /* Defined in getopt_init.c */ extern char *__getopt_nonoption_flags; static int nonoption_flags_max_len; static int nonoption_flags_len; static int original_argc; static char *const *original_argv; /* Make sure the environment variable bash 2.0 puts in the environment is valid for the getopt call we must make sure that the ARGV passed to getopt is that one passed to the process. */ static void __attribute__ ((unused)) store_args_and_env (int argc, char *const *argv) { /* XXX This is no good solution. We should rather copy the args so that we can compare them later. But we must not use malloc(3). */ original_argc = argc; original_argv = argv; } # ifdef text_set_element text_set_element (__libc_subinit, store_args_and_env); # endif /* text_set_element */ # define SWAP_FLAGS(ch1, ch2) \ if (nonoption_flags_len > 0) \ { \ char __tmp = __getopt_nonoption_flags[ch1]; \ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ __getopt_nonoption_flags[ch2] = __tmp; \ } #else /* !_LIBC */ # define SWAP_FLAGS(ch1, ch2) #endif /* _LIBC */ /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The other is elements [last_nonopt,optind), which contains all the options processed since those non-options were skipped. `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. */ #if defined __STDC__ && __STDC__ static void exchange (char **); #endif static void exchange (argv) char **argv; { int bottom = first_nonopt; int middle = last_nonopt; int top = optind; char *tem; /* Exchange the shorter segment with the far end of the longer segment. That puts the shorter segment into the right place. It leaves the longer segment in the right place overall, but it consists of two parts that need to be swapped next. */ #ifdef _LIBC /* First make sure the handling of the `__getopt_nonoption_flags' string can work normally. Our top argument must be in the range of the string. */ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { /* We must extend the array. The user plays games with us and presents new arguments. */ char *new_str = malloc (top + 1); if (new_str == NULL) nonoption_flags_len = nonoption_flags_max_len = 0; else { memset (__mempcpy (new_str, __getopt_nonoption_flags, nonoption_flags_max_len), '\0', top + 1 - nonoption_flags_max_len); nonoption_flags_max_len = top + 1; __getopt_nonoption_flags = new_str; } } #endif while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { /* Bottom segment is the short one. */ int len = middle - bottom; register int i; /* Swap it with the top part of the top segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); } /* Exclude the moved bottom segment from further swapping. */ top -= len; } else { /* Top segment is the short one. */ int len = top - middle; register int i; /* Swap it with the bottom part of the bottom segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; SWAP_FLAGS (bottom + i, middle + i); } /* Exclude the moved top segment from further swapping. */ bottom += len; } } /* Update records for the slots the non-options now occupy. */ first_nonopt += (optind - last_nonopt); last_nonopt = optind; } /* Initialize the internal data when the first call is made. */ #if defined __STDC__ && __STDC__ static const char *_getopt_initialize (int, char *const *, const char *); #endif static const char * _getopt_initialize (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { /* Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ first_nonopt = last_nonopt = optind; nextchar = NULL; posixly_correct = getenv ("POSIXLY_CORRECT"); /* Determine how to handle the ordering of options and nonoptions. */ if (optstring[0] == '-') { ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; } else if (posixly_correct != NULL) ordering = REQUIRE_ORDER; else ordering = PERMUTE; #ifdef _LIBC if (posixly_correct == NULL && argc == original_argc && argv == original_argv) { if (nonoption_flags_max_len == 0) { if (__getopt_nonoption_flags == NULL || __getopt_nonoption_flags[0] == '\0') nonoption_flags_max_len = -1; else { const char *orig_str = __getopt_nonoption_flags; int len = nonoption_flags_max_len = strlen (orig_str); if (nonoption_flags_max_len < argc) nonoption_flags_max_len = argc; __getopt_nonoption_flags = (char *) malloc (nonoption_flags_max_len); if (__getopt_nonoption_flags == NULL) nonoption_flags_max_len = -1; else memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), '\0', nonoption_flags_max_len - len); } } nonoption_flags_len = nonoption_flags_max_len; } else nonoption_flags_len = 0; #endif return optstring; } /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns -1. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal (argc, argv, optstring, longopts, longind, long_only) int argc; char *const *argv; const char *optstring; const struct option *longopts; int *longind; int long_only; { optarg = NULL; if (optind == 0 || !__getopt_initialized) { if (optind == 0) optind = 1; /* Don't scan ARGV[0], the program name. */ optstring = _getopt_initialize (argc, argv, optstring); __getopt_initialized = 1; } /* Test whether ARGV[optind] points to a non-option argument. Either it does not have option syntax, or there is an environment flag from the shell indicating it is not an option. The later information is only used when the used in the GNU libc. */ #ifdef _LIBC # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ || (optind < nonoption_flags_len \ && __getopt_nonoption_flags[optind] == '1')) #else # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif if (nextchar == NULL || *nextchar == '\0') { /* Advance to the next ARGV-element. */ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been moved back by the user (who may also have changed the arguments). */ if (last_nonopt > optind) last_nonopt = optind; if (first_nonopt > optind) first_nonopt = optind; if (ordering == PERMUTE) { /* If we have just processed some options following some non-options, exchange them so that the options come first. */ if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (last_nonopt != optind) first_nonopt = optind; /* Skip any additional non-options and extend the range of non-options previously skipped. */ while (optind < argc && NONOPTION_P) optind++; last_nonopt = optind; } /* The special ARGV-element `--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (optind != argc && !strcmp (argv[optind], "--")) { optind++; if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (first_nonopt == last_nonopt) first_nonopt = optind; last_nonopt = argc; optind = argc; } /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return -1; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if (NONOPTION_P) { if (ordering == REQUIRE_ORDER) return -1; optarg = argv[optind++]; return 1; } /* We have found another option-ARGV-element. Skip the initial punctuation. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } /* Decode the current option-ARGV-element. */ /* Check whether the ARGV-element is a long option. If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't consider it an abbreviated form of a long option that starts with f. Otherwise there would be no way to give the -f short option. On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg "u". This distinction seems to be the most useful approach. */ if (longopts != NULL && (argv[optind][1] == '-' || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = -1; int option_index; for (nameend = nextchar; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == (unsigned int) strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; optopt = 0; return '?'; } if (pfound != NULL) { option_index = indfound; optind++; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) if (argv[optind - 1][1] == '-') /* --option */ fprintf (stderr, _("%s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); else /* +option or -option */ fprintf (stderr, _("%s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); nextchar += strlen (nextchar); optopt = pfound->val; return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen (nextchar); optopt = pfound->val; return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' || my_index (optstring, *nextchar) == NULL) { if (opterr) { if (argv[optind][1] == '-') /* --option */ fprintf (stderr, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); else /* +option or -option */ fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); } nextchar = (char *) ""; optind++; optopt = 0; return '?'; } } /* Look at and handle the next short option-character. */ { char c = *nextchar++; char *temp = my_index (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') ++optind; if (temp == NULL || c == ':') { if (opterr) { if (posixly_correct) /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c); else fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c); } optopt = c; return '?'; } /* Convenience. Treat POSIX -W foo same as long option --foo */ if (temp[0] == 'W' && temp[1] == ';') { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; return c; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; /* optarg is now the argument, see if it's in the table of longopts. */ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; return '?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) fprintf (stderr, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name); nextchar += strlen (nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen (nextchar); return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } nextchar = NULL; return 'W'; /* Let the application handle it. */ } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = NULL; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } int getopt (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { return _getopt_internal (argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); } #endif /* Not ELIDE_CODE. */ atomicparsley-0.9.2~svn110.orig/src/AP_iconv.h0000644000000000000000000002125011226366037016036 0ustar //==================================================================// /* AtomicParsley - AP_iconv.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 */ //==================================================================// 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" extern int xmlLittleEndian; void xmlInitEndianDetection(); 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); /*------------- these functions are independent of any secondary licensing -------------*/ 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); atomicparsley-0.9.2~svn110.orig/src/AP_NSFile_utils.h0000644000000000000000000000215011226366037017256 0ustar //==================================================================// /* AtomicParsley - AP_NSFile_utils.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 */ //==================================================================// 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); atomicparsley-0.9.2~svn110.orig/src/Makefile.in0000644000000000000000000001263311226366037016241 0ustar # Copyright (C) 1994, 1995-8, 1999 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. SHELL = @SHELL@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ VPATH = @srcdir@ prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ datadir = @datadir@ exec_prefix = @exec_prefix@ includedir = @includedir@ infodir = @infodir@ libdir = @libdir@ libexecdir = @libexecdir@ localstatedir = @localstatedir@ mandir = @mandir@ oldincludedir = @oldincludedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sysconfdir = @sysconfdir@ CXX = @CXX@ CXXFLAGS = @CXXFLAGS@ DEFS = @DEFS@ LIBS = @LIBS@ LDFLAGS = @LDFLAGS@ INSTALL = @INSTALL@ DEBUG = @debug@ HAVE_LIBZ = @HAVE_LIBZ@ OS_NAME = @os_name@ HAVE_DARWIN_PLATFORM = @HAVE_DARWIN_PLATFORM@ DARWIN_BUILD_UNIVERSAL = @universal@ DARWIN_NATIVE_ARCH = @AP_NATIVE_ARCH@ DARWIN_CROSS_ARCH = @AP_CROSS_ARCH@ DARWIN_UNIVERSAL_SDK_ROOT = @DARWIN_U_SYSROOT@ HAVE_GETOPT = @HAVE_GETOPT_H@ CXXFLAGS+= -Wall -Wno-unused CPPFLAGS+=-DHAVE_CONFIG_H EXTRA_LIBS = -lz LINK = link STRIP = ALL_OBJ = $(AP_OBJ_DIR)/*.o AP_OBJ_DIR = $(top_srcdir)/obj_dir COMMON_SOURCE_FILES = AP_commons.cpp AP_arrays.cpp AP_iconv.cpp \ AtomicParsley.cpp AP_AtomExtracts.cpp APar_sha1.cpp APar_uuid.cpp \ AP_ID3v2_tags.cpp AP_MetadataListings.cpp AP_CDTOC.cpp main.cpp COMMON_OBJ = makeworkdir $(AP_OBJ_DIR)/AP_commons.o \ $(AP_OBJ_DIR)/AP_arrays.o \ $(AP_OBJ_DIR)/AP_iconv.o \ $(AP_OBJ_DIR)/AtomicParsley.o \ $(AP_OBJ_DIR)/AP_AtomExtracts.o \ $(AP_OBJ_DIR)/APar_sha1.o \ $(AP_OBJ_DIR)/APar_uuid.o \ $(AP_OBJ_DIR)/AP_ID3v2_tags.o \ $(AP_OBJ_DIR)/AP_MetadataListings.o \ $(AP_OBJ_DIR)/AP_CDTOC.o \ $(AP_OBJ_DIR)/main.o ifneq ($(HAVE_GETOPT), 1) COMMON_SOURCE_FILES+= extras/getopt.c extras/getopt1.c COMMON_OBJ+= $(AP_OBJ_DIR)/getopt.o $(AP_OBJ_DIR)/getopt1.o endif ifeq ($(DEBUG), yes) CPPFLAGS+=-DDEBUG_V STRIP = strip $(top_srcdir)/AtomicParsley endif ifeq ("$(OS_NAME)", "Darwin") COMMON_SOURCE_FILES+= AP_NSFile_utils.mm AP_NSImage.mm COMMON_OBJ+= $(AP_OBJ_DIR)/AP_NSFile_utils.o $(AP_OBJ_DIR)/AP_NSImage.o CPPFLAGS+=-DDARWIN_PLATFORM LDFLAGS+=-framework Cocoa -framework Foundation -framework IOKit endif ifeq ($(HAVE_LIBZ), 1) COMMON_OBJ+=$(AP_OBJ_DIR)/APar_zlib.o endif ifeq ("$(DARWIN_BUILD_UNIVERSAL)", "yes") STRIP = strip $(top_srcdir)/AtomicParsley_$(DARWIN_NATIVE_ARCH); \ strip $(top_srcdir)/AtomicParsley_$(DARWIN_CROSS_ARCH) LINK = link-uiniversal #COMMON_OBJ = $(patsubst %_$(DARWIN_NATIVE_ARCH).o,%.o,$(wildcard *.o)) endif #------------------------------------------------------------------------- all: $(COMMON_OBJ) $(LINK) .SUFFIXES: .cpp .c .mm .o makeworkdir: mkdir -p $(AP_OBJ_DIR) $(AP_OBJ_DIR)/%.o: %.c @echo "$< ..." $(CXX) $(CXXFLAGS) -I../src $(CPPFLAGS) $(EXTRACPPFLAGS) -MT $(AP_OBJ_DIR)/$*.o -MD -MP -MF $(AP_OBJ_DIR)/$*.Tpo -c -o $(AP_OBJ_DIR)/$*.o $*.c $(AP_OBJ_DIR)/%.o: %.cpp ifeq ("$(DARWIN_BUILD_UNIVERSAL)", "yes") @echo "$< ..." $(CXX) -arch $(DARWIN_NATIVE_ARCH) $(CXXFLAGS) -I../src $(CPPFLAGS) $(EXTRACPPFLAGS) -MT $(AP_OBJ_DIR)/$*.o -MD -MP -MF $(AP_OBJ_DIR)/$*.Tpo -c -o $(AP_OBJ_DIR)/$*_$(DARWIN_NATIVE_ARCH).o $*.cpp $(CXX) -arch $(DARWIN_CROSS_ARCH) $(CXXFLAGS) -I../src $(CPPFLAGS) $(EXTRACPPFLAGS) -MT $(AP_OBJ_DIR)/$*.o -MD -MP -MF $(AP_OBJ_DIR)/$*.Tpo -c -o $(AP_OBJ_DIR)/$*_$(DARWIN_CROSS_ARCH).o $*.cpp else $(CXX) $(CXXFLAGS) -I../src $(CPPFLAGS) $(EXTRACPPFLAGS) -MT $(AP_OBJ_DIR)/$*.o -MD -MP -MF $(AP_OBJ_DIR)/$*.Tpo -c -o $(AP_OBJ_DIR)/$*.o $*.cpp endif $(AP_OBJ_DIR)/%.o: %.mm ifeq ("$(DARWIN_BUILD_UNIVERSAL)", "yes") @echo "$< ..." $(CXX) -arch $(DARWIN_NATIVE_ARCH) $(CXXFLAGS) -I../src $(CPPFLAGS) $(EXTRACPPFLAGS) -MT $(AP_OBJ_DIR)/$*.o -MD -MP -MF $(AP_OBJ_DIR)/$*.Tpo -c -o $(AP_OBJ_DIR)/$*_$(DARWIN_NATIVE_ARCH).o $*.mm $(CXX) -arch $(DARWIN_CROSS_ARCH) $(CXXFLAGS) -I../src $(CPPFLAGS) $(EXTRACPPFLAGS) -MT $(AP_OBJ_DIR)/$*.o -MD -MP -MF $(AP_OBJ_DIR)/$*.Tpo -c -o $(AP_OBJ_DIR)/$*_$(DARWIN_CROSS_ARCH).o $*.mm else $(CXX) $(CXXFLAGS) -I../src $(CPPFLAGS) $(EXTRACPPFLAGS) -MT $(AP_OBJ_DIR)/$*.o -MD -MP -MF $(AP_OBJ_DIR)/$*.Tpo -c -o $(AP_OBJ_DIR)/$*.o $*.mm endif link: @echo "Linking ..." $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(top_srcdir)/AtomicParsley $(ALL_OBJ) $(EXTRA_LIBS) $(STRIP) link-uiniversal: @echo "Linking & lipo joining ..." $(CXX) -arch $(DARWIN_NATIVE_ARCH) $(CXXFLAGS) $(LDFLAGS) -o $(top_srcdir)/AtomicParsley_$(DARWIN_NATIVE_ARCH) $(AP_OBJ_DIR)/*_$(DARWIN_NATIVE_ARCH).o $(EXTRA_LIBS) $(CXX) -arch $(DARWIN_CROSS_ARCH) $(CXXFLAGS) $(LDFLAGS) -isysroot $(DARWIN_UNIVERSAL_SDK_ROOT) -o $(top_srcdir)/AtomicParsley_$(DARWIN_CROSS_ARCH) $(AP_OBJ_DIR)/*_$(DARWIN_CROSS_ARCH).o $(EXTRA_LIBS) $(STRIP) lipo -arch $(DARWIN_NATIVE_ARCH) $(top_srcdir)/AtomicParsley_$(DARWIN_NATIVE_ARCH) -arch $(DARWIN_CROSS_ARCH) $(top_srcdir)/AtomicParsley_$(DARWIN_CROSS_ARCH) -create -output $(top_srcdir)/AtomicParsley clean: rm -rf $(AP_OBJ_DIR) install: uninstall: maint-clean: dist-clean: .PHONY: all clean install dist-clean maint-clean # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: atomicparsley-0.9.2~svn110.orig/src/AP_AtomExtracts.h0000644000000000000000000000546311226366037017346 0ustar //==================================================================// /* AtomicParsley - AP_AtomExtracts.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 */ //==================================================================// #include "AP_commons.h" /* win32 requires the uintX_t's defined */ 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; uint32_t section3_length; uint32_t section4_length; uint8_t ObjectTypeIndication; uint32_t max_bitrate; uint32_t avg_bitrate; uint32_t section5_length; uint8_t descriptor_object_typeID; uint16_t channels; uint32_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 }; void APar_ExtractDetails(FILE* isofile, uint8_t optional_output); void APar_ExtractBrands(char* filepath); atomicparsley-0.9.2~svn110.orig/src/AtomicParsley.cpp0000644000000000000000000065341111226366037017461 0ustar //==================================================================// /* AtomicParsley - AtomicParsley.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 2005-2007 puck_lock ---------------------- 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 */ //==================================================================// #if defined HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #if defined (HAVE_UNISTD_H) #include #elif defined (_MSC_VER) #include #endif #include "AtomicParsley.h" #include "AP_AtomDefinitions.h" #include "AP_iconv.h" #include "AP_arrays.h" #include "APar_uuid.h" #include "AP_ID3v2_tags.h" #include "AP_MetadataListings.h" #if defined (DARWIN_PLATFORM) #include "AP_NSImage.h" #include "AP_NSFile_utils.h" #endif //#define DEBUG_V /////////////////////////////////////////////////////////////////////////////////////// // Global Variables // /////////////////////////////////////////////////////////////////////////////////////// bool modified_atoms = false; bool alter_original = false; bool svn_build = true; //controls which type of versioning - svn build-time stamp //bool svn_build = false; //controls which type of versioning - release number FILE* source_file = NULL; uint32_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; uint32_t max_buffer = 4096*125; // increased to 512KB uint32_t bytes_before_mdat=0; uint32_t bytes_into_mdat = 0; uint64_t mdat_supplemental_offset = 0; uint32_t removed_bytes_tally = 0; uint32_t new_file_size = 0; //used for the progressbar uint32_t brand = 0; uint32_t mdatData = 0; //now global, used in bitrate calcs uint32_t gapless_void_padding = 0; //possibly used in the context of gapless playback support by Apple struct DynamicUpdateStat dynUpd; struct padding_preferences pad_prefs; bool contains_unsupported_64_bit_atom = false; //reminder that there are some 64-bit files that aren't yet supported (and where that limit is set) #if defined (WIN32) || defined (__CYGWIN__) short max_display_width = 45; #else short max_display_width = 75; //ah, figured out grub - vga=773 makes a whole new world open up #endif char* file_progress_buffer=(char*)calloc(1, sizeof(char)* (max_display_width+50) ); //+50 for any overflow in "%100", or "|" #if defined (DARWIN_PLATFORM) 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 (_MSC_VER) char unicode_enabled[12]; memset(unicode_enabled, 0, 12); if (UnicodeOutputStatus == WIN32_UTF16) { memcpy(unicode_enabled, "(utf16)", 7); //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) { memcpy(unicode_enabled, "(raw utf8)", 10); //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 if (svn_build) { //below is the versioning from svn if used; remember to switch to AtomicParsley_version for a release fprintf(stdout, "AtomicParsley from svn built on %s %s\n", __DATE__, unicode_enabled); } else { //below is the release versioning fprintf(stdout, "AtomicParsley version: %s %s\n", AtomicParsley_version, unicode_enabled); //release version } return; } /////////////////////////////////////////////////////////////////////////////////////// // Generic Functions // /////////////////////////////////////////////////////////////////////////////////////// int APar_TestArtworkBinaryData(const char* artworkPath) { int artwork_dataType = 0; FILE *artfile = APar_OpenFile(artworkPath, "rb"); if (artfile != NULL) { fread(twenty_byte_buffer, 1, 8, artfile); if ( strncmp(twenty_byte_buffer, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) == 0 ) { artwork_dataType = AtomFlags_Data_PNGBinary; } else if ( strncmp(twenty_byte_buffer, "\xFF\xD8\xFF\xE0", 4) == 0 || memcmp(twenty_byte_buffer, "\xFF\xD8\xFF\xE1", 4) == 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; } #if defined (DARWIN_PLATFORM) // enables writing out the contents of a single memory-resident atom out to a text file; for in-house testing purposes only - and unused in some time void APar_AtomicWriteTest(short AtomicNumber, bool binary) { AtomicInfo anAtom = parsedAtoms[AtomicNumber]; 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/singleton_atom.txt"); FILE* single_atom_file; single_atom_file = fopen(indy_atom_path, "wb"); if (single_atom_file != NULL) { if (binary) { fwrite(anAtom.AtomicData, (size_t)(anAtom.AtomicLength - 12), 1, single_atom_file); } else { char* data = (char*)malloc(sizeof(char)*4); UInt32_TO_String4(anAtom.AtomicLength, data); fwrite(data, 4, 1, single_atom_file); fwrite(anAtom.AtomicName, 4, 1, single_atom_file); UInt32_TO_String4((uint32_t)anAtom.AtomicVerFlags, data); fwrite(data, 4, 1, single_atom_file); fwrite(anAtom.AtomicData, strlen(anAtom.AtomicData)+1, 1, single_atom_file); free(data); } } fclose(single_atom_file); free(indy_atom_path); return; } #endif void APar_FreeMemory() { for(int iter=0; iter < atom_number; iter++) { 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 (DARWIN_PLATFORM) 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 (memcmp(unparsed_opts,"MaxDimensions=",14) == 0) { unparsed_opts+=14; myPicturePrefs.max_dimension = (int)strtol(unparsed_opts, NULL, 10); } else if (memcmp(unparsed_opts,"DPI=",4) == 0) { unparsed_opts+=4; myPicturePrefs.dpi = (int)strtol(unparsed_opts, NULL, 10); } else if (memcmp(unparsed_opts,"MaxKBytes=",10) == 0) { unparsed_opts+=10; myPicturePrefs.max_Kbytes = (int)strtol(unparsed_opts, NULL, 10)*1024; } else if (memcmp(unparsed_opts,"AllPixJPEG=",11) == 0) { unparsed_opts+=11; if (memcmp(unparsed_opts, "true", 4) == 0) { myPicturePrefs.allJPEG = true; } } else if (memcmp(unparsed_opts,"AllPixPNG=",10) == 0) { unparsed_opts+=10; if (memcmp(unparsed_opts, "true", 4) == 0) { myPicturePrefs.allPNG = true; } } else if (memcmp(unparsed_opts,"AddBothPix=",11) == 0) { unparsed_opts+=11; if (memcmp(unparsed_opts, "true", 4) == 0) { myPicturePrefs.addBOTHpix = true; } } else if (memcmp(unparsed_opts,"SquareUp",7) == 0) { unparsed_opts+=7; myPicturePrefs.squareUp = true; } else if (strncmp(unparsed_opts,"removeTempPix",13) == 0) { unparsed_opts+=13; myPicturePrefs.removeTempPix = true; } else if (memcmp(unparsed_opts,"keepTempPix",11) == 0) { //NEW unparsed_opts+=11; myPicturePrefs.removeTempPix = false; } else if (memcmp(unparsed_opts,"ForceHeight=",12) == 0) { unparsed_opts+=12; myPicturePrefs.force_height = strtol(unparsed_opts, NULL, 10); } else if (memcmp(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, 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, 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) size_t proto_rdns_len = strlen(proto_atom->ReverseDNSname) + 1; size_t test_rdns_len = strlen(parsedAtoms[test_atom].ReverseDNSname) + 1; size_t rdns_strlen = (proto_rdns_len > test_rdns_len? proto_rdns_len : test_rdns_len); if (memcmp(proto_atom->ReverseDNSname, parsedAtoms[test_atom].ReverseDNSname, rdns_strlen) == 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 ( memcmp(reverseDNSdomain, parsedAtoms[APar_FindPrecedingAtom(test_atom)].ReverseDNSdomain, strlen(reverseDNSdomain)+1) == 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 && memcmp(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 (memcmp(search_atom_name+4, ":[", 2) == 0 && memcmp(search_atom_name + portion_len -1, "]", 1) == 0) { 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 (memcmp(search_atom_name+4, "[", 1) == 0) { sscanf(search_atom_name+5, "%hhu", &desired_index); #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 (memcmp(search_atom_name, ".", 1) == 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(uint32_t Astart, uint32_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 = &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 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(uint32_t atom_start, uint32_t atom_length) { short 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 0x6D703432 : //'mp42' case 0x6D703431 : //'mp41' case 0x69736F6D : //'isom' case 0x69736F32 : //'iso2' case 0x61766331 : //'avc1' case 0x4D345648 : //'MV4H' 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, uint32_t atom_start, uint32_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, uint32_t midJump) { memset(twenty_byte_buffer, 0, 12); fseeko(file, midJump, SEEK_SET); fread(twenty_byte_buffer, 1, 12, file); 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, (uint16_t)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[%hhu].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; } uint64_t APar_64bitAtomRead(FILE *file, uint32_t jump_point) { uint64_t extended_dataSize = APar_read64(twenty_byte_buffer, file, jump_point+8); //here's the problem: there can be some HUGE MPEG-4 files. They get specified by a 64-bit value. The specification says only use them when necessary - I've seen them on files about 700MB. So this will artificially place a limit on the maximum file size that would be supported under a 32-bit only AtomicParsley (but could still see & use smaller 64-bit values). For my 700MB file, moov was (rounded up) 4MB. So say 4MB x 6 +1MB give or take, and the biggest moov atom I would support is.... a heart stopping 30MB (rounded up). GADZOOKS!!! So, since I have no need to go greater than that EVER, I'm going to stik with uin32_t for filesizes and offsets & that sort. But a smaller 64-bit mdat (essentially a pseudo 32-bit traditional mdat) can be supported as long as its less than UINT32_T_MAX (minus our big fat moov allowance). if ( extended_dataSize > 4294967295UL - 30000000) { contains_unsupported_64_bit_atom = true; fprintf(stdout, "You must be off your block thinking I'm going to tag a file that is at LEAST %llu bytes long.\n", extended_dataSize); fprintf(stdout, "AtomicParsley doesn't have full 64-bit support"); exit (2); } return extended_dataSize; } /*---------------------- 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 = (uint32_t)(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 (memcmp(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 (memcmp(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 (memcmp(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 ( memcmp(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 ( memcmp(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(uint32_t atom_start, uint32_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) { memcpy(container, "FILE_LEVEL", 11); } } return; } /*---------------------- 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 12 bytes (to accommodate flags & versioning). 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 listed has having a language attribute, it is read to support multiple langauges (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; uint32_t dataSize = 0; uint32_t jump = 0; fread(data, 1, 12, file); 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 < (uint32_t)file_size) { uuid_info.uuid_form = UUID_DEPRECATED_FORM; //start with the assumption that any found atom is in the depracted uuid form fread(data, 1, 12, file); char *atom = data+4; dataSize = UInt32FromBigEndian(data); if (jpeg2000signature) { if (memcmp(atom, "ftyp", 4) == 0) { APar_IdentifyBrand( data + 8 ); } else { exit(0); //the atom right after the jpeg2000/mjpeg2000 signature is *supposed* to be 'ftyp' } jpeg2000signature = false; } if ( dataSize > (uint64_t)file_size) { dataSize = (uint32_t)(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, 20); APar_readX(uuid_info.binary_uuid, file, jump+8, 16); if (UInt32FromBigEndian(uuid_info.binary_uuid+8) == 0) { //the deperacted uuid form atom = data+8; atom_verflags = APar_read32(uuid_info.binary_uuid, file, jump+12); if (atom_verflags > (uint32_t)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 = UInt32FromBigEndian(data+8); //flags & versioning were already read in with the original 12 bytes } if (KnownAtoms[filtered_known_atom].box_type == PACKED_LANG_ATOM) { //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_64bitAtomRead(file, jump); 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); fseeko(file, jump + 12, SEEK_SET); //'name' atom is the 2nd child fread(parsedAtoms[atom_number-1].ReverseDNSdomain, 1, dataSize - 12, file); } 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); fseeko(file, jump + 12, SEEK_SET); //'name' atom is the 2nd child fread(parsedAtoms[atom_number-1].ReverseDNSname, 1, dataSize - 12, file); } 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 : { short parent_atom = APar_FindParentAtom(atom_number-1, generalAtomicLevel); //to accommodate the retarted utility that keeps putting in 'prjp' atoms in mpeg-4 files written QTstyle if (parsedAtoms[parent_atom].AtomicContainerState == DUAL_STATE_ATOM) { jump = parsedAtoms[parent_atom].AtomicStart + parsedAtoms[parent_atom].AtomicLength; } else { jump += dataSize; } break; } } //end swtich generalAtomicLevel = APar_GetCurrentAtomDepth(jump, dataSize); if ( (jump > 8 ? jump : 8) >= (uint32_t)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, 100); 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 %u 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(char* atom_name, uint8_t cntr_state, uint8_t atom_class, uint32_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; } 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) { 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, uint32_t ancillary_data, uint8_t anc_bit_width) { uint32_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] = 0xFE; //BOM target_atom->AtomicData[atom_data_pos+1] = 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, 255); //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 (total_bytes > MAXDATA_PAYLOAD) { free(target_atom->AtomicData); target_atom->AtomicData = NULL; target_atom->AtomicData = (char*)malloc( sizeof(char)* (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, uint32_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, (uint32_t)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_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, false); off_t picture_size = findFileSize(artworkPath); if (picture_size > 0) { APar_MetaData_atom_QuickInit(atom_num, APar_TestArtworkBinaryData(artworkPath), 0, (uint32_t)picture_size ); FILE* artfile = APar_OpenFile(artworkPath, "rb"); uint32_t bytes_read = APar_ReadFile(parsedAtoms[atom_num].AtomicData + 4, artfile, (uint32_t)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 (memcmp(artworkPath, "REMOVE_ALL", 10) == 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 }; short parent_atom = desiredAtom->AtomicNumber; //used on Darwin adding a 2nd image (the original) 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 (DARWIN_PLATFORM) //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, 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) { uint32_t formed_string_offset = 0; uint32_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] = 0xFE; //BOM formed_keyword_struct[formed_string_offset+2] = 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, 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, 20); memset(uuid_binary_str, 0, 20); memset(uuid_4char_name, 0, 10); 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 (%u-%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(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 (memcmp(rDNS_domain, "com.apple.iTunes", 17) == 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(char* frameID_str, char meta_area, 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) { uint32_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) { uint32_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... uint32_t APar_DetermineMediaData_AtomPosition() { uint32_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 ? (uint32_t )parsedAtoms[stop_atom].AtomicLengthExtended : parsedAtoms[stop_atom].AtomicLength); //fprintf(stdout, "%i %s (%u)\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 ----------------------*/ uint32_t APar_QuickSumAtomicLengths(AtomicInfo* target_atom) { uint32_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 ? (uint32_t)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 ? (uint32_t)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(uint32_t desired_data_pos, uint32_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 + (uint32_t)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); uint32_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++) { uint16_t an_item_ID = UInt16FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset); uint16_t a_data_ref_idx = UInt16FromBigEndian(parsedAtoms[iloc_number].AtomicData+aggregate_offset+2); uint32_t base_offset = 0; uint32_t curr_container_pos = 0; uint32_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 = (uint32_t)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++) { uint32_t this_extent_offset = 0; uint32_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 = (uint32_t)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 = (uint32_t)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: %u, total bytes %u\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); uint32_t exisiting_offset_into_atom = base_offset - container_atom->AtomicStart; uint32_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 @ %u (now at %u)\n", container_atom->AtomicStart, curr_container_pos); fprintf(stdout, "debug: AP_Readjust_iloc_atom item is %u bytes offset into atom (was %u, now %u)\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((uint64_t)new_item_offset, base_offset_ptr); } iloc_changed = true; } } } return iloc_changed; } bool APar_Readjust_CO64_atom(uint32_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(uint32_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... memset(tfhd_atomFlags_scrap, 0, 10); memcpy(tfhd_atomFlags_scrap, parsedAtoms[tfhd_number].AtomicData, 4); uint32_t track_ID = UInt32FromBigEndian(tfhd_atomFlags_scrap); //unused 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(uint32_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(uint32_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(uint32_t new_padding_length) { uint32_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; if (new_padding_length - 2000 < 8) { 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. ----------------------*/ uint32_t APar_PaddingAmount(uint32_t target_amount, bool limit_by_prefs) { uint32_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; } uint32_t mdat_pos = 0; uint32_t moov_udta_pos = APar_QuickSumAtomicLengths(dynUpd.moov_udta_atom); uint32_t moov_last_trak_pos = APar_QuickSumAtomicLengths(dynUpd.last_trak_child_atom); uint32_t moov_meta_pos = APar_QuickSumAtomicLengths(dynUpd.moov_meta_atom); uint32_t moov_pos = APar_QuickSumAtomicLengths(dynUpd.moov_atom); uint32_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) { uint32_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 { uint32_t padding_remaining = dynUpd.padding_bytes - offset_increase; uint32_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 { uint32_t padding_replenishment = dynUpd.first_mdat_atom->AtomicStart - mdat_pos; uint32_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() { uint32_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 ? (uint32_t)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 += (uint32_t)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; uint32_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 %u)\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 %u)\n", parsedAtoms[next_atom].AtomicName, parsedAtoms[next_atom].AtomicLevel, atom_size); //fprintf(stdout, "\t\teval %s's child atom %s, level:%i (sum %u, added %u)\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 > (uint32_t)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'); #if defined (_MSC_VER) /* apparently, long long is forbidden there*/ fprintf(stderr, "atom %s is %u bytes long which is greater than the filesize of %llu\n", parsedAtoms[iter].AtomicName, parsedAtoms[iter].AtomicLength, (long unsigned int)file_size); #else fprintf(stderr, "atom %s is %u bytes long which is greater than the filesize of %llu\n", parsedAtoms[iter].AtomicName, parsedAtoms[iter].AtomicLength, (long long unsigned int)file_size); #endif 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 = (uint64_t)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 = (float)((float)((uint32_t)simple_tally) * 100.0 / (double)(file_size-removed_bytes_tally) ); int percentage_difference = (int)lroundf((float)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 %llu\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, char* forced_suffix, bool random_filename = true) { char* suffix = NULL; 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 (_MSC_VER) 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 { getcwd(temp_path, MAXPATHLEN); 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", 5); } 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 fseeko(source_file, userdata_atom->AtomicStart, SEEK_SET); fread(dump_buffer, 1, (size_t)userdata_atom->AtomicLength, source_file); 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((uint64_t)current_time, container_header_atom->AtomicData+8); } else { UInt32_TO_String4(current_time, container_header_atom->AtomicData+4); } return; } void APar_ShellProgressBar(uint32_t bytes_written) { if (dynUpd.updage_by_padding) { return; } strcpy(file_progress_buffer, " Progress: "); double dispprog = (double)bytes_written/(double)new_file_size * 100.0 *( (double)max_display_width/100.0); int display_progress = (int)lroundf((float)dispprog); double percomp = 100.0 * (double)((double)bytes_written/ (double)new_file_size); int percentage_complete = (int)lroundf((float)percomp); for (int i = 0; i <= max_display_width; i++) { if (i < display_progress ) { strcat(file_progress_buffer, "="); } else if (i == display_progress) { sprintf(file_progress_buffer, "%s>%d%%", file_progress_buffer, percentage_complete); } else { strcat(file_progress_buffer, "-"); } } if (percentage_complete < 100) { strcat(file_progress_buffer, "-"); if (percentage_complete < 10) { strcat(file_progress_buffer, "-"); } } strcat(file_progress_buffer, "|"); fprintf(stdout, "%s\r", file_progress_buffer); fflush(stdout); return; } void APar_MergeTempFile(FILE* dest_file, FILE *src_file, uint32_t src_file_size, uint32_t dest_position, char* &buffer) { uint32_t file_pos = 0; while (file_pos <= src_file_size) { if (file_pos + max_buffer <= src_file_size ) { fseeko(src_file, file_pos, SEEK_SET); fread(buffer, 1, (size_t)max_buffer, src_file); //fseek(dest_file, dest_position + file_pos, SEEK_SET); #if defined(_MSC_VER) 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, (size_t)max_buffer, 1, dest_file); file_pos += max_buffer; } else { fseeko(src_file, file_pos, SEEK_SET); fread(buffer, 1, (size_t)(src_file_size - file_pos), src_file); //fprintf(stdout, "buff starts with %s\n", buffer+4); #if defined(_MSC_VER) 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, (size_t)(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) fflush(dest_file); SetEndOfFile((HANDLE)_get_osfhandle(fileno(dest_file))); #else ftruncate(fileno(dest_file), src_file_size+dest_position); #endif } return; } uint32_t APar_WriteAtomically(FILE* source_file, FILE* temp_file, bool from_file, char* &buffer, uint32_t bytes_written_tally, short this_atom) { uint32_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 = (uint32_t)file_size - parsedAtoms[this_atom].AtomicLength; } else if (parsedAtoms[this_atom].AtomicLength == 1) { //part of the pseudo 64-bit support parsedAtoms[this_atom].AtomicLength = (uint32_t)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. while (bytes_written <= parsedAtoms[this_atom].AtomicLength) { if (bytes_written + max_buffer <= parsedAtoms[this_atom].AtomicLength ) { //fprintf(stdout, "Writing atom %s from file looping into buffer\n", parsedAtoms[this_atom].AtomicName); //fprintf(stdout, "Writing atom %s from file looping into buffer %u - %u | %u\n", parsedAtoms[this_atom].AtomicName, parsedAtoms[this_atom].AtomicLength, bytes_written_tally, bytes_written); //read&write occurs from & including atom name through end of atom fseeko(source_file, (bytes_written + parsedAtoms[this_atom].AtomicStart), SEEK_SET); fread(buffer, 1, (size_t)max_buffer, source_file); fseeko(temp_file, (bytes_written_tally + bytes_written), SEEK_SET); fwrite(buffer, (size_t)max_buffer, 1, temp_file); bytes_written += max_buffer; APar_ShellProgressBar(bytes_written_tally + bytes_written); } else { //we either came up on a short atom (most are), or the last bit of a really long atom //fprintf(stdout, "Writing atom %s from file directly into buffer\n", parsedAtoms[this_atom].AtomicName); fseeko(source_file, (bytes_written + parsedAtoms[this_atom].AtomicStart), SEEK_SET); fread(buffer, 1, (size_t)(parsedAtoms[this_atom].AtomicLength - bytes_written), source_file); fseeko(temp_file, (bytes_written_tally + bytes_written), SEEK_SET); fwrite(buffer, (size_t)(parsedAtoms[this_atom].AtomicLength - bytes_written), 1, temp_file); bytes_written += parsedAtoms[this_atom].AtomicLength - bytes_written; APar_ShellProgressBar(bytes_written_tally + bytes_written); break; } } } else { // we are going to be writing not from the file, but directly from the tree (in memory). uint32_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, "%u\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( (uint32_t)parsedAtoms[this_atom].AtomicVerFlags, twenty_byte_buffer); fwrite(twenty_byte_buffer, 4, 1, temp_file); bytes_written += 4; } uint32_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( (uint32_t)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, uint32_t last_atom_pos, char* buffer) { uint32_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, (size_t)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, (size_t)(gapless_void_padding - gapless_padding_bytes_written), 1, mp4file); gapless_padding_bytes_written+= (gapless_void_padding - gapless_padding_bytes_written); break; } } return; } 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; uint32_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 uint32_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 (_MSC_VER) char* invisi_command=(char*)malloc(sizeof(char)*2*MAXPATHLEN); memset( invisi_command, 0, 2*MAXPATHLEN+1); memcpy( invisi_command, "ATTRIB +S +H ", 13); memcpy( invisi_command + strlen(invisi_command), temp_file_name, strlen(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 (DARWIN_PLATFORM) 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 (strncmp(ISObasemediafile,outfile,strlen(outfile)) == 0 && (strlen(outfile) == strlen(ISObasemediafile)) ) { //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 (DARWIN_PLATFORM) 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 (DARWIN_PLATFORM) 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 temp file.\n"); } 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 temp file.\n"); 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); uint32_t metadata_len = (uint32_t)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 insuffiecient space to retag the source file (%u!=%u).\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 ( IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { #if defined (_MSC_VER) /* native windows seems to require removing the file first; rename() on Mac OS X does the removing automatically as needed */ wchar_t* utf16_filepath = Convert_multibyteUTF8_to_wchar(ISObasemediafile); _wremove(utf16_filepath); free(utf16_filepath); utf16_filepath = NULL; #endif } else { remove(ISObasemediafile); } 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); 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 ( IsUnicodeWinOS() && UnicodeOutputStatus == WIN32_UTF16) { #if defined (_MSC_VER) 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; #endif } else { 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; } atomicparsley-0.9.2~svn110.orig/src/AP_iconv.cpp0000644000000000000000000005461311226366037016402 0ustar //==================================================================// /* AtomicParsley - AP_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 2005-2007 puck_lock */ //==================================================================// #include #include #if defined (WIN32) #include #include "AP_iconv.h" #endif #include "AP_commons.h" //==================================================================// // 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 = 1; void xmlInitEndianDetection() { unsigned short int tst = 0x1234; unsigned char *ptr = (unsigned char *) &tst; if (*ptr == 0x12) xmlLittleEndian = 0; else if (*ptr == 0x34) xmlLittleEndian = 1; return; } /** * 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) 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; } atomicparsley-0.9.2~svn110.orig/src/APar_sha1.cpp0000644000000000000000000003153711226366037016443 0ustar /* sha1.c - 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 APar_Common.h alignment macros (for msvc) */ #include "APar_sha1.h" #include #include /* SWAP does an endian swap on architectures that are little-endian, as SHA1 needs some data in a big-endian form. */ /* #if defined (__ppc__) || defined (__ppc64__) # define SWAP(n) (n) #else # define SWAP(n) \ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) #endif */ #define BLOCKSIZE 4096 #if BLOCKSIZE % 64 != 0 # error "invalid BLOCKSIZE" #endif /* This array contains the bytes used to pad the buffer to the next 64-byte boundary. (RFC 1321, 3.1: Step 1) */ static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; /* Takes a pointer to a 160 bit block of data (five 32 bit ints) and intializes it to the start constants of the SHA1 algorithm. This must be called before using hash in the call to sha1_hash. */ void sha1_init_ctx (struct sha1_ctx *ctx) { ctx->A = 0x67452301; ctx->B = 0xefcdab89; ctx->C = 0x98badcfe; ctx->D = 0x10325476; ctx->E = 0xc3d2e1f0; ctx->total[0] = ctx->total[1] = 0; ctx->buflen = 0; } /* Put result from CTX in first 20 bytes following RESBUF. The result must be in little endian byte order. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ void * sha1_read_ctx (const struct sha1_ctx *ctx, void *resbuf) { ((md5_uint32 *) resbuf)[0] = SWAP32 (ctx->A); ((md5_uint32 *) resbuf)[1] = SWAP32 (ctx->B); ((md5_uint32 *) resbuf)[2] = SWAP32 (ctx->C); ((md5_uint32 *) resbuf)[3] = SWAP32 (ctx->D); ((md5_uint32 *) resbuf)[4] = SWAP32 (ctx->E); return resbuf; } /* Process the remaining bytes in the internal buffer and the usual prolog according to the standard and write the result to RESBUF. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ void * sha1_finish_ctx (struct sha1_ctx *ctx, void *resbuf) { /* Take yet unprocessed bytes into account. */ md5_uint32 bytes = ctx->buflen; size_t pad; /* Now count remaining bytes. */ ctx->total[0] += bytes; if (ctx->total[0] < bytes) ++ctx->total[1]; pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; memcpy (&ctx->buffer[bytes], fillbuf, pad); /* Put the 64-bit file length in *bits* at the end of the buffer. */ *(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP32 (ctx->total[0] << 3); *(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP32 ((ctx->total[1] << 3) | (ctx->total[0] >> 29)); /* Process last bytes. */ sha1_process_block (ctx->buffer, bytes + pad + 8, ctx); return sha1_read_ctx (ctx, resbuf); } /* Compute SHA1 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ int sha1_stream (FILE *stream, void *resblock) { struct sha1_ctx ctx; char buffer[BLOCKSIZE + 72]; size_t sum; /* Initialize the computation context. */ sha1_init_ctx (&ctx); /* Iterate over full file contents. */ while (1) { /* We read the file in blocks of BLOCKSIZE bytes. One call of the computation function processes the whole buffer so that with the next round of the loop another block can be read. */ size_t n; sum = 0; /* Read block. Take care for partial reads. */ while (1) { n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream); sum += n; if (sum == BLOCKSIZE) break; if (n == 0) { /* Check for the error flag IFF N == 0, so that we don't exit the loop after a partial read due to e.g., EAGAIN or EWOULDBLOCK. */ if (ferror (stream)) return 1; goto process_partial_block; } /* We've read at least one byte, so ignore errors. But always check for EOF, since feof may be true even though N > 0. Otherwise, we could end up calling fread after EOF. */ if (feof (stream)) goto process_partial_block; } /* Process buffer with BLOCKSIZE bytes. Note that BLOCKSIZE % 64 == 0 */ sha1_process_block (buffer, BLOCKSIZE, &ctx); } process_partial_block:; /* Process any remaining bytes. */ if (sum > 0) sha1_process_bytes (buffer, sum, &ctx); /* Construct result in desired memory. */ sha1_finish_ctx (&ctx, resblock); return 0; } /* Compute MD5 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ void * sha1_buffer (const char *buffer, size_t len, void *resblock) { struct sha1_ctx ctx; /* Initialize the computation context. */ sha1_init_ctx (&ctx); /* Process whole buffer but last len % 64 bytes. */ sha1_process_bytes (buffer, len, &ctx); /* Put result in desired memory area. */ return sha1_finish_ctx (&ctx, resblock); } void sha1_process_bytes (const void *buffer, size_t len, struct sha1_ctx *ctx) { /* When we already have some bits in our internal buffer concatenate both inputs first. */ if (ctx->buflen != 0) { size_t left_over = ctx->buflen; size_t add = 128 - left_over > len ? len : 128 - left_over; memcpy (&ctx->buffer[left_over], buffer, add); ctx->buflen += add; if (ctx->buflen > 64) { sha1_process_block (ctx->buffer, ctx->buflen & ~63, ctx); ctx->buflen &= 63; /* The regions in the following copy operation cannot overlap. */ memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63], ctx->buflen); } buffer = (const char *) buffer + add; len -= add; } /* Process available complete blocks. */ if (len >= 64) { #if !_STRING_ARCH_unaligned # define alignof(type) offsetof (struct { char c; type x; }, x) # define UNALIGNED_P(p) (((size_t) p) % 4 != 0) //# define UNALIGNED_P(p) (((size_t) p) % alignof (md5_uint32) != 0) if (UNALIGNED_P (buffer)) while (len > 64) { sha1_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx); buffer = (const char *) buffer + 64; len -= 64; } else #endif { sha1_process_block (buffer, len & ~63, ctx); buffer = (const char *) buffer + (len & ~63); len &= 63; } } /* Move remaining bytes in internal buffer. */ if (len > 0) { size_t left_over = ctx->buflen; memcpy (&ctx->buffer[left_over], buffer, len); left_over += len; if (left_over >= 64) { sha1_process_block (ctx->buffer, 64, ctx); left_over -= 64; memcpy (ctx->buffer, &ctx->buffer[64], left_over); } ctx->buflen = left_over; } } /* --- Code below is the primary difference between md5.c and sha1.c --- */ /* SHA1 round constants */ #define K1 0x5a827999L #define K2 0x6ed9eba1L #define K3 0x8f1bbcdcL #define K4 0xca62c1d6L /* Round functions. Note that F2 is the same as F4. */ #define F1(B,C,D) ( D ^ ( B & ( C ^ D ) ) ) #define F2(B,C,D) (B ^ C ^ D) #define F3(B,C,D) ( ( B & C ) | ( D & ( B | C ) ) ) #define F4(B,C,D) (B ^ C ^ D) /* Process LEN bytes of BUFFER, accumulating context into CTX. It is assumed that LEN % 64 == 0. Most of this code comes from GnuPG's cipher/sha1.c. */ void sha1_process_block (const void *buffer, size_t len, struct sha1_ctx *ctx) { const md5_uint32 *words = (md5_uint32*)buffer; size_t nwords = len / sizeof (md5_uint32); const md5_uint32 *endp = words + nwords; md5_uint32 x[16]; md5_uint32 a = ctx->A; md5_uint32 b = ctx->B; md5_uint32 c = ctx->C; md5_uint32 d = ctx->D; md5_uint32 e = ctx->E; /* First increment the byte count. RFC 1321 specifies the possible length of the file up to 2^64 bits. Here we only compute the number of bytes. Do a double word increment. */ ctx->total[0] += len; if (ctx->total[0] < len) ++ctx->total[1]; #define rol(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) #define M(I) ( tm = x[I&0x0f] ^ x[(I-14)&0x0f] \ ^ x[(I-8)&0x0f] ^ x[(I-3)&0x0f] \ , (x[I&0x0f] = rol(tm, 1)) ) #define R(A,B,C,D,E,F,K,M) do { E += rol( A, 5 ) \ + F( B, C, D ) \ + K \ + M; \ B = rol( B, 30 ); \ } while(0) while (words < endp) { md5_uint32 tm; int t; for (t = 0; t < 16; t++) { x[t] = SWAP32 (*words); words++; } R( a, b, c, d, e, F1, K1, x[ 0] ); R( e, a, b, c, d, F1, K1, x[ 1] ); R( d, e, a, b, c, F1, K1, x[ 2] ); R( c, d, e, a, b, F1, K1, x[ 3] ); R( b, c, d, e, a, F1, K1, x[ 4] ); R( a, b, c, d, e, F1, K1, x[ 5] ); R( e, a, b, c, d, F1, K1, x[ 6] ); R( d, e, a, b, c, F1, K1, x[ 7] ); R( c, d, e, a, b, F1, K1, x[ 8] ); R( b, c, d, e, a, F1, K1, x[ 9] ); R( a, b, c, d, e, F1, K1, x[10] ); R( e, a, b, c, d, F1, K1, x[11] ); R( d, e, a, b, c, F1, K1, x[12] ); R( c, d, e, a, b, F1, K1, x[13] ); R( b, c, d, e, a, F1, K1, x[14] ); R( a, b, c, d, e, F1, K1, x[15] ); R( e, a, b, c, d, F1, K1, M(16) ); R( d, e, a, b, c, F1, K1, M(17) ); R( c, d, e, a, b, F1, K1, M(18) ); R( b, c, d, e, a, F1, K1, M(19) ); R( a, b, c, d, e, F2, K2, M(20) ); R( e, a, b, c, d, F2, K2, M(21) ); R( d, e, a, b, c, F2, K2, M(22) ); R( c, d, e, a, b, F2, K2, M(23) ); R( b, c, d, e, a, F2, K2, M(24) ); R( a, b, c, d, e, F2, K2, M(25) ); R( e, a, b, c, d, F2, K2, M(26) ); R( d, e, a, b, c, F2, K2, M(27) ); R( c, d, e, a, b, F2, K2, M(28) ); R( b, c, d, e, a, F2, K2, M(29) ); R( a, b, c, d, e, F2, K2, M(30) ); R( e, a, b, c, d, F2, K2, M(31) ); R( d, e, a, b, c, F2, K2, M(32) ); R( c, d, e, a, b, F2, K2, M(33) ); R( b, c, d, e, a, F2, K2, M(34) ); R( a, b, c, d, e, F2, K2, M(35) ); R( e, a, b, c, d, F2, K2, M(36) ); R( d, e, a, b, c, F2, K2, M(37) ); R( c, d, e, a, b, F2, K2, M(38) ); R( b, c, d, e, a, F2, K2, M(39) ); R( a, b, c, d, e, F3, K3, M(40) ); R( e, a, b, c, d, F3, K3, M(41) ); R( d, e, a, b, c, F3, K3, M(42) ); R( c, d, e, a, b, F3, K3, M(43) ); R( b, c, d, e, a, F3, K3, M(44) ); R( a, b, c, d, e, F3, K3, M(45) ); R( e, a, b, c, d, F3, K3, M(46) ); R( d, e, a, b, c, F3, K3, M(47) ); R( c, d, e, a, b, F3, K3, M(48) ); R( b, c, d, e, a, F3, K3, M(49) ); R( a, b, c, d, e, F3, K3, M(50) ); R( e, a, b, c, d, F3, K3, M(51) ); R( d, e, a, b, c, F3, K3, M(52) ); R( c, d, e, a, b, F3, K3, M(53) ); R( b, c, d, e, a, F3, K3, M(54) ); R( a, b, c, d, e, F3, K3, M(55) ); R( e, a, b, c, d, F3, K3, M(56) ); R( d, e, a, b, c, F3, K3, M(57) ); R( c, d, e, a, b, F3, K3, M(58) ); R( b, c, d, e, a, F3, K3, M(59) ); R( a, b, c, d, e, F4, K4, M(60) ); R( e, a, b, c, d, F4, K4, M(61) ); R( d, e, a, b, c, F4, K4, M(62) ); R( c, d, e, a, b, F4, K4, M(63) ); R( b, c, d, e, a, F4, K4, M(64) ); R( a, b, c, d, e, F4, K4, M(65) ); R( e, a, b, c, d, F4, K4, M(66) ); R( d, e, a, b, c, F4, K4, M(67) ); R( c, d, e, a, b, F4, K4, M(68) ); R( b, c, d, e, a, F4, K4, M(69) ); R( a, b, c, d, e, F4, K4, M(70) ); R( e, a, b, c, d, F4, K4, M(71) ); R( d, e, a, b, c, F4, K4, M(72) ); R( c, d, e, a, b, F4, K4, M(73) ); R( b, c, d, e, a, F4, K4, M(74) ); R( a, b, c, d, e, F4, K4, M(75) ); R( e, a, b, c, d, F4, K4, M(76) ); R( d, e, a, b, c, F4, K4, M(77) ); R( c, d, e, a, b, F4, K4, M(78) ); R( b, c, d, e, a, F4, K4, M(79) ); a = ctx->A += a; b = ctx->B += b; c = ctx->C += c; d = ctx->D += d; e = ctx->E += e; } } atomicparsley-0.9.2~svn110.orig/src/AtomicParsley.h0000644000000000000000000006267411226366037017133 0ustar //==================================================================// /* 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 */ //==================================================================// #if defined (_WIN32) || defined (_MSC_VER) #define MAXPATHLEN 255 #else #include #endif #if defined (_MSC_VER) #define _UNICODE #define strncasecmp strnicmp #include /* for WriteConsoleW */ #endif #include "AP_commons.h" #include "AP_ID3v2_Definitions.h" const uint32_t AtomFlags_Data_Binary = 0; // Atom version 1byte/ Atom flags 3 bytes; 0x00 00 00 00 const uint32_t AtomFlags_Data_Text = 1; // UTF-8, no termination const uint32_t AtomFlags_Data_JPEGBinary = 13; // \x0D const uint32_t AtomFlags_Data_PNGBinary = 14; // \x0E const uint32_t AtomFlags_Data_UInt = 21; // \x15 for cpil, tmpo, rtng; iTMS atoms: cnID, atID, plID, geID, sfID, akID const uint32_t AtomFlags_Data_uuid_binary = 88; // 0x58 for uuid atoms that contain files 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 }; struct AtomicInfo { short AtomicNumber; uint32_t AtomicStart; uint32_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; }; //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. typedef 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; uint32_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 { char* known_atom_name; 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 { char* stik_string; uint8_t stik_number; } stiks; typedef struct { char* storefront_string; uint32_t storefront_number; } sfIDs; typedef struct { char* iso639_2_code; char* iso639_1_code; char* language_in_english; } iso639_lang; typedef struct { char* media_rating; char* media_rating_cli_str; } m_ratings; 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 }; //==========================================================// extern bool parsedfile; extern bool file_opened; extern bool modified_atoms; extern bool alter_original; 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 uint32_t mdatData; extern uint32_t file_size; extern uint32_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[]; //==========================================================// #define AtomicParsley_version "0.9.0" #define MAX_ATOMS 1024 #define MAXDATA_PAYLOAD 1256 #define DEFAULT_PADDING_LENGTH 2048; #define MINIMUM_REQUIRED_PADDING_LENGTH 0; #define MAXIMUM_REQUIRED_PADDING_LENGTH 5000; //--------------------------------------------------------------------------------------------------------------------------------// //--------------------------------------------------------------------------------------------------------------------------------// 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, 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, uint32_t ancillary_data, uint8_t anc_bit_width); void APar_atom_Binary_Put(AtomicInfo* target_atom, const char* binary_data, uint32_t bytecount, uint32_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_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, char* uuidName, const uint32_t dataType, const char* uuidValue, bool shellAtom); uint16_t APar_TestVideoDescription(AtomicInfo* video_desc_atom, FILE* ISObmff_file); //test whether the ipod uuid can be added for a video track 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(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(char* frameID_str, char meta_area, 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); //--------------------------------------------------------------------------------------------------------------------------------// /* 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 */ atomicparsley-0.9.2~svn110.orig/src/AP_ID3v2_tags.h0000644000000000000000000000462111226366037016570 0ustar //==================================================================// /* AtomicParsley - AP_ID3v2_tags.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 */ //==================================================================// struct AdjunctArgs { char* targetLang; char* descripArg; char* mimeArg; char* pictypeArg; char* filenameArg; char* ratingArg; 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(); char* ConvertCLIFrameStr_TO_frameID(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(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, char* frame_str, char* frame_payload, AdjunctArgs* adjunct_payloads, uint8_t str_encoding); void APar_FreeID32Memory(ID3v2Tag* id32tag); atomicparsley-0.9.2~svn110.orig/src/AP_NSFile_utils.mm0000644000000000000000000001703311226366037017446 0ustar //==================================================================// /* AtomicParsley - AP_NSFile_utils.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 */ //==================================================================// #import #include "AP_commons.h" #include "AtomicParsley.h" /*---------------------- 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; } 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 char* output_suffix = strrchr(outputPath, '.'); char* typecode = (char*)malloc( sizeof(char)* 4 ); memset(typecode, 0, sizeof(char)*4); uint32_t type_code = APar_4CC_CreatorCode(inputPath, 0); UInt32_TO_String4(type_code, typecode); //fprintf(stdout, "%s - %s\n", typecode, input_suffix); APar_TestTracksForKind(); if (strncasecmp(input_suffix, ".mp4", 4) == 0 || strncasecmp(output_suffix, ".mp4", 4) == 0) { //only work on the generic .mp4 extension if (track_codecs.has_avc1 || track_codecs.has_mp4v || track_codecs.has_drmi) { type_code = APar_4CC_CreatorCode(outputPath, 'M4V '); //for a podcast an audio track with either a text, jpeg or url track is required, otherwise it will fall through to generic m4a; //files that are already .m4b or 'M4B ' don't even enter into this situation, so they are safe //if the file had video with subtitles (tx3g), then it would get taken care of above in the video section - unsupported by QT currently } else if (track_codecs.has_mp4a && (track_codecs.has_timed_text || track_codecs.has_timed_jpeg || track_codecs.has_timed_tx3g) ) { type_code = APar_4CC_CreatorCode(outputPath, 'M4B '); //default to audio; technically so would a drms iTMS drm audio file with ".mp4". But that would also mean it was renamed. They should be 'M4P ' } else { type_code = APar_4CC_CreatorCode(outputPath, 'M4A '); } } else if (track_codecs.has_avc1 || track_codecs.has_mp4v || track_codecs.has_drmi) { type_code = APar_4CC_CreatorCode(outputPath, 'M4V '); } return; } atomicparsley-0.9.2~svn110.orig/src/config.h.in0000644000000000000000000001027011226366037016212 0ustar /* src/config.h.in. Generated from configure.ac by autoheader. */ /* Define program version */ #undef AP_VER /* build binary with debug output */ #undef DEBUG /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_DIRENT_H /* Define to 1 if you have the header file. */ #undef HAVE_ERRNO_H /* Define to 1 if you have the `fseeko' function. */ #undef HAVE_FSEEKO /* Define to 1 if you have the `fsetpos' function. */ #undef HAVE_FSETPOS /* Define to 1 if you have the header file. */ #undef HAVE_GETOPT_H /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_CDROM_H /* Define to 1 if you have the `lroundf' function. */ #undef HAVE_LROUNDF /* Define to 1 if your system has a GNU libc compatible `malloc' function, and to 0 otherwise. */ #undef HAVE_MALLOC /* Define to 1 if you have the header file. */ #undef HAVE_MATH_H /* Define to 1 if you have the `memcmp' function. */ #undef HAVE_MEMCMP /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H /* Define to 1 if you have the `memset' function. */ #undef HAVE_MEMSET /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_NDIR_H /* Define to 1 if you have the `remove' function. */ #undef HAVE_REMOVE /* Define to 1 if you have the `rename' function. */ #undef HAVE_RENAME /* Define to 1 if you have the header file. */ #undef HAVE_SIGNAL_H /* Define to 1 if you have the `sranddev' function. */ #undef HAVE_SRANDDEV /* Define to 1 if you have the `sscanf' function. */ #undef HAVE_SSCANF /* Define to 1 if you have the header file. */ #undef HAVE_STDDEF_H /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDIO_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the `strdup' function. */ #undef HAVE_STRDUP /* Define to 1 if you have the `strerror' function. */ #undef HAVE_STRERROR /* Define to 1 if you have the `strftime' function. */ #undef HAVE_STRFTIME /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if you have the `strncasecmp' function. */ #undef HAVE_STRNCASECMP /* Define to 1 if you have the `strncmp' function. */ #undef HAVE_STRNCMP /* Define to 1 if you have the `strrchr' function. */ #undef HAVE_STRRCHR /* Define to 1 if you have the `strsep' function. */ #undef HAVE_STRSEP /* Define to 1 if you have the `strstr' function. */ #undef HAVE_STRSTR /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_SYS_DIR_H /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_SYS_NDIR_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* Define to 1 if you have that is POSIX.1 compatible. */ #undef HAVE_SYS_WAIT_H /* Define to 1 if you have the header file. */ #undef HAVE_TIME_H /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Define to 1 if you have the header file. */ #undef HAVE_WCHAR_H /* Define to 1 if you have the header file. */ #undef HAVE_ZLIB_H /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS /* Define to empty if `const' does not conform to ANSI C. */ #undef const /* Define to rpl_malloc if the replacement function should be used. */ #undef malloc /* OS Platform name */ #undef os_name atomicparsley-0.9.2~svn110.orig/src/AP_NSImage.mm0000644000000000000000000001714611226366037016376 0ustar //==================================================================// /* AtomicParsley - AP_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 */ //==================================================================// #import #include #include #include #include #include #include "AtomicParsley.h" #include "AP_NSImage.h" 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\xE0", 4) == 0) { isJPEG=true; isPNG=false; } free(picHeader); picHeader=NULL; return; } char* DeriveNewPath(const char *filePath, PicPrefs myPicPrefs, char* newpath) { 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; } } off_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; }